/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = "./src/converse.js"); /******/ }) /************************************************************************/ /******/ ({ /***/ "./node_modules/backbone.nativeview/backbone.nativeview.js": /*!*****************************************************************!*\ !*** ./node_modules/backbone.nativeview/backbone.nativeview.js ***! \*****************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Backbone.NativeView.js 0.3.3 // --------------- // (c) 2015 Adam Krebs, Jimmy Yuen Ho Wong // Backbone.NativeView may be freely distributed under the MIT license. // For all details and documentation: // https://github.com/akre54/Backbone.NativeView (function (factory) { if (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else {} }(function (Backbone) { // Cached regex to match an opening '<' of an HTML tag, possibly left-padded // with whitespace. var paddedLt = /^\s*=9 and modern browsers. var matchesSelector = ElementProto.matches || ElementProto.webkitMatchesSelector || ElementProto.mozMatchesSelector || ElementProto.msMatchesSelector || ElementProto.oMatchesSelector || // Make our own `Element#matches` for IE8 function(selector) { // Use querySelectorAll to find all elements matching the selector, // then check if the given element is included in that list. // Executing the query on the parentNode reduces the resulting nodeList, // (document doesn't have a parentNode). var nodeList = (this.parentNode || document).querySelectorAll(selector) || []; return ~indexOf(nodeList, this); }; // Cache Backbone.View for later access in constructor var BBView = Backbone.View; // To extend an existing view to use native methods, extend the View prototype // with the mixin: _.extend(MyView.prototype, Backbone.NativeViewMixin); Backbone.NativeViewMixin = { _domEvents: null, constructor: function() { this._domEvents = []; return BBView.apply(this, arguments); }, $: function(selector) { return this.el.querySelectorAll(selector); }, _removeElement: function() { this.undelegateEvents(); if (this.el.parentNode) this.el.parentNode.removeChild(this.el); }, // Apply the `element` to the view. `element` can be a CSS selector, // a string of HTML, or an Element node. _setElement: function(element) { if (typeof element == 'string') { if (paddedLt.test(element)) { var el = document.createElement('div'); el.innerHTML = element; this.el = el.firstChild; } else { this.el = document.querySelector(element); } } else { this.el = element; } }, // Set a hash of attributes to the view's `el`. We use the "prop" version // if available, falling back to `setAttribute` for the catch-all. _setAttributes: function(attrs) { for (var attr in attrs) { attr in this.el ? this.el[attr] = attrs[attr] : this.el.setAttribute(attr, attrs[attr]); } }, // Make a event delegation handler for the given `eventName` and `selector` // and attach it to `this.el`. // If selector is empty, the listener will be bound to `this.el`. If not, a // new handler that will recursively traverse up the event target's DOM // hierarchy looking for a node that matches the selector. If one is found, // the event's `delegateTarget` property is set to it and the return the // result of calling bound `listener` with the parameters given to the // handler. delegate: function(eventName, selector, listener) { if (typeof selector === 'function') { listener = selector; selector = null; } var root = this.el; var handler = selector ? function (e) { var node = e.target || e.srcElement; for (; node && node != root; node = node.parentNode) { if (matchesSelector.call(node, selector)) { e.delegateTarget = node; listener(e); } } } : listener; elementAddEventListener.call(this.el, eventName, handler, false); this._domEvents.push({eventName: eventName, handler: handler, listener: listener, selector: selector}); return handler; }, // Remove a single delegated event. Either `eventName` or `selector` must // be included, `selector` and `listener` are optional. undelegate: function(eventName, selector, listener) { if (typeof selector === 'function') { listener = selector; selector = null; } if (this.el) { var handlers = this._domEvents.slice(); for (var i = 0, len = handlers.length; i < len; i++) { var item = handlers[i]; var match = item.eventName === eventName && (listener ? item.listener === listener : true) && (selector ? item.selector === selector : true); if (!match) continue; elementRemoveEventListener.call(this.el, item.eventName, item.handler, false); this._domEvents.splice(indexOf(handlers, item), 1); } } return this; }, // Remove all events created with `delegate` from `el` undelegateEvents: function() { if (this.el) { for (var i = 0, len = this._domEvents.length; i < len; i++) { var item = this._domEvents[i]; elementRemoveEventListener.call(this.el, item.eventName, item.handler, false); }; this._domEvents.length = 0; } return this; } }; Backbone.NativeView = Backbone.View.extend(Backbone.NativeViewMixin); return Backbone.NativeView; })); /***/ }), /***/ "./node_modules/backbone.overview/backbone.orderedlistview.js": /*!********************************************************************!*\ !*** ./node_modules/backbone.overview/backbone.orderedlistview.js ***! \********************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*** IMPORTS FROM imports-loader ***/ var backbone = (backbone || {}); backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_modules/backbone.nativeview/backbone.nativeview.js"); /*! * Backbone.OrderedListView * * Copyright (c) 2017, JC Brand * Licensed under the Mozilla Public License (MPL) */ (function (root, factory) { if (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! backbone.overview */ "backbone.overview")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else {} }(this, function (_, Backbone) { "use strict"; Backbone.OrderedListView = Backbone.Overview.extend({ /* An OrderedListView is a special type of Overview which adds some * methods and conventions for rendering an ordered list of elements. */ // The `listItems` attribute denotes the path (from this View) to the // list of items. listItems: 'model', // The `sortEvent` attribute specifies the event which should cause the // ordered list to be sorted. sortEvent: 'change', // The `listSelector` is the selector used to query for the DOM list // element which contains the ordered items. listSelector: '.ordered-items', // The `itemView` is constructor which should be called to create a // View for a new item. ItemView: undefined, // The `subviewIndex` is the attribute of the list element model which // acts as the index of the subview in the overview. // An overview is a "Collection" of views, and they can be retrieved // via an index. By default this is the 'id' attribute, but it could be // set to something else. subviewIndex: 'id', initialize () { this.sortEventually = _.debounce( this.sortAndPositionAllItems.bind(this), 500); this.items = _.get(this, this.listItems); this.items.on('add', this.sortAndPositionAllItems, this); this.items.on('remove', this.removeView, this); if (!_.isNil(this.sortEvent)) { this.items.on(this.sortEvent, this.sortEventually, this); } }, createItemView (item) { let item_view = this.get(item.get(this.subviewIndex)); if (!item_view) { item_view = new this.ItemView({model: item}); this.add(item.get(this.subviewIndex), item_view); } else { item_view.model = item; item_view.initialize(); } item_view.render(); return item_view; }, removeView (item) { this.remove(item.get(this.subviewIndex)); }, sortAndPositionAllItems () { if (!this.items.length) { return; } this.items.sort(); const list_el = this.el.querySelector(this.listSelector); const div = document.createElement('div'); list_el.parentNode.replaceChild(div, list_el); this.items.each((item) => { let view = this.get(item.get(this.subviewIndex)); if (_.isUndefined(view)) { view = this.createItemView(item) } list_el.insertAdjacentElement('beforeend', view.el); }); div.parentNode.replaceChild(list_el, div); } }); return Backbone.OrderedListView; })); /***/ }), /***/ "./node_modules/backbone.vdomview/backbone.vdomview.js": /*!*************************************************************!*\ !*** ./node_modules/backbone.vdomview/backbone.vdomview.js ***! \*************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*** IMPORTS FROM imports-loader ***/ var backbone = (backbone || {}); backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_modules/backbone.nativeview/backbone.nativeview.js"); /*! * Backbone.VDOMView * * MIT Licensed. Copyright (c) 2017, JC Brand */ (function (root, factory) { if (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(/*! snabbdom */ "./node_modules/snabbdom/dist/snabbdom.js"), __webpack_require__(/*! snabbdom-attributes */ "./node_modules/snabbdom/dist/snabbdom-attributes.js"), __webpack_require__(/*! snabbdom-class */ "./node_modules/snabbdom/dist/snabbdom-class.js"), __webpack_require__(/*! snabbdom-dataset */ "./node_modules/snabbdom/dist/snabbdom-dataset.js"), __webpack_require__(/*! snabbdom-props */ "./node_modules/snabbdom/dist/snabbdom-props.js"), __webpack_require__(/*! snabbdom-style */ "./node_modules/snabbdom/dist/snabbdom-style.js"), __webpack_require__(/*! tovnode */ "./node_modules/snabbdom/dist/tovnode.js"), __webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js") ], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else {} }(this, function ( snabbdom, snabbdom_attributes, snabbdom_class, snabbdom_dataset, snabbdom_props, snabbdom_style, tovnode, _, Backbone) { "use strict"; let domParser = new DOMParser(); const patch = snabbdom.init([ snabbdom_attributes.default, snabbdom_class.default, snabbdom_dataset.default, snabbdom_props.default, snabbdom_style.default ]); const View = _.isUndefined(Backbone.NativeView) ? Backbone.View : Backbone.NativeView; function parseHTMLToDOM (html_str) { /* Parses a string with HTML and returns a DOM element. * * Forked from vdom_parser: * https://github.com/bitinn/vdom-parser */ if (typeof html_str !== 'string') { throw new Error('Invalid parameter type in parseHTMLToDOM'); } if ( !('DOMParser' in window) ) { throw new Error( 'DOMParser is not available, '+ 'so parsing string to DOM node is not possible.'); } if (!html_str) { return document.createTextNode(''); } domParser = domParser || new DOMParser(); const doc = domParser.parseFromString(html_str, 'text/html'); // most tags default to body if (doc.body.firstChild) { return doc.getElementsByTagName('body')[0].firstChild; // some tags, like script and style, default to head } else if (doc.head.firstChild && (doc.head.firstChild.tagName !== 'TITLE' || doc.title)) { return doc.head.firstChild; // special case for html comment, cdata, doctype } else if (doc.firstChild && doc.firstChild.tagName !== 'HTML') { return doc.firstChild; // other element, such as whitespace, or html/body/head tag, fallback to empty text node } else { return document.createTextNode(''); } } Backbone.VDOMView = View.extend({ updateEventListeners (old_vnode, new_vnode) { this.setElement(new_vnode.elm); }, render () { if (_.isFunction(this.beforeRender)) { this.beforeRender(); } let new_vnode; if (!_.isNil(this.toHTML)) { new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML())); } else { new_vnode = tovnode.toVNode(this.toDOM()); } new_vnode.data.hook = _.extend({ create: this.updateEventListeners.bind(this), update: this.updateEventListeners.bind(this) }); const el = this.vnode ? this.vnode.elm : this.el; if (el.outerHTML !== new_vnode.elm.outerHTML) { this.vnode = patch(this.vnode || this.el, new_vnode); } if (_.isFunction(this.afterRender)) { this.afterRender(); } return this; } }); return Backbone.VDOMView; })); /***/ }), /***/ "./node_modules/backbone/backbone.js": /*!*******************************************!*\ !*** ./node_modules/backbone/backbone.js ***! \*******************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Backbone.js 1.3.3 // (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function(factory) { // Establish the root object, `window` (`self`) in the browser, or `global` on the server. // We use `self` instead of `window` for `WebWorker` support. var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global); // Set up Backbone appropriately for the environment. Start with AMD. if (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! jquery */ "./src/jquery-stub.js"), exports], __WEBPACK_AMD_DEFINE_RESULT__ = (function(_, $, exports) { // Export global even in AMD case in case this script is loaded with // others that may still expect a global Backbone. root.Backbone = factory(root, exports, _, $); }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); // Next for Node.js or CommonJS. jQuery may not be needed as a module. } else { var _, $; } })(function(root, Backbone, _, $) { // Initial Setup // ------------- // Save the previous value of the `Backbone` variable, so that it can be // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; // Create a local reference to a common array method we'll want to use later. var slice = Array.prototype.slice; // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '1.3.3'; // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns // the `$` variable. Backbone.$ = $; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and // set a `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... this will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Proxy Backbone class methods to Underscore functions, wrapping the model's // `attributes` object or collection's `models` array behind the scenes. // // collection.filter(function(model) { return model.get('age') > 10 }); // collection.each(this.addView); // // `Function#apply` can be slow so we use the method's arg count, if we know it. var addMethod = function(length, method, attribute) { switch (length) { case 1: return function() { return _[method](this[attribute]); }; case 2: return function(value) { return _[method](this[attribute], value); }; case 3: return function(iteratee, context) { return _[method](this[attribute], cb(iteratee, this), context); }; case 4: return function(iteratee, defaultVal, context) { return _[method](this[attribute], cb(iteratee, this), defaultVal, context); }; default: return function() { var args = slice.call(arguments); args.unshift(this[attribute]); return _[method].apply(_, args); }; } }; var addUnderscoreMethods = function(Class, methods, attribute) { _.each(methods, function(length, method) { if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); }); }; // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. var cb = function(iteratee, instance) { if (_.isFunction(iteratee)) return iteratee; if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; return iteratee; }; var modelMatcher = function(attrs) { var matcher = _.matches(attrs); return function(model) { return matcher(model.attributes); }; }; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // a custom event channel. You may bind a callback to an event with `on` or // remove with `off`; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // var Events = Backbone.Events = {}; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Iterates over the standard `event, callback` (as well as the fancy multiple // space-separated events `"change blur", callback` and jQuery-style event // maps `{event: callback}`). var eventsApi = function(iteratee, events, name, callback, opts) { var i = 0, names; if (name && typeof name === 'object') { // Handle event maps. if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; for (names = _.keys(name); i < names.length ; i++) { events = eventsApi(iteratee, events, names[i], name[names[i]], opts); } } else if (name && eventSplitter.test(name)) { // Handle space-separated event names by delegating them individually. for (names = name.split(eventSplitter); i < names.length; i++) { events = iteratee(events, names[i], callback, opts); } } else { // Finally, standard events. events = iteratee(events, name, callback, opts); } return events; }; // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. Events.on = function(name, callback, context) { return internalOn(this, name, callback, context); }; // Guard the `listening` argument from the public API. var internalOn = function(obj, name, callback, context, listening) { obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, ctx: obj, listening: listening }); if (listening) { var listeners = obj._listeners || (obj._listeners = {}); listeners[listening.id] = listening; } return obj; }; // Inversion-of-control versions of `on`. Tell *this* object to listen to // an event in another object... keeping track of what it's listening to // for easier unbinding later. Events.listenTo = function(obj, name, callback) { if (!obj) return this; var id = obj._listenId || (obj._listenId = _.uniqueId('l')); var listeningTo = this._listeningTo || (this._listeningTo = {}); var listening = listeningTo[id]; // This object is not listening to any other events on `obj` yet. // Setup the necessary references to track the listening callbacks. if (!listening) { var thisId = this._listenId || (this._listenId = _.uniqueId('l')); listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; } // Bind callbacks on obj, and keep track of them on listening. internalOn(obj, name, callback, this, listening); return this; }; // The reducing API that adds a callback to the `events` object. var onApi = function(events, name, callback, options) { if (callback) { var handlers = events[name] || (events[name] = []); var context = options.context, ctx = options.ctx, listening = options.listening; if (listening) listening.count++; handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); } return events; }; // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. Events.off = function(name, callback, context) { if (!this._events) return this; this._events = eventsApi(offApi, this._events, name, callback, { context: context, listeners: this._listeners }); return this; }; // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. Events.stopListening = function(obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var ids = obj ? [obj._listenId] : _.keys(listeningTo); for (var i = 0; i < ids.length; i++) { var listening = listeningTo[ids[i]]; // If listening doesn't exist, this object is not currently // listening to obj. Break out early. if (!listening) break; listening.obj.off(name, callback, this); } return this; }; // The reducing API that removes a callback from the `events` object. var offApi = function(events, name, callback, options) { if (!events) return; var i = 0, listening; var context = options.context, listeners = options.listeners; // Delete all events listeners and "drop" events. if (!name && !callback && !context) { var ids = _.keys(listeners); for (; i < ids.length; i++) { listening = listeners[ids[i]]; delete listeners[listening.id]; delete listening.listeningTo[listening.objId]; } return; } var names = name ? [name] : _.keys(events); for (; i < names.length; i++) { name = names[i]; var handlers = events[name]; // Bail out if there are no events stored. if (!handlers) break; // Replace events if there are any remaining. Otherwise, clean up. var remaining = []; for (var j = 0; j < handlers.length; j++) { var handler = handlers[j]; if ( callback && callback !== handler.callback && callback !== handler.callback._callback || context && context !== handler.context ) { remaining.push(handler); } else { listening = handler.listening; if (listening && --listening.count === 0) { delete listeners[listening.id]; delete listening.listeningTo[listening.objId]; } } } // Update tail event if the list has any events. Otherwise, clean up. if (remaining.length) { events[name] = remaining; } else { delete events[name]; } } return events; }; // Bind an event to only be triggered a single time. After the first time // the callback is invoked, its listener will be removed. If multiple events // are passed in using the space-separated syntax, the handler will fire // once for each event, not once for a combination of all events. Events.once = function(name, callback, context) { // Map the event into a `{event: once}` object. var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); if (typeof name === 'string' && context == null) callback = void 0; return this.on(events, callback, context); }; // Inversion-of-control versions of `once`. Events.listenToOnce = function(obj, name, callback) { // Map the event into a `{event: once}` object. var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj)); return this.listenTo(obj, events); }; // Reduces the event callbacks into a map of `{event: onceWrapper}`. // `offer` unbinds the `onceWrapper` after it has been called. var onceMap = function(map, name, callback, offer) { if (callback) { var once = map[name] = _.once(function() { offer(name, once); callback.apply(this, arguments); }); once._callback = callback; } return map; }; // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). Events.trigger = function(name) { if (!this._events) return this; var length = Math.max(0, arguments.length - 1); var args = Array(length); for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; eventsApi(triggerApi, this._events, name, void 0, args); return this; }; // Handles triggering the appropriate event callbacks. var triggerApi = function(objEvents, name, callback, args) { if (objEvents) { var events = objEvents[name]; var allEvents = objEvents.all; if (events && allEvents) allEvents = allEvents.slice(); if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, [name].concat(args)); } return objEvents; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } }; // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who // want global "pubsub" in a convenient place. _.extend(Backbone, Events); // Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId(this.cidPrefix); this.attributes = {}; if (options.collection) this.collection = options.collection; if (options.parse) attrs = this.parse(attrs, options) || {}; var defaults = _.result(this, 'defaults'); attrs = _.defaults(_.extend({}, defaults, attrs), defaults); this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', // The prefix is used to create the client id which is used to identify models locally. // You may want to override this if you're experiencing name clashes with model ids. cidPrefix: 'c', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Special-cased proxy to underscore's `_.matches` method. matches: function(attrs) { return !!_.iteratee(attrs, this)(this.attributes); }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. var unset = options.unset; var silent = options.silent; var changes = []; var changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } var current = this.attributes; var changed = this.changed; var prev = this._previousAttributes; // For each `set` attribute, update or delete the current value. for (var attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { changed[attr] = val; } else { delete changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Update the `id`. if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = options; for (var i = 0; i < changes.length; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var old = this._changing ? this._previousAttributes : this.attributes; var changed = {}; for (var attr in diff) { var val = diff[attr]; if (_.isEqual(old[attr], val)) continue; changed[attr] = val; } return _.size(changed) ? changed : false; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server, merging the response with the model's // local attributes. Any changed attributes will trigger a "change" event. fetch: function(options) { options = _.extend({parse: true}, options); var model = this; var success = options.success; options.success = function(resp) { var serverAttrs = options.parse ? model.parse(resp, options) : resp; if (!model.set(serverAttrs, options)) return false; if (success) success.call(options.context, model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options = _.extend({validate: true, parse: true}, options); var wait = options.wait; // If we're not waiting and attributes exist, save acts as // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. if (attrs && !wait) { if (!this.set(attrs, options)) return false; } else if (!this._validate(attrs, options)) { return false; } // After a successful server-side save, the client is (optionally) // updated with the server-side state. var model = this; var success = options.success; var attributes = this.attributes; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = options.parse ? model.parse(resp, options) : resp; if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); if (serverAttrs && !model.set(serverAttrs, options)) return false; if (success) success.call(options.context, model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); // Set temporary attributes if `{wait: true}` to properly find new ids. if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch' && !options.attrs) options.attrs = attrs; var xhr = this.sync(method, this, options); // Restore attributes. this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var wait = options.wait; var destroy = function() { model.stopListening(); model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (wait) destroy(); if (success) success.call(options.context, model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; var xhr = false; if (this.isNew()) { _.defer(options.success); } else { wrapError(this, options); xhr = this.sync('delete', this, options); } if (!wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; var id = this.get(this.idAttribute); return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. isNew: function() { return !this.has(this.idAttribute); }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend({}, options, {validate: true})); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options, {validationError: error})); return false; } }); // Underscore methods that we want to implement on the Model, mapped to the // number of arguments they take. var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, omit: 0, chain: 1, isEmpty: 1}; // Mix in each Underscore method as a proxy to `Model#attributes`. addUnderscoreMethods(Model, modelMethods, 'attributes'); // Backbone.Collection // ------------------- // If models tend to represent a single row of data, a Backbone Collection is // more analogous to a table full of data ... or a small slice or page of that // table, or a collection of rows that belong together for a particular reason // -- all of the messages in this particular folder, all of the documents // belonging to this particular author, and so on. Collections maintain // indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; // Default options for `Collection#set`. var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; // Splices `insert` into `array` at index `at`. var splice = function(array, insert, at) { at = Math.min(Math.max(at, 0), array.length); var tail = Array(array.length - at); var length = insert.length; var i; for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; for (i = 0; i < length; i++) array[i + at] = insert[i]; for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; }; // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model: Model, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON: function(options) { return this.map(function(model) { return model.toJSON(options); }); }, // Proxy `Backbone.sync` by default. sync: function() { return Backbone.sync.apply(this, arguments); }, // Add a model, or list of models to the set. `models` may be Backbone // Models or raw JavaScript objects to be converted to Models, or any // combination of the two. add: function(models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. remove: function(models, options) { options = _.extend({}, options); var singular = !_.isArray(models); models = singular ? [models] : models.slice(); var removed = this._removeModels(models, options); if (!options.silent && removed.length) { options.changes = {added: [], merged: [], removed: removed}; this.trigger('update', this, options); } return singular ? removed[0] : removed; }, // Update a collection by `set`-ing a new list of models, adding new ones, // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **Model#set**, // the core operation for updating the data contained by the collection. set: function(models, options) { if (models == null) return; options = _.extend({}, setOptions, options); if (options.parse && !this._isModel(models)) { models = this.parse(models, options) || []; } var singular = !_.isArray(models); models = singular ? [models] : models.slice(); var at = options.at; if (at != null) at = +at; if (at > this.length) at = this.length; if (at < 0) at += this.length + 1; var set = []; var toAdd = []; var toMerge = []; var toRemove = []; var modelMap = {}; var add = options.add; var merge = options.merge; var remove = options.remove; var sort = false; var sortable = this.comparator && at == null && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; // Turn bare objects into model references, and prevent invalid models // from being added. var model, i; for (i = 0; i < models.length; i++) { model = models[i]; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. var existing = this.get(model); if (existing) { if (merge && model !== existing) { var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); toMerge.push(existing); if (sortable && !sort) sort = existing.hasChanged(sortAttr); } if (!modelMap[existing.cid]) { modelMap[existing.cid] = true; set.push(existing); } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { model = models[i] = this._prepareModel(model, options); if (model) { toAdd.push(model); this._addReference(model, options); modelMap[model.cid] = true; set.push(model); } } } // Remove stale models. if (remove) { for (i = 0; i < this.length; i++) { model = this.models[i]; if (!modelMap[model.cid]) toRemove.push(model); } if (toRemove.length) this._removeModels(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. var orderChanged = false; var replace = !sortable && add && remove; if (set.length && replace) { orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { return m !== set[index]; }); this.models.length = 0; splice(this.models, set, 0); this.length = this.models.length; } else if (toAdd.length) { if (sortable) sort = true; splice(this.models, toAdd, at == null ? this.length : at); this.length = this.models.length; } // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort/update events. if (!options.silent) { for (i = 0; i < toAdd.length; i++) { if (at != null) options.index = at + i; model = toAdd[i]; model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); if (toAdd.length || toRemove.length || toMerge.length) { options.changes = { added: toAdd, removed: toRemove, merged: toMerge }; this.trigger('update', this, options); } } // Return the added (or merged) model (or models). return singular ? models[0] : models; }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. reset: function(models, options) { options = options ? _.clone(options) : {}; for (var i = 0; i < this.models.length; i++) { this._removeReference(this.models[i], options); } options.previousModels = this.models; this._reset(); models = this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return models; }, // Add a model to the end of the collection. push: function(model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. pop: function(options) { var model = this.at(this.length - 1); return this.remove(model, options); }, // Add a model to the beginning of the collection. unshift: function(model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. shift: function(options) { var model = this.at(0); return this.remove(model, options); }, // Slice out a sub-array of models from the collection. slice: function() { return slice.apply(this.models, arguments); }, // Get a model from the set by id, cid, model object with id or cid // properties, or an attributes object that is transformed through modelId. get: function(obj) { if (obj == null) return void 0; return this._byId[obj] || this._byId[this.modelId(obj.attributes || obj)] || obj.cid && this._byId[obj.cid]; }, // Returns `true` if the model is in the collection. has: function(obj) { return this.get(obj) != null; }, // Get the model at the given index. at: function(index) { if (index < 0) index += this.length; return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { return this[first ? 'find' : 'filter'](attrs); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. findWhere: function(attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { var comparator = this.comparator; if (!comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); var length = comparator.length; if (_.isFunction(comparator)) comparator = _.bind(comparator, this); // Run sort based on type of `comparator`. if (length === 1 || _.isString(comparator)) { this.models = this.sortBy(comparator); } else { this.models.sort(comparator); } if (!options.silent) this.trigger('sort', this, options); return this; }, // Pluck an attribute from each model in the collection. pluck: function(attr) { return this.map(attr + ''); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = _.extend({parse: true}, options); var success = options.success; var collection = this; options.success = function(resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success.call(options.context, collection, resp, options); collection.trigger('sync', collection, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { options = options ? _.clone(options) : {}; var wait = options.wait; model = this._prepareModel(model, options); if (!model) return false; if (!wait) this.add(model, options); var collection = this; var success = options.success; options.success = function(m, resp, callbackOpts) { if (wait) collection.add(m, callbackOpts); if (success) success.call(callbackOpts.context, m, resp, callbackOpts); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse: function(resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. clone: function() { return new this.constructor(this.models, { model: this.model, comparator: this.comparator }); }, // Define how to uniquely identify models in the collection. modelId: function(attrs) { return attrs[this.model.prototype.idAttribute || 'id']; }, // Private method to reset all internal state. Called when the collection // is first initialized or reset. _reset: function() { this.length = 0; this.models = []; this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. _prepareModel: function(attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; return attrs; } options = options ? _.clone(options) : {}; options.collection = this; var model = new this.model(attrs, options); if (!model.validationError) return model; this.trigger('invalid', this, model.validationError, options); return false; }, // Internal method called by both remove and set. _removeModels: function(models, options) { var removed = []; for (var i = 0; i < models.length; i++) { var model = this.get(models[i]); if (!model) continue; var index = this.indexOf(model); this.models.splice(index, 1); this.length--; // Remove references before triggering 'remove' event to prevent an // infinite loop. #3693 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } removed.push(model); this._removeReference(model, options); } return removed; }, // Method for checking whether an object should be considered a model for // the purposes of adding to the collection. _isModel: function(model) { return model instanceof Model; }, // Internal method to create a model's ties to a collection. _addReference: function(model, options) { this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; model.on('all', this._onModelEvent, this); }, // Internal method to sever a model's ties to a collection. _removeReference: function(model, options) { delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { if (model) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (event === 'change') { var prevId = this.modelId(model.previousAttributes()); var id = this.modelId(model.attributes); if (prevId !== id) { if (prevId != null) delete this._byId[prevId]; if (id != null) this._byId[id] = model; } } } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; // Mix in each Underscore method as a proxy to `Collection#models`. addUnderscoreMethods(Collection, collectionMethods, 'models'); // Backbone.View // ------------- // Backbone Views are almost more convention than they are actual code. A View // is simply a JavaScript object that represents a logical chunk of UI in the // DOM. This might be a single item, an entire list, a sidebar or panel, or // even the surrounding frame which wraps your whole app. Defining a chunk of // UI as a **View** allows you to define your DOM events declaratively, without // having to worry about render order ... and makes it easy for the view to // react to specific changes in the state of your models. // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); _.extend(this, _.pick(options, viewOptions)); this._ensureElement(); this.initialize.apply(this, arguments); }; // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be set as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. $: function(selector) { return this.$el.find(selector); }, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render: function() { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable Backbone.Events listeners. remove: function() { this._removeElement(); this.stopListening(); return this; }, // Remove this view's element from the document and all event listeners // attached to it. Exposed for subclasses using an alternative DOM // manipulation API. _removeElement: function() { this.$el.remove(); }, // Change the view's element (`this.el` property) and re-delegate the // view's events on the new element. setElement: function(element) { this.undelegateEvents(); this._setElement(element); this.delegateEvents(); return this; }, // Creates the `this.el` and `this.$el` references for this view using the // given `el`. `el` can be a CSS selector or an HTML string, a jQuery // context or an element. Subclasses can override this to utilize an // alternative DOM manipulation API and are only required to set the // `this.el` property. _setElement: function(el) { this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); this.el = this.$el[0]; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save', // 'click .open': function(e) { ... } // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. delegateEvents: function(events) { events || (events = _.result(this, 'events')); if (!events) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[method]; if (!method) continue; var match = key.match(delegateEventSplitter); this.delegate(match[1], match[2], _.bind(method, this)); } return this; }, // Add a single event listener to the view's element (or a child element // using `selector`). This only works for delegate-able events: not `focus`, // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. delegate: function(eventName, selector, listener) { this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); return this; }, // Clears all callbacks previously bound to the view by `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // Backbone views attached to the same DOM element. undelegateEvents: function() { if (this.$el) this.$el.off('.delegateEvents' + this.cid); return this; }, // A finer-grained `undelegateEvents` for removing a single delegated event. // `selector` and `listener` are both optional. undelegate: function(eventName, selector, listener) { this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); return this; }, // Produces a DOM element to be assigned to your view. Exposed for // subclasses using an alternative DOM manipulation API. _createElement: function(tagName) { return document.createElement(tagName); }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.id) attrs.id = _.result(this, 'id'); if (this.className) attrs['class'] = _.result(this, 'className'); this.setElement(this._createElement(_.result(this, 'tagName'))); this._setAttributes(attrs); } else { this.setElement(_.result(this, 'el')); } }, // Set attributes from a hash on this view's element. Exposed for // subclasses using an alternative DOM manipulation API. _setAttributes: function(attributes) { this.$el.attr(attributes); } }); // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default options, unless specified. _.defaults(options || (options = {}), { emulateHTTP: Backbone.emulateHTTP, emulateJSON: Backbone.emulateJSON }); // Default JSON-request options. var params = {type: type, dataType: 'json'}; // Ensure that we have a URL. if (!options.url) { params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (options.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model: params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; options.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; } // Don't process data on a non-GET request. if (params.type !== 'GET' && !options.emulateJSON) { params.processData = false; } // Pass along `textStatus` and `errorThrown` from jQuery. var error = options.error; options.error = function(xhr, textStatus, errorThrown) { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) error.call(options.context, xhr, textStatus, errorThrown); }; // Make the request, allowing the user to override any Ajax options. var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }; // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PATCH', 'delete': 'DELETE', 'read': 'GET' }; // Set the default implementation of `Backbone.ajax` to proxy through to `$`. // Override this if you'd like to use a different library. Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; // Backbone.Router // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); } }); return this; }, // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. execute: function(callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; return param ? decodeURIComponent(param) : null; }); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on either // [pushState](http://diveintohtml5.info/history.html) and real URLs, or // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. var History = Backbone.History = function() { this.handlers = []; this.checkUrl = _.bind(this.checkUrl, this); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } }; // Cached regex for stripping a leading hash/slash and trailing space. var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes. var rootStripper = /^\/+|\/+$/g; // Cached regex for stripping urls of hash. var pathStripper = /#.*$/; // Has the history handling already been started? History.started = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Are we at the app root? atRoot: function() { var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return path === this.root && !this.getSearch(); }, // Does the pathname match the root? matchRoot: function() { var path = this.decodeFragment(this.location.pathname); var rootPath = path.slice(0, this.root.length - 1) + '/'; return rootPath === this.root; }, // Unicode characters in `location.pathname` are percent encoded so they're // decoded for comparison. `%25` should not be decoded since it may be part // of an encoded parameter. decodeFragment: function(fragment) { return decodeURI(fragment.replace(/%25/g, '%2525')); }, // In IE6, the hash fragment and search params are incorrect if the // fragment contains `?`. getSearch: function() { var match = this.location.href.replace(/#.*/, '').match(/\?.+/); return match ? match[0] : ''; }, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function(window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the pathname and search params, without the root. getPath: function() { var path = this.decodeFragment( this.location.pathname + this.getSearch() ).slice(this.root.length - 1); return path.charAt(0) === '/' ? path.slice(1) : path; }, // Get the cross-browser normalized URL fragment from the path or hash. getFragment: function(fragment) { if (fragment == null) { if (this._usePushState || !this._wantsHashChange) { fragment = this.getPath(); } else { fragment = this.getHash(); } } return fragment.replace(routeStripper, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { if (History.started) throw new Error('Backbone.history has already been started'); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); this._useHashChange = this._wantsHashChange && this._hasHashChange; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.history && this.history.pushState); this._usePushState = this._wantsPushState && this._hasPushState; this.fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); // Transition from hashChange to pushState or vice versa if both are // requested. if (this._wantsHashChange && this._wantsPushState) { // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !this.atRoot()) { var rootPath = this.root.slice(0, -1) || '/'; this.location.replace(rootPath + '#' + this.getPath()); // Return immediately as browser will do redirect to new url return true; // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._hasPushState && this.atRoot()) { this.navigate(this.getHash(), {replace: true}); } } // Proxy an iframe to handle location events if the browser doesn't // support the `hashchange` event, HTML5 history, or the user wants // `hashChange` but not `pushState`. if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) { this.iframe = document.createElement('iframe'); this.iframe.src = 'javascript:0'; this.iframe.style.display = 'none'; this.iframe.tabIndex = -1; var body = document.body; // Using `appendChild` will throw on IE < 9 if the document is not ready. var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; iWindow.document.open(); iWindow.document.close(); iWindow.location.hash = '#' + this.fragment; } // Add a cross-platform `addEventListener` shim for older browsers. var addEventListener = window.addEventListener || function(eventName, listener) { return attachEvent('on' + eventName, listener); }; // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. if (this._usePushState) { addEventListener('popstate', this.checkUrl, false); } else if (this._useHashChange && !this.iframe) { addEventListener('hashchange', this.checkUrl, false); } else if (this._wantsHashChange) { this._checkUrlInterval = setInterval(this.checkUrl, this.interval); } if (!this.options.silent) return this.loadUrl(); }, // Disable Backbone.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. stop: function() { // Add a cross-platform `removeEventListener` shim for older browsers. var removeEventListener = window.removeEventListener || function(eventName, listener) { return detachEvent('on' + eventName, listener); }; // Remove window listeners. if (this._usePushState) { removeEventListener('popstate', this.checkUrl, false); } else if (this._useHashChange && !this.iframe) { removeEventListener('hashchange', this.checkUrl, false); } // Clean up the iframe if necessary. if (this.iframe) { document.body.removeChild(this.iframe); this.iframe = null; } // Some environments will throw when clearing an undefined interval. if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); History.started = false; }, // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. route: function(route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. checkUrl: function(e) { var current = this.getFragment(); // If the user pressed the back button, the iframe's hash will have // changed and we should use that for comparison. if (current === this.fragment && this.iframe) { current = this.getHash(this.iframe.contentWindow); } if (current === this.fragment) return false; if (this.iframe) this.navigate(current); this.loadUrl(); }, // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. loadUrl: function(fragment) { // If the root doesn't match, no routes can match either. if (!this.matchRoot()) return false; fragment = this.fragment = this.getFragment(fragment); return _.some(this.handlers, function(handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; } }); }, // Save a fragment into the hash history, or replace the URL state if the // 'replace' option is passed. You are responsible for properly URL-encoding // the fragment in advance. // // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. navigate: function(fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; // Normalize the fragment. fragment = this.getFragment(fragment || ''); // Don't include a trailing slash on the root. var rootPath = this.root; if (fragment === '' || fragment.charAt(0) === '?') { rootPath = rootPath.slice(0, -1) || '/'; } var url = rootPath + fragment; // Strip the hash and decode for matching. fragment = this.decodeFragment(fragment.replace(pathStripper, '')); if (this.fragment === fragment) return; this.fragment = fragment; // If pushState is available, we use it to set the fragment as a real URL. if (this._usePushState) { this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. } else if (this._wantsHashChange) { this._updateHash(this.location, fragment, options.replace); if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { var iWindow = this.iframe.contentWindow; // Opening and closing the iframe tricks IE7 and earlier to push a // history entry on hash-tag change. When replace is true, we don't // want this. if (!options.replace) { iWindow.document.open(); iWindow.document.close(); } this._updateHash(iWindow.location, fragment, options.replace); } // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. } else { return this.location.assign(url); } if (options.trigger) return this.loadUrl(fragment); }, // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. _updateHash: function(location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); } else { // Some browsers require that `hash` contains a leading #. location.hash = '#' + fragment; } } }); // Create the default Backbone.history. Backbone.history = new History; // Helpers // ------- // Helper function to correctly set up the prototype chain for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var extend = function(protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function and add the prototype properties. child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; }; // Set up inheritance for the model, collection, router, view and history. Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. var urlError = function() { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. var wrapError = function(model, options) { var error = options.error; options.error = function(resp) { if (error) error.call(options.context, model, resp, options); model.trigger('error', model, resp, options); }; }; return Backbone; }); /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js"))) /***/ }), /***/ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js": /*!*******************************************************************!*\ !*** ./node_modules/bootstrap.native/dist/bootstrap-native-v4.js ***! \*******************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Native Javascript for Bootstrap 4 v2.0.22 | © dnp_theme | MIT-License (function (root, factory) { if (true) { // AMD support: !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else { var bsn; } }(this, function () { /* Native Javascript for Bootstrap 4 | Internal Utility Functions ----------------------------------------------------------------*/ "use strict"; // globals var globalObject = typeof global !== 'undefined' ? global : this||window, DOC = document, HTML = DOC.documentElement, body = 'body', // allow the library to be used in // Native Javascript for Bootstrap Global Object BSN = globalObject.BSN = {}, supports = BSN.supports = [], // function toggle attributes dataToggle = 'data-toggle', dataDismiss = 'data-dismiss', dataSpy = 'data-spy', dataRide = 'data-ride', // components stringAlert = 'Alert', stringButton = 'Button', stringCarousel = 'Carousel', stringCollapse = 'Collapse', stringDropdown = 'Dropdown', stringModal = 'Modal', stringPopover = 'Popover', stringScrollSpy = 'ScrollSpy', stringTab = 'Tab', stringTooltip = 'Tooltip', // options DATA API databackdrop = 'data-backdrop', dataKeyboard = 'data-keyboard', dataTarget = 'data-target', dataInterval = 'data-interval', dataHeight = 'data-height', dataPause = 'data-pause', dataTitle = 'data-title', dataOriginalTitle = 'data-original-title', dataOriginalText = 'data-original-text', dataDismissible = 'data-dismissible', dataTrigger = 'data-trigger', dataAnimation = 'data-animation', dataContainer = 'data-container', dataPlacement = 'data-placement', dataDelay = 'data-delay', dataOffsetTop = 'data-offset-top', dataOffsetBottom = 'data-offset-bottom', // option keys backdrop = 'backdrop', keyboard = 'keyboard', delay = 'delay', content = 'content', target = 'target', interval = 'interval', pause = 'pause', animation = 'animation', placement = 'placement', container = 'container', // box model offsetTop = 'offsetTop', offsetBottom = 'offsetBottom', offsetLeft = 'offsetLeft', scrollTop = 'scrollTop', scrollLeft = 'scrollLeft', clientWidth = 'clientWidth', clientHeight = 'clientHeight', offsetWidth = 'offsetWidth', offsetHeight = 'offsetHeight', innerWidth = 'innerWidth', innerHeight = 'innerHeight', scrollHeight = 'scrollHeight', height = 'height', // aria ariaExpanded = 'aria-expanded', ariaHidden = 'aria-hidden', // event names clickEvent = 'click', hoverEvent = 'hover', keydownEvent = 'keydown', keyupEvent = 'keyup', resizeEvent = 'resize', scrollEvent = 'scroll', // originalEvents showEvent = 'show', shownEvent = 'shown', hideEvent = 'hide', hiddenEvent = 'hidden', closeEvent = 'close', closedEvent = 'closed', slidEvent = 'slid', slideEvent = 'slide', changeEvent = 'change', // other getAttribute = 'getAttribute', setAttribute = 'setAttribute', hasAttribute = 'hasAttribute', createElement = 'createElement', appendChild = 'appendChild', innerHTML = 'innerHTML', getElementsByTagName = 'getElementsByTagName', preventDefault = 'preventDefault', getBoundingClientRect = 'getBoundingClientRect', querySelectorAll = 'querySelectorAll', getElementsByCLASSNAME = 'getElementsByClassName', indexOf = 'indexOf', parentNode = 'parentNode', length = 'length', toLowerCase = 'toLowerCase', Transition = 'Transition', Webkit = 'Webkit', style = 'style', push = 'push', tabindex = 'tabindex', contains = 'contains', active = 'active', showClass = 'show', collapsing = 'collapsing', disabled = 'disabled', loading = 'loading', left = 'left', right = 'right', top = 'top', bottom = 'bottom', // tooltip / popover mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ], tipPositions = /\b(top|bottom|left|right)+/, // modal modalOverlay = 0, fixedTop = 'fixed-top', fixedBottom = 'fixed-bottom', // transitionEnd since 2.0.4 supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style], transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end', // set new focus element since 2.0.3 setFocus = function(element){ element.focus ? element.focus() : element.setActive(); }, // class manipulation, since 2.0.0 requires polyfill.js addClass = function(element,classNAME) { element.classList.add(classNAME); }, removeClass = function(element,classNAME) { element.classList.remove(classNAME); }, hasClass = function(element,classNAME){ // since 2.0.0 return element.classList[contains](classNAME); }, // selection methods getElementsByClassName = function(element,classNAME) { // returns Array return [].slice.call(element[getElementsByCLASSNAME]( classNAME )); }, queryElement = function (selector, parent) { var lookUp = parent ? parent : DOC; return typeof selector === 'object' ? selector : lookUp.querySelector(selector); }, getClosest = function (element, selector) { //element is the element and selector is for the closest parent element to find // source http://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/ var firstChar = selector.charAt(0), selectorSubstring = selector.substr(1); if ( firstChar === '.' ) {// If selector is a class for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match if ( queryElement(selector,element[parentNode]) !== null && hasClass(element,selectorSubstring) ) { return element; } } } else if ( firstChar === '#' ) { // If selector is an ID for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match if ( element.id === selectorSubstring ) { return element; } } } return false; }, // event attach jQuery style / trigger since 1.2.0 on = function (element, event, handler) { element.addEventListener(event, handler, false); }, off = function(element, event, handler) { element.removeEventListener(event, handler, false); }, one = function (element, event, handler) { // one since 2.0.4 on(element, event, function handlerWrapper(e){ handler(e); off(element, event, handlerWrapper); }); }, emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4 if (supportTransitions) { one(element, transitionEndEvent, function(e){ handler(e); }); } else { handler(); } }, bootstrapCustomEvent = function (eventName, componentName, related) { var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName); OriginalCustomEvent.relatedTarget = related; this.dispatchEvent(OriginalCustomEvent); }, // tooltip / popover stuff getScroll = function() { // also Affix and ScrollSpy uses it return { y : globalObject.pageYOffset || HTML[scrollTop], x : globalObject.pageXOffset || HTML[scrollLeft] } }, styleTip = function(link,element,position,parent) { // both popovers and tooltips (target,tooltip,placement,elementToAppendTo) var elementDimensions = { w : element[offsetWidth], h: element[offsetHeight] }, windowWidth = (HTML[clientWidth] || DOC[body][clientWidth]), windowHeight = (HTML[clientHeight] || DOC[body][clientHeight]), rect = link[getBoundingClientRect](), scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] }, linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] }, isPopover = hasClass(element,'popover'), topPosition, leftPosition, arrow = queryElement('.arrow',element), arrowTop, arrowLeft, arrowWidth, arrowHeight, halfTopExceed = rect[top] + linkDimensions.h/2 - elementDimensions.h/2 < 0, halfLeftExceed = rect[left] + linkDimensions.w/2 - elementDimensions.w/2 < 0, halfRightExceed = rect[left] + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth, halfBottomExceed = rect[top] + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight, topExceed = rect[top] - elementDimensions.h < 0, leftExceed = rect[left] - elementDimensions.w < 0, bottomExceed = rect[top] + elementDimensions.h + linkDimensions.h >= windowHeight, rightExceed = rect[left] + elementDimensions.w + linkDimensions.w >= windowWidth; // recompute position position = (position === left || position === right) && leftExceed && rightExceed ? top : position; // first, when both left and right limits are exceeded, we fall back to top|bottom position = position === top && topExceed ? bottom : position; position = position === bottom && bottomExceed ? top : position; position = position === left && leftExceed ? right : position; position = position === right && rightExceed ? left : position; // update tooltip/popover class element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position)); // we check the computed width & height and update here arrowWidth = arrow[offsetWidth]; arrowHeight = arrow[offsetHeight]; // apply styling to tooltip or popover if ( position === left || position === right ) { // secondary|side positions if ( position === left ) { // LEFT leftPosition = rect[left] + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 ); } else { // RIGHT leftPosition = rect[left] + scroll.x + linkDimensions.w; } // adjust top and arrow if (halfTopExceed) { topPosition = rect[top] + scroll.y; arrowTop = linkDimensions.h/2 - arrowWidth; } else if (halfBottomExceed) { topPosition = rect[top] + scroll.y - elementDimensions.h + linkDimensions.h; arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth; } else { topPosition = rect[top] + scroll.y - elementDimensions.h/2 + linkDimensions.h/2; arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2); } } else if ( position === top || position === bottom ) { // primary|vertical positions if ( position === top) { // TOP topPosition = rect[top] + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 ); } else { // BOTTOM topPosition = rect[top] + scroll.y + linkDimensions.h; } // adjust left | right and also the arrow if (halfLeftExceed) { leftPosition = 0; arrowLeft = rect[left] + linkDimensions.w/2 - arrowWidth; } else if (halfRightExceed) { leftPosition = windowWidth - elementDimensions.w*1.01; arrowLeft = elementDimensions.w - ( windowWidth - rect[left] ) + linkDimensions.w/2 - arrowWidth/2; } else { leftPosition = rect[left] + scroll.x - elementDimensions.w/2 + linkDimensions.w/2; arrowLeft = elementDimensions.w/2 - arrowWidth/2; } } // apply style to tooltip/popover and its arrow element[style][top] = topPosition + 'px'; element[style][left] = leftPosition + 'px'; arrowTop && (arrow[style][top] = arrowTop + 'px'); arrowLeft && (arrow[style][left] = arrowLeft + 'px'); }; BSN.version = '2.0.22'; /* Native Javascript for Bootstrap 4 | Alert -------------------------------------------*/ // ALERT DEFINITION // ================ var Alert = function( element ) { // initialization element element = queryElement(element); // bind, target alert, duration and stuff var self = this, component = 'alert', alert = getClosest(element,'.'+component), triggerHandler = function(){ hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); }, // handlers clickHandler = function(e){ alert = getClosest(e[target],'.'+component); element = queryElement('['+dataDismiss+'="'+component+'"]',alert); element && alert && (element === e[target] || element[contains](e[target])) && self.close(); }, transitionEndHandler = function(){ bootstrapCustomEvent.call(alert, closedEvent, component); off(element, clickEvent, clickHandler); // detach it's listener alert[parentNode].removeChild(alert); }; // public method this.close = function() { if ( alert && element && hasClass(alert,showClass) ) { bootstrapCustomEvent.call(alert, closeEvent, component); removeClass(alert,showClass); alert && triggerHandler(); } }; // init if ( !(stringAlert in element ) ) { // prevent adding event handlers twice on(element, clickEvent, clickHandler); } element[stringAlert] = self; }; // ALERT DATA API // ============== supports[push]([stringAlert, Alert, '['+dataDismiss+'="alert"]']); /* Native Javascript for Bootstrap 4 | Button ---------------------------------------------*/ // BUTTON DEFINITION // =================== var Button = function( element ) { // initialization element element = queryElement(element); // constant var toggled = false, // toggled makes sure to prevent triggering twice the change.bs.button events // strings component = 'button', checked = 'checked', reset = 'reset', LABEL = 'LABEL', INPUT = 'INPUT', // private methods keyHandler = function(e){ var key = e.which || e.keyCode; key === 32 && e[target] === DOC.activeElement && toggle(e); }, preventScroll = function(e){ var key = e.which || e.keyCode; key === 32 && e[preventDefault](); }, toggle = function(e) { var label = e[target].tagName === LABEL ? e[target] : e[target][parentNode].tagName === LABEL ? e[target][parentNode] : null; // the .btn label if ( !label ) return; //react if a label or its immediate child is clicked var eventTarget = e[target], // the button itself, the target of the handler function labels = getElementsByClassName(eventTarget[parentNode],'btn'), // all the button group buttons input = label[getElementsByTagName](INPUT)[0]; if ( !input ) return; //return if no input found // manage the dom manipulation if ( input.type === 'checkbox' ) { //checkboxes if ( !input[checked] ) { addClass(label,active); input[getAttribute](checked); input[setAttribute](checked,checked); input[checked] = true; } else { removeClass(label,active); input[getAttribute](checked); input.removeAttribute(checked); input[checked] = false; } if (!toggled) { // prevent triggering the event twice toggled = true; bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group } } if ( input.type === 'radio' && !toggled ) { // radio buttons if ( !input[checked] ) { // don't trigger if already active addClass(label,active); input[setAttribute](checked,checked); input[checked] = true; bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group toggled = true; for (var i = 0, ll = labels[length]; i= 0; // bottom && top }, setActivePage = function( pageIndex ) { //indicators for ( var i = 0, icl = indicators[length]; i < icl; i++ ) { removeClass(indicators[i],active); } if (indicators[pageIndex]) addClass(indicators[pageIndex], active); }; // public methods this.cycle = function() { timer = setInterval(function() { isElementInScrollRange() && (index++, self.slideTo( index ) ); }, this[interval]); }; this.slideTo = function( next ) { if (isSliding) return; // when controled via methods, make sure to check again var activeItem = this.getActiveIndex(), // the current active orientation; // determine slideDirection first if ( (activeItem < next ) || (activeItem === 0 && next === total -1 ) ) { slideDirection = self[direction] = left; // next } else if ( (activeItem > next) || (activeItem === total - 1 && next === 0 ) ) { slideDirection = self[direction] = right; // prev } // find the right next index if ( next < 0 ) { next = total - 1; } else if ( next === total ){ next = 0; } // update index index = next; orientation = slideDirection === left ? 'next' : 'prev'; //determine type bootstrapCustomEvent.call(element, slideEvent, component, slides[next]); // here we go with the slide isSliding = true; clearInterval(timer); setActivePage( next ); if ( supportTransitions && hasClass(element,'slide') ) { addClass(slides[next],carouselItem +'-'+ orientation); slides[next][offsetWidth]; addClass(slides[next],carouselItem +'-'+ slideDirection); addClass(slides[activeItem],carouselItem +'-'+ slideDirection); one(slides[activeItem], transitionEndEvent, function(e) { var timeout = e[target] !== slides[activeItem] ? e.elapsedTime*1000 : 0; setTimeout(function(){ isSliding = false; addClass(slides[next],active); removeClass(slides[activeItem],active); removeClass(slides[next],carouselItem +'-'+ orientation); removeClass(slides[next],carouselItem +'-'+ slideDirection); removeClass(slides[activeItem],carouselItem +'-'+ slideDirection); bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) { self.cycle(); } },timeout+100); }); } else { addClass(slides[next],active); slides[next][offsetWidth]; removeClass(slides[activeItem],active); setTimeout(function() { isSliding = false; if ( self[interval] && !hasClass(element,paused) ) { self.cycle(); } bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); }, 100 ); } }; this.getActiveIndex = function () { return slides[indexOf](getElementsByClassName(element,carouselItem+' active')[0]) || 0; }; // init if ( !(stringCarousel in element ) ) { // prevent adding event handlers twice if ( self[pause] && self[interval] ) { on( element, mouseHover[0], pauseHandler ); on( element, mouseHover[1], resumeHandler ); on( element, 'touchstart', pauseHandler ); on( element, 'touchend', resumeHandler ); } rightArrow && on( rightArrow, clickEvent, controlsHandler ); leftArrow && on( leftArrow, clickEvent, controlsHandler ); indicator && on( indicator, clickEvent, indicatorHandler ); self[keyboard] === true && on( globalObject, keydownEvent, keyHandler ); } if (self.getActiveIndex()<0) { slides[length] && addClass(slides[0],active); indicators[length] && setActivePage(0); } if ( self[interval] ){ self.cycle(); } element[stringCarousel] = self; }; // CAROUSEL DATA API // ================= supports[push]( [ stringCarousel, Carousel, '['+dataRide+'="carousel"]' ] ); /* Native Javascript for Bootstrap 4 | Collapse -----------------------------------------------*/ // COLLAPSE DEFINITION // =================== var Collapse = function( element, options ) { // initialization element element = queryElement(element); // set options options = options || {}; // event targets and constants var accordion = null, collapse = null, self = this, isAnimating = false, // when true it will prevent click handlers accordionData = element[getAttribute]('data-parent'), // component strings component = 'collapse', collapsed = 'collapsed', // private methods openAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, showEvent, component); isAnimating = true; addClass(collapseElement,collapsing); removeClass(collapseElement,component); collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; emulateTransitionEnd(collapseElement, function() { isAnimating = false; collapseElement[setAttribute](ariaExpanded,'true'); toggle[setAttribute](ariaExpanded,'true'); removeClass(collapseElement,collapsing); addClass(collapseElement, component); addClass(collapseElement,showClass); collapseElement[style][height] = ''; bootstrapCustomEvent.call(collapseElement, shownEvent, component); }); }, closeAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, hideEvent, component); isAnimating = true; collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first removeClass(collapseElement,component); removeClass(collapseElement,showClass); addClass(collapseElement,collapsing); collapseElement[offsetWidth]; // force reflow to enable transition collapseElement[style][height] = '0px'; emulateTransitionEnd(collapseElement, function() { isAnimating = false; collapseElement[setAttribute](ariaExpanded,'false'); toggle[setAttribute](ariaExpanded,'false'); removeClass(collapseElement,collapsing); addClass(collapseElement,component); collapseElement[style][height] = ''; bootstrapCustomEvent.call(collapseElement, hiddenEvent, component); }); }, getTarget = function() { var href = element.href && element[getAttribute]('href'), parent = element[getAttribute](dataTarget), id = href || ( parent && parent.charAt(0) === '#' ) && parent; return id && queryElement(id); }; // public methods this.toggle = function(e) { e[preventDefault](); if (isAnimating) return; if (!hasClass(collapse,showClass)) { self.show(); } else { self.hide(); } }; this.hide = function() { closeAction(collapse,element); addClass(element,collapsed); }; this.show = function() { if ( accordion ) { var activeCollapse = queryElement('.'+component+'.'+showClass,accordion), toggle = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ), correspondingCollapse = toggle && (toggle[getAttribute](dataTarget) || toggle.href); if ( activeCollapse && toggle && activeCollapse !== collapse ) { closeAction(activeCollapse,toggle); if ( correspondingCollapse.split('#')[1] !== collapse.id ) { addClass(toggle,collapsed); } else { removeClass(toggle,collapsed); } } } openAction(collapse,element); removeClass(element,collapsed); }; // init if ( !(stringCollapse in element ) ) { // prevent adding event handlers twice on(element, clickEvent, self.toggle); } collapse = getTarget(); accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData); element[stringCollapse] = self; }; // COLLAPSE DATA API // ================= supports[push]( [ stringCollapse, Collapse, '['+dataToggle+'="collapse"]' ] ); /* Native Javascript for Bootstrap 4 | Dropdown ----------------------------------------------*/ // DROPDOWN DEFINITION // =================== var Dropdown = function( element, option ) { // initialization element element = queryElement(element); // set option this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false; // constants, event targets, strings var self = this, children = 'children', parent = element[parentNode], component = 'dropdown', open = 'open', relatedTarget = null, menu = queryElement('.dropdown-menu', parent), menuItems = (function(){ var set = menu[children], newSet = []; for ( var i=0; i1?idx-1:0) : key === 40 ? (idx HTML[clientHeight]; scrollbarWidth = measureScrollbar(); }, adjustDialog = function () { modal[style][paddingLeft] = !bodyIsOverflowing && modalIsOverflowing ? scrollbarWidth + 'px' : ''; modal[style][paddingRight] = bodyIsOverflowing && !modalIsOverflowing ? scrollbarWidth + 'px' : ''; }, resetAdjustments = function () { modal[style][paddingLeft] = ''; modal[style][paddingRight] = ''; }, createOverlay = function() { modalOverlay = 1; var newOverlay = DOC[createElement]('div'); overlay = queryElement('.'+modalBackdropString); if ( overlay === null ) { newOverlay[setAttribute]('class',modalBackdropString+' fade'); overlay = newOverlay; DOC[body][appendChild](overlay); } }, removeOverlay = function() { overlay = queryElement('.'+modalBackdropString); if ( overlay && overlay !== null && typeof overlay === 'object' ) { modalOverlay = 0; DOC[body].removeChild(overlay); overlay = null; } bootstrapCustomEvent.call(modal, hiddenEvent, component); }, keydownHandlerToggle = function() { if (hasClass(modal,showClass)) { on(DOC, keydownEvent, keyHandler); } else { off(DOC, keydownEvent, keyHandler); } }, resizeHandlerToggle = function() { if (hasClass(modal,showClass)) { on(globalObject, resizeEvent, self.update); } else { off(globalObject, resizeEvent, self.update); } }, dismissHandlerToggle = function() { if (hasClass(modal,showClass)) { on(modal, clickEvent, dismissHandler); } else { off(modal, clickEvent, dismissHandler); } }, // triggers triggerShow = function() { setFocus(modal); bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget); }, triggerHide = function() { modal[style].display = ''; element && (setFocus(element)); (function(){ if (!getElementsByClassName(DOC,component+' '+showClass)[0]) { resetAdjustments(); resetScrollbar(); removeClass(DOC[body],component+'-open'); overlay && hasClass(overlay,'fade') ? (removeClass(overlay,showClass), emulateTransitionEnd(overlay,removeOverlay)) : removeOverlay(); resizeHandlerToggle(); dismissHandlerToggle(); keydownHandlerToggle(); } }()); }, // handlers clickHandler = function(e) { var clickTarget = e[target]; clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode]; if ( clickTarget === element && !hasClass(modal,showClass) ) { modal.modalTrigger = element; relatedTarget = element; self.show(); e[preventDefault](); } }, keyHandler = function(e) { if (self[keyboard] && e.which == 27 && hasClass(modal,showClass)) { self.hide(); } }, dismissHandler = function(e) { var clickTarget = e[target]; if ( hasClass(modal,showClass) && (clickTarget[parentNode][getAttribute](dataDismiss) === component || clickTarget[getAttribute](dataDismiss) === component || (clickTarget === modal && self[backdrop] !== staticString) ) ) { self.hide(); relatedTarget = null; e[preventDefault](); } }; // public methods this.toggle = function() { if ( hasClass(modal,showClass) ) {this.hide();} else {this.show();} }; this.show = function() { bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget); // we elegantly hide any opened modal var currentOpen = getElementsByClassName(DOC,component+' '+showClass)[0]; currentOpen && currentOpen !== modal && currentOpen.modalTrigger[stringModal].hide(); if ( this[backdrop] ) { !modalOverlay && createOverlay(); } if ( overlay && modalOverlay && !hasClass(overlay,showClass)) { overlay[offsetWidth]; // force reflow to enable trasition addClass(overlay, showClass); } setTimeout( function() { modal[style].display = 'block'; checkScrollbar(); setScrollbar(); adjustDialog(); addClass(DOC[body],component+'-open'); addClass(modal,showClass); modal[setAttribute](ariaHidden, false); resizeHandlerToggle(); dismissHandlerToggle(); keydownHandlerToggle(); hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); }, supportTransitions ? 150 : 0); }; this.hide = function() { bootstrapCustomEvent.call(modal, hideEvent, component); overlay = queryElement('.'+modalBackdropString); removeClass(modal,showClass); modal[setAttribute](ariaHidden, true); (function(){ hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); }()); }; this.setContent = function( content ) { queryElement('.'+component+'-content',modal)[innerHTML] = content; }; this.update = function() { if (hasClass(modal,showClass)) { checkScrollbar(); setScrollbar(); adjustDialog(); } }; // init // prevent adding event handlers over and over // modal is independent of a triggering element if ( !!element && !(stringModal in element) ) { on(element, clickEvent, clickHandler); } if ( !!self[content] ) { self.setContent( self[content] ); } !!element && (element[stringModal] = self); }; // DATA API supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] ); /* Native Javascript for Bootstrap 4 | Popover ----------------------------------------------*/ // POPOVER DEFINITION // ================== var Popover = function( element, options ) { // initialization element element = queryElement(element); // set options options = options || {}; // DATA API var triggerData = element[getAttribute](dataTrigger), // click / hover / focus animationData = element[getAttribute](dataAnimation), // true / false placementData = element[getAttribute](dataPlacement), dismissibleData = element[getAttribute](dataDismissible), delayData = element[getAttribute](dataDelay), containerData = element[getAttribute](dataContainer), // internal strings component = 'popover', template = 'template', trigger = 'trigger', classString = 'class', div = 'div', fade = 'fade', content = 'content', dataContent = 'data-content', dismissible = 'dismissible', closeBtn = '', // check container containerElement = queryElement(options[container]), containerDataElement = queryElement(containerData), // maybe the element is inside a modal modal = getClosest(element,'.modal'), // maybe the element is inside a fixed navbar navbarFixedTop = getClosest(element,'.'+fixedTop), navbarFixedBottom = getClosest(element,'.'+fixedBottom); // set instance options this[template] = options[template] ? options[template] : null; // JavaScript only this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent; this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; this[placement] = options[placement] ? options[placement] : placementData || top; this[delay] = parseInt(options[delay] || delayData) || 200; this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false; this[container] = containerElement ? containerElement : containerDataElement ? containerDataElement : navbarFixedTop ? navbarFixedTop : navbarFixedBottom ? navbarFixedBottom : modal ? modal : DOC[body]; // bind, content var self = this, titleString = element[getAttribute](dataTitle) || null, contentString = element[getAttribute](dataContent) || null; if ( !contentString && !this[template] ) return; // invalidate // constants, vars var popover = null, timer = 0, placementSetting = this[placement], // handlers dismissibleHandler = function(e) { if (popover !== null && e[target] === queryElement('.close',popover)) { self.hide(); } }, // private methods removePopover = function() { self[container].removeChild(popover); timer = null; popover = null; }, createPopover = function() { titleString = element[getAttribute](dataTitle); // check content again contentString = element[getAttribute](dataContent); popover = DOC[createElement](div); // popover arrow var popoverArrow = DOC[createElement](div); popoverArrow[setAttribute](classString,'arrow'); popover[appendChild](popoverArrow); if ( contentString !== null && self[template] === null ) { //create the popover from data attributes popover[setAttribute]('role','tooltip'); if (titleString !== null) { var popoverTitle = DOC[createElement]('h3'); popoverTitle[setAttribute](classString,component+'-header'); popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString; popover[appendChild](popoverTitle); } //set popover content var popoverContent = DOC[createElement](div); popoverContent[setAttribute](classString,component+'-body'); popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString; popover[appendChild](popoverContent); } else { // or create the popover from template var popoverTemplate = DOC[createElement](div); popoverTemplate[innerHTML] = self[template]; popover[innerHTML] = popoverTemplate.firstChild[innerHTML]; } //append to the container self[container][appendChild](popover); popover[style].display = 'block'; popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); }, showPopover = function () { !hasClass(popover,showClass) && ( addClass(popover,showClass) ); }, updatePopover = function() { styleTip(element,popover,placementSetting,self[container]); }, // event toggle dismissHandlerToggle = function(type){ if (clickEvent == self[trigger] || 'focus' == self[trigger]) { !self[dismissible] && type( element, 'blur', self.hide ); } self[dismissible] && type( DOC, clickEvent, dismissibleHandler ); type( globalObject, resizeEvent, self.hide ); }, // triggers showTrigger = function() { dismissHandlerToggle(on); bootstrapCustomEvent.call(element, shownEvent, component); }, hideTrigger = function() { dismissHandlerToggle(off); removePopover(); bootstrapCustomEvent.call(element, hiddenEvent, component); }; // public methods / handlers this.toggle = function() { if (popover === null) { self.show(); } else { self.hide(); } }; this.show = function() { clearTimeout(timer); timer = setTimeout( function() { if (popover === null) { placementSetting = self[placement]; // we reset placement in all cases createPopover(); updatePopover(); showPopover(); bootstrapCustomEvent.call(element, showEvent, component); !!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger(); } }, 20 ); }; this.hide = function() { clearTimeout(timer); timer = setTimeout( function() { if (popover && popover !== null && hasClass(popover,showClass)) { bootstrapCustomEvent.call(element, hideEvent, component); removeClass(popover,showClass); !!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger(); } }, self[delay] ); }; // init if ( !(stringPopover in element) ) { // prevent adding event handlers twice if (self[trigger] === hoverEvent) { on( element, mouseHover[0], self.show ); if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); } } else if (clickEvent == self[trigger] || 'focus' == self[trigger]) { on( element, self[trigger], self.toggle ); } } element[stringPopover] = self; }; // POPOVER DATA API // ================ supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] ); /* Native Javascript for Bootstrap 4 | ScrollSpy -----------------------------------------------*/ // SCROLLSPY DEFINITION // ==================== var ScrollSpy = function(element, options) { // initialization element, the element we spy on element = queryElement(element); // DATA API var targetData = queryElement(element[getAttribute](dataTarget)), offsetData = element[getAttribute]('data-offset'); // set options options = options || {}; if ( !options[target] && !targetData ) { return; } // invalidate // event targets, constants var self = this, spyTarget = options[target] && queryElement(options[target]) || targetData, links = spyTarget && spyTarget[getElementsByTagName]('A'), offset = parseInt(offsetData || options['offset']) || 10, items = [], targetItems = [], scrollOffset, scrollTarget = element[offsetHeight] < element[scrollHeight] ? element : globalObject, // determine which is the real scrollTarget isWindow = scrollTarget === globalObject; // populate items and targets for (var i=0, il=links[length]; i= topEdge && bottomEdge > scrollOffset; if ( !isActive && inside ) { if ( !hasClass(item,active) ) { addClass(item,active); if (dropdownLink && !hasClass(dropdownLink,active) ) { addClass(dropdownLink,active); } bootstrapCustomEvent.call(element, 'activate', 'scrollspy', items[index]); } } else if ( !inside ) { if ( hasClass(item,active) ) { removeClass(item,active); if (dropdownLink && hasClass(dropdownLink,active) && !getElementsByClassName(item[parentNode],active).length ) { removeClass(dropdownLink,active); } } } else if ( !inside && !isActive || isActive && inside ) { return; } }, updateItems = function(){ scrollOffset = isWindow ? getScroll().y : element[scrollTop]; for (var index=0, itl=items[length]; index 1 ) { activeTab = activeTabs[activeTabs[length]-1]; } return activeTab; }, getActiveContent = function() { return queryElement(getActiveTab()[getAttribute]('href')); }, // handler clickHandler = function(e) { var href = e[target][getAttribute]('href'); e[preventDefault](); next = e[target][getAttribute](dataToggle) === component || (href && href.charAt(0) === '#') ? e[target] : e[target][parentNode]; // allow for child elements like icons to use the handler !tabs[isAnimating] && !hasClass(next,active) && self.show(); }; // public method this.show = function() { // the tab we clicked is now the next tab next = next || element; nextContent = queryElement(next[getAttribute]('href')); //this is the actual object, the next tab content to activate activeTab = getActiveTab(); activeContent = getActiveContent(); tabs[isAnimating] = true; removeClass(activeTab,active); addClass(next,active); if ( dropdown ) { if ( !hasClass(element[parentNode],'dropdown-menu') ) { if (hasClass(dropdown,active)) removeClass(dropdown,active); } else { if (!hasClass(dropdown,active)) addClass(dropdown,active); } } bootstrapCustomEvent.call(activeTab, hideEvent, component, next); if (hasClass(activeContent, 'fade')) { removeClass(activeContent,showClass); emulateTransitionEnd(activeContent, triggerHide); } else { triggerHide(); } }; // init if ( !(stringTab in element) ) { // prevent adding event handlers twice on(element, clickEvent, clickHandler); } if (self[height]) { tabsContentContainer = getActiveContent()[parentNode]; } element[stringTab] = self; }; // TAB DATA API // ============ supports[push]( [ stringTab, Tab, '['+dataToggle+'="tab"]' ] ); /* Native Javascript for Bootstrap 4 | Tooltip ---------------------------------------------*/ // TOOLTIP DEFINITION // ================== var Tooltip = function( element,options ) { // initialization element element = queryElement(element); // set options options = options || {}; // DATA API var animationData = element[getAttribute](dataAnimation), placementData = element[getAttribute](dataPlacement), delayData = element[getAttribute](dataDelay), containerData = element[getAttribute](dataContainer), // strings component = 'tooltip', classString = 'class', title = 'title', fade = 'fade', div = 'div', // check container containerElement = queryElement(options[container]), containerDataElement = queryElement(containerData), // maybe the element is inside a modal modal = getClosest(element,'.modal'), // maybe the element is inside a fixed navbar navbarFixedTop = getClosest(element,'.'+fixedTop), navbarFixedBottom = getClosest(element,'.'+fixedBottom); // set instance options this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; this[placement] = options[placement] ? options[placement] : placementData || top; this[delay] = parseInt(options[delay] || delayData) || 200; this[container] = containerElement ? containerElement : containerDataElement ? containerDataElement : navbarFixedTop ? navbarFixedTop : navbarFixedBottom ? navbarFixedBottom : modal ? modal : DOC[body]; // bind, event targets, title and constants var self = this, timer = 0, placementSetting = this[placement], tooltip = null, titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); if ( !titleString || titleString == "" ) return; // invalidate // private methods var removeToolTip = function() { self[container].removeChild(tooltip); tooltip = null; timer = null; }, createToolTip = function() { titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); // read the title again if ( !titleString || titleString == "" ) return false; // invalidate tooltip = DOC[createElement](div); tooltip[setAttribute]('role',component); // tooltip arrow var tooltipArrow = DOC[createElement](div); tooltipArrow[setAttribute](classString,'arrow'); tooltip[appendChild](tooltipArrow); var tooltipInner = DOC[createElement](div); tooltipInner[setAttribute](classString,component+'-inner'); tooltip[appendChild](tooltipInner); tooltipInner[innerHTML] = titleString; self[container][appendChild](tooltip); tooltip[setAttribute](classString, component + ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); }, updateTooltip = function () { styleTip(element,tooltip,placementSetting,self[container]); }, showTooltip = function () { !hasClass(tooltip,showClass) && ( addClass(tooltip,showClass) ); }, // triggers showTrigger = function() { on( globalObject, resizeEvent, self.hide ); bootstrapCustomEvent.call(element, shownEvent, component); }, hideTrigger = function() { off( globalObject, resizeEvent, self.hide ); removeToolTip(); bootstrapCustomEvent.call(element, hiddenEvent, component); }; // public methods this.show = function() { clearTimeout(timer); timer = setTimeout( function() { if (tooltip === null) { placementSetting = self[placement]; // we reset placement in all cases if(createToolTip() == false) return; updateTooltip(); showTooltip(); bootstrapCustomEvent.call(element, showEvent, component); !!self[animation] ? emulateTransitionEnd(tooltip, showTrigger) : showTrigger(); } }, 20 ); }; this.hide = function() { clearTimeout(timer); timer = setTimeout( function() { if (tooltip && hasClass(tooltip,showClass)) { bootstrapCustomEvent.call(element, hideEvent, component); removeClass(tooltip,showClass); !!self[animation] ? emulateTransitionEnd(tooltip, hideTrigger) : hideTrigger(); } }, self[delay]); }; this.toggle = function() { if (!tooltip) { self.show(); } else { self.hide(); } }; // init if ( !(stringTooltip in element) ) { // prevent adding event handlers twice element[setAttribute](dataOriginalTitle,titleString); element.removeAttribute(title); on(element, mouseHover[0], self.show); on(element, mouseHover[1], self.hide); } element[stringTooltip] = self; }; // TOOLTIP DATA API // ================= supports[push]( [ stringTooltip, Tooltip, '['+dataToggle+'="tooltip"]' ] ); /* Native Javascript for Bootstrap 4 | Initialize Data API --------------------------------------------------------*/ var initializeDataAPI = function( constructor, collection ){ for (var i=0, l=collection[length]; i:-)':'1f606', '\':-(':'1f613', '>:-(':'1f620', ':\'-(':'1f622', 'O:-)':'1f607', '0:-3':'1f607', '0:-)':'1f607', '0;^)':'1f607', 'O;-)':'1f607', '0;-)':'1f607', 'O:-3':'1f607', '-__-':'1f611', ':-Þ':'1f61b', ':)':'1f606', '>;)':'1f606', '>=)':'1f606', ';-)':'1f609', '*-)':'1f609', ';-]':'1f609', ';^)':'1f609', '\':(':'1f613', '\'=(':'1f613', ':-*':'1f618', ':^*':'1f618', '>:P':'1f61c', 'X-P':'1f61c', '>:[':'1f61e', ':-(':'1f61e', ':-[':'1f61e', '>:(':'1f620', ':\'(':'1f622', ';-(':'1f622', '>.<':'1f623', '#-)':'1f635', '%-)':'1f635', 'X-)':'1f635', '\\0/':'1f646', '\\O/':'1f646', '0:3':'1f607', '0:)':'1f607', 'O:)':'1f607', 'O=)':'1f607', 'O:3':'1f607', 'B-)':'1f60e', '8-)':'1f60e', 'B-D':'1f60e', '8-D':'1f60e', '-_-':'1f611', '>:\\':'1f615', '>:/':'1f615', ':-/':'1f615', ':-.':'1f615', ':-P':'1f61b', ':Þ':'1f61b', ':-b':'1f61b', ':-O':'1f62e', 'O_O':'1f62e', '>:O':'1f62e', ':-X':'1f636', ':-#':'1f636', ':-)':'1f642', '(y)':'1f44d', '<3':'2764', ':D':'1f603', '=D':'1f603', ';)':'1f609', '*)':'1f609', ';]':'1f609', ';D':'1f609', ':*':'1f618', '=*':'1f618', ':(':'1f61e', ':[':'1f61e', '=(':'1f61e', ':@':'1f620', ';(':'1f622', 'D:':'1f628', ':$':'1f633', '=$':'1f633', '#)':'1f635', '%)':'1f635', 'X)':'1f635', 'B)':'1f60e', '8)':'1f60e', ':/':'1f615', ':\\':'1f615', '=/':'1f615', '=\\':'1f615', ':L':'1f615', '=L':'1f615', ':P':'1f61b', '=P':'1f61b', ':b':'1f61b', ':O':'1f62e', ':X':'1f636', ':#':'1f636', '=X':'1f636', '=#':'1f636', ':)':'1f642', '=]':'1f642', '=)':'1f642', ':]':'1f642' }; ns.asciiRegexp = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\:D|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\])'; ns.emojiVersion = '3.1'; // you can [optionally] modify this to load alternate emoji versions. see readme for backwards compatibility and version options ns.emojiSize = '32'; ns.greedyMatch = false; // set to true for greedy unicode matching ns.imagePathPNG = 'https://cdn.jsdelivr.net/emojione/assets/' + ns.emojiVersion + '/png/'; ns.defaultPathPNG = ns.imagePathPNG; ns.imageTitleTag = true; // set to false to remove title attribute from img tag ns.sprites = false; // if this is true then sprite markup will be used ns.spriteSize = '32'; ns.unicodeAlt = true; // use the unicode char as the alt attribute (makes copy and pasting the resulting text better) ns.ascii = false; // change to true to convert ascii smileys ns.riskyMatchAscii = false; // set true to match ascii without leading/trailing space char ns.regShortNames = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+ns.shortnames+")", "gi"); ns.regAscii = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|((\\s|^)"+ns.asciiRegexp+"(?=\\s|$|[!,.?]))", "gi"); ns.regAsciiRisky = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(()"+ns.asciiRegexp+"())", "gi"); ns.regUnicode = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(?:\uD83C\uDFF3)\uFE0F?\u200D?(?:\uD83C\uDF08)|(?:\uD83D\uDC41)\uFE0F?\u200D?(?:\uD83D\uDDE8)\uFE0F?|[#-9]\uFE0F?\u20E3|(?:(?:\uD83C\uDFF4)(?:\uDB40[\uDC60-\uDCFF]){1,6})|(?:\uD83C[\uDDE0-\uDDFF]){2}|(?:(?:\uD83D[\uDC68\uDC69]))\uFE0F?(?:\uD83C[\uDFFA-\uDFFF])?\u200D?(?:[\u2695\u2696\u2708]|\uD83C[\uDF3E-\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83D[\uDC68\uDC69]|\uD83E[\uDDD0-\uDDDF])(?:\uD83C[\uDFFA-\uDFFF])?\u200D?[\u2640\u2642\u2695\u2696\u2708]?\uFE0F?|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])[\u200D\uFE0F]{0,2}){1,3}(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])\uFE0F?){2,4}|(?:\uD83D[\uDC68\uDC69\uDC6E\uDC71-\uDC87\uDD75\uDE45-\uDE4E]|\uD83E[\uDD26\uDD37]|\uD83C[\uDFC3-\uDFCC]|\uD83E[\uDD38-\uDD3E]|\uD83D[\uDEA3-\uDEB6]|\u26f9|\uD83D\uDC6F)\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])?\u200D?[\u2640\u2642]?\uFE0F?|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85-\uDFCC]|\uD83D[\uDC42-\uDCAA\uDD74-\uDD96\uDE45-\uDE4F\uDEA3-\uDECC]|\uD83E[\uDD18-\uDD3E])\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u2194-\u2199\u21a9-\u21aa]\uFE0F?|[\u0023\u002a]|[\u3030\u303d]\uFE0F?|(?:\ud83c[\udd70-\udd71]|\ud83c\udd8e|\ud83c[\udd91-\udd9a])\uFE0F?|\u24c2\uFE0F?|[\u3297\u3299]\uFE0F?|(?:\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51])\uFE0F?|[\u203c\u2049]\uFE0F?|[\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe]\uFE0F?|[\u00a9\u00ae]\uFE0F?|[\u2122\u2139]\uFE0F?|\ud83c\udc04\uFE0F?|[\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55]\uFE0F?|[\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa]\uFE0F?|\ud83c\udccf|[\u2934\u2935]\uFE0F?)|[\u2700-\u27bf]\uFE0F?|[\ud800-\udbff][\udc00-\udfff]\uFE0F?|[\u2600-\u26FF]\uFE0F?|[\u0030-\u0039]\uFE0F", "g"); ns.toImage = function(str) { str = ns.unicodeToImage(str); str = ns.shortnameToImage(str); return str; }; // Uses toShort to transform all unicode into a standard shortname // then transforms the shortname into unicode // This is done for standardization when converting several unicode types ns.unifyUnicode = function(str) { str = ns.toShort(str); str = ns.shortnameToUnicode(str); return str; }; // Replace shortnames (:wink:) with Ascii equivalents ( ;^) ) // Useful for systems that dont support unicode nor images ns.shortnameToAscii = function(str) { var unicode, // something to keep in mind here is that array flip will destroy // half of the ascii text "emojis" because the unicode numbers are duplicated // this is ok for what it's being used for unicodeToAscii = ns.objectFlip(ns.asciiList); str = str.replace(ns.regShortNames, function(shortname) { if( (typeof shortname === 'undefined') || (shortname === '') || (!(shortname in ns.emojioneList)) ) { // if the shortname doesnt exist just return the entire match return shortname; } else { unicode = ns.emojioneList[shortname].uc_output; if(typeof unicodeToAscii[unicode] !== 'undefined') { return unicodeToAscii[unicode]; } else { return shortname; } } }); return str; }; // will output unicode from shortname // useful for sending emojis back to mobile devices ns.shortnameToUnicode = function(str) { // replace regular shortnames first var unicode,fname; str = str.replace(ns.regShortNames, function(shortname) { if( (typeof shortname === 'undefined') || (shortname === '') || (!(shortname in ns.emojioneList)) ) { // if the shortname doesnt exist just return the entire matchhju return shortname; } unicode = ns.emojioneList[shortname].uc_output.toUpperCase(); fname = ns.emojioneList[shortname].uc_base; return ns.convert(unicode); }); // if ascii smileys are turned on, then we'll replace them! if (ns.ascii) { var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii; str = str.replace(asciiRX, function(entire, m1, m2, m3) { if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) { // if the ascii doesnt exist just return the entire match return entire; } m3 = ns.unescapeHTML(m3); unicode = ns.asciiList[m3].toUpperCase(); return m2+ns.convert(unicode); }); } return str; }; ns.shortnameToImage = function(str) { // replace regular shortnames first var replaceWith,shortname,unicode,fname,alt,category,title,size,ePath; var mappedUnicode = ns.mapUnicodeToShort(); str = str.replace(ns.regShortNames, function(shortname) { if( (typeof shortname === 'undefined') || (shortname === '') || (ns.shortnames.indexOf(shortname) === -1) ) { // if the shortname doesnt exist just return the entire match return shortname; } else { // map shortname to parent if (!ns.emojioneList[shortname]) { for ( var emoji in ns.emojioneList ) { if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue; if (ns.emojioneList[emoji].shortnames.indexOf(shortname) === -1) continue; shortname = emoji; break; } } unicode = ns.emojioneList[shortname].uc_output; fname = ns.emojioneList[shortname].uc_base; category = (fname.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category; title = ns.imageTitleTag ? 'title="' + shortname + '"' : ''; size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32'; //if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/'; // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : shortname; if(ns.sprites) { replaceWith = '' + alt + ''; } else { replaceWith = '' + alt + ''; } return replaceWith; } }); // if ascii smileys are turned on, then we'll replace them! if (ns.ascii) { var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii; str = str.replace(asciiRX, function(entire, m1, m2, m3) { if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) { // if the ascii doesnt exist just return the entire match return entire; } m3 = ns.unescapeHTML(m3); unicode = ns.asciiList[m3]; shortname = mappedUnicode[unicode]; category = (unicode.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category; title = ns.imageTitleTag ? 'title="' + ns.escapeHTML(m3) + '"' : ''; size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32'; //if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/'; // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : ns.escapeHTML(m3); if(ns.sprites) { replaceWith = m2+'' + alt + ''; } else { replaceWith = m2+''+alt+''; } return replaceWith; }); } return str; }; ns.unicodeToImage = function(str) { var replaceWith,unicode,short,fname,alt,category,title,size,ePath; var mappedUnicode = ns.mapUnicodeToShort(); var eList = ns.emojioneList; str = str.replace(ns.regUnicode, function(unicodeChar) { if( (typeof unicodeChar === 'undefined') || (unicodeChar === '') ) { return unicodeChar; } else if ( unicodeChar in ns.jsEscapeMap ) { fname = ns.jsEscapeMap[unicodeChar]; } else if ( ns.greedyMatch && unicodeChar in ns.jsEscapeMapGreedy ) { fname = ns.jsEscapeMapGreedy[unicodeChar]; } else { return unicodeChar; } // then map to shortname and locate the filename short = mappedUnicode[fname]; // then pull the unicode output from emojioneList fname = eList[short].uc_base; unicode = eList[short].uc_output; category = (fname.includes("-1f3f")) ? 'diversity' : eList[short].category; size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32'; //if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/'; // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : short; title = ns.imageTitleTag ? 'title="' + short + '"' : ''; if(ns.sprites) { replaceWith = '' + alt + ''; } else { replaceWith = '' + alt + ''; } return replaceWith; }); // if ascii smileys are turned on, then we'll replace them! if (ns.ascii) { var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii; str = str.replace(asciiRX, function(entire, m1, m2, m3) { if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) { // if the ascii doesnt exist just return the entire match return entire; } m3 = ns.unescapeHTML(m3); unicode = ns.asciiList[m3]; shortname = mappedUnicode[unicode]; category = (unicode.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category; title = ns.imageTitleTag ? 'title="' + ns.escapeHTML(m3) + '"' : ''; size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32'; //if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/'; // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : ns.escapeHTML(m3); if(ns.sprites) { replaceWith = m2+'' + alt + ''; } else { replaceWith = m2+''+alt+''; } return replaceWith; }); } return str; }; // this is really just unicodeToShortname() but I opted for the shorthand name to match toImage() ns.toShort = function(str) { var find = ns.unicodeCharRegex(); return ns.replaceAll(str, find); }; // for converting unicode code points and code pairs to their respective characters ns.convert = function(unicode) { if(unicode.indexOf("-") > -1) { var parts = []; var s = unicode.split('-'); for(var i = 0; i < s.length; i++) { var part = parseInt(s[i], 16); if (part >= 0x10000 && part <= 0x10FFFF) { var hi = Math.floor((part - 0x10000) / 0x400) + 0xD800; var lo = ((part - 0x10000) % 0x400) + 0xDC00; part = (String.fromCharCode(hi) + String.fromCharCode(lo)); } else { part = String.fromCharCode(part); } parts.push(part); } return parts.join(''); } else { var s = parseInt(unicode, 16); if (s >= 0x10000 && s <= 0x10FFFF) { var hi = Math.floor((s - 0x10000) / 0x400) + 0xD800; var lo = ((s - 0x10000) % 0x400) + 0xDC00; return (String.fromCharCode(hi) + String.fromCharCode(lo)); } else { return String.fromCharCode(s); } } }; ns.escapeHTML = function (string) { var escaped = { '&' : '&', '<' : '<', '>' : '>', '"' : '"', '\'': ''' }; return string.replace(/[&<>"']/g, function (match) { return escaped[match]; }); }; ns.unescapeHTML = function (string) { var unescaped = { '&' : '&', '&' : '&', '&' : '&', '<' : '<', '<' : '<', '<' : '<', '>' : '>', '>' : '>', '>' : '>', '"' : '"', '"' : '"', '"' : '"', ''' : '\'', ''' : '\'', ''' : '\'' }; return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/ig, function (match) { return unescaped[match]; }); }; ns.shortnameConversionMap = function() { var map = [], emoji; for (emoji in ns.emojioneList) { if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue; map[ns.convert(ns.emojioneList[emoji].uc_output)] = emoji; } return map; }; ns.unicodeCharRegex = function() { var map = []; for (emoji in ns.emojioneList) { if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue; map.push(ns.convert(ns.emojioneList[emoji].uc_output)); } return map.join('|'); }; ns.mapEmojioneList = function (addToMapStorage) { for (var shortname in ns.emojioneList) { if (!ns.emojioneList.hasOwnProperty(shortname)) { continue; } var unicode = ns.emojioneList[shortname].uc_base; addToMapStorage(unicode, shortname); } }; ns.mapUnicodeToShort = function() { if (!ns.memMapShortToUnicode) { ns.memMapShortToUnicode = {}; ns.mapEmojioneList(function (unicode, shortname) { ns.memMapShortToUnicode[unicode] = shortname; }); } return ns.memMapShortToUnicode; }; ns.memorizeReplacement = function() { if (!ns.unicodeReplacementRegEx || !ns.memMapShortToUnicodeCharacters) { var unicodeList = []; ns.memMapShortToUnicodeCharacters = {}; ns.mapEmojioneList(function (unicode, shortname) { var emojiCharacter = ns.convert(unicode); ns.memMapShortToUnicodeCharacters[emojiCharacter] = shortname; unicodeList.push(emojiCharacter); }); ns.unicodeReplacementRegEx = unicodeList.join('|'); } }; ns.mapUnicodeCharactersToShort = function() { ns.memorizeReplacement(); return ns.memMapShortToUnicodeCharacters; }; //reverse an object ns.objectFlip = function (obj) { var key, tmp_obj = {}; for (key in obj) { if (obj.hasOwnProperty(key)) { tmp_obj[obj[key]] = key; } } return tmp_obj; }; ns.escapeRegExp = function(string) { return string.replace(/[-[\]{}()*+?.,;:&\\^$#\s]/g, "\\$&"); }; ns.replaceAll = function(string, find) { var escapedFind = ns.escapeRegExp(find); //sorted largest output to smallest output var search = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+escapedFind+")", "gi"); // callback prevents replacing anything inside of these common html tags as well as between an tag var replace = function(entire, m1) { return ((typeof m1 === 'undefined') || (m1 === '')) ? entire : ns.shortnameConversionMap()[m1]; }; return string.replace(search,replace); }; }(this.emojione = this.emojione || {})); if(true) module.exports = this.emojione; /***/ }), /***/ "./node_modules/filesize/lib/filesize.js": /*!***********************************************!*\ !*** ./node_modules/filesize/lib/filesize.js ***! \***********************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(global) { /** * filesize * * @copyright 2018 Jason Mulligan * @license BSD-3-Clause * @version 3.6.1 */ (function (global) { var b = /^(b|B)$/, symbol = { iec: { bits: ["b", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib"], bytes: ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] }, jedec: { bits: ["b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb"], bytes: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] } }, fullform = { iec: ["", "kibi", "mebi", "gibi", "tebi", "pebi", "exbi", "zebi", "yobi"], jedec: ["", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"] }; /** * filesize * * @method filesize * @param {Mixed} arg String, Int or Float to transform * @param {Object} descriptor [Optional] Flags * @return {String} Readable file size String */ function filesize(arg) { var descriptor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var result = [], val = 0, e = void 0, base = void 0, bits = void 0, ceil = void 0, full = void 0, fullforms = void 0, neg = void 0, num = void 0, output = void 0, round = void 0, unix = void 0, separator = void 0, spacer = void 0, standard = void 0, symbols = void 0; if (isNaN(arg)) { throw new Error("Invalid arguments"); } bits = descriptor.bits === true; unix = descriptor.unix === true; base = descriptor.base || 2; round = descriptor.round !== void 0 ? descriptor.round : unix ? 1 : 2; separator = descriptor.separator !== void 0 ? descriptor.separator || "" : ""; spacer = descriptor.spacer !== void 0 ? descriptor.spacer : unix ? "" : " "; symbols = descriptor.symbols || descriptor.suffixes || {}; standard = base === 2 ? descriptor.standard || "jedec" : "jedec"; output = descriptor.output || "string"; full = descriptor.fullform === true; fullforms = descriptor.fullforms instanceof Array ? descriptor.fullforms : []; e = descriptor.exponent !== void 0 ? descriptor.exponent : -1; num = Number(arg); neg = num < 0; ceil = base > 2 ? 1000 : 1024; // Flipping a negative number to determine the size if (neg) { num = -num; } // Determining the exponent if (e === -1 || isNaN(e)) { e = Math.floor(Math.log(num) / Math.log(ceil)); if (e < 0) { e = 0; } } // Exceeding supported length, time to reduce & multiply if (e > 8) { e = 8; } // Zero is now a special case because bytes divide by 1 if (num === 0) { result[0] = 0; result[1] = unix ? "" : symbol[standard][bits ? "bits" : "bytes"][e]; } else { val = num / (base === 2 ? Math.pow(2, e * 10) : Math.pow(1000, e)); if (bits) { val = val * 8; if (val >= ceil && e < 8) { val = val / ceil; e++; } } result[0] = Number(val.toFixed(e > 0 ? round : 0)); result[1] = base === 10 && e === 1 ? bits ? "kb" : "kB" : symbol[standard][bits ? "bits" : "bytes"][e]; if (unix) { result[1] = standard === "jedec" ? result[1].charAt(0) : e > 0 ? result[1].replace(/B$/, "") : result[1]; if (b.test(result[1])) { result[0] = Math.floor(result[0]); result[1] = ""; } } } // Decorating a 'diff' if (neg) { result[0] = -result[0]; } // Applying custom symbol result[1] = symbols[result[1]] || result[1]; // Returning Array, Object, or String (default) if (output === "array") { return result; } if (output === "exponent") { return e; } if (output === "object") { return { value: result[0], suffix: result[1], symbol: result[1] }; } if (full) { result[1] = fullforms[e] ? fullforms[e] : fullform[standard][e] + (bits ? "bit" : "byte") + (result[0] === 1 ? "" : "s"); } if (separator.length > 0) { result[0] = result[0].toString().replace(".", separator); } return result.join(spacer); } // Partial application for functional programming filesize.partial = function (opt) { return function (arg) { return filesize(arg, opt); }; }; // CommonJS, AMD, script tag if (true) { module.exports = filesize; } else {} })(typeof window !== "undefined" ? window : global); /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js"))) /***/ }), /***/ "./node_modules/hellojs/dist/hello.all.js": /*!************************************************!*\ !*** ./node_modules/hellojs/dist/hello.all.js ***! \************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(process, setImmediate) {var __WEBPACK_AMD_DEFINE_RESULT__;/*! hellojs v1.16.1 | (c) 2012-2017 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ // ES5 Object.create if (!Object.create) { // Shim, Object create // A shim for Object.create(), it adds a prototype to a new object Object.create = (function() { function F() {} return function(o) { if (arguments.length != 1) { throw new Error('Object.create implementation only accepts one parameter.'); } F.prototype = o; return new F(); }; })(); } // ES5 Object.keys if (!Object.keys) { Object.keys = function(o, k, r) { r = []; for (k in o) { if (r.hasOwnProperty.call(o, k)) r.push(k); } return r; }; } // ES5 [].indexOf if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(s) { for (var j = 0; j < this.length; j++) { if (this[j] === s) { return j; } } return -1; }; } // ES5 [].forEach if (!Array.prototype.forEach) { Array.prototype.forEach = function(fun/*, thisArg*/) { if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (typeof fun !== 'function') { throw new TypeError(); } var thisArg = arguments.length >= 2 ? arguments[1] : void 0; for (var i = 0; i < len; i++) { if (i in t) { fun.call(thisArg, t[i], i, t); } } return this; }; } // ES5 [].filter if (!Array.prototype.filter) { Array.prototype.filter = function(fun, thisArg) { var a = []; this.forEach(function(val, i, t) { if (fun.call(thisArg || void 0, val, i, t)) { a.push(val); } }); return a; }; } // Production steps of ECMA-262, Edition 5, 15.4.4.19 // Reference: http://es5.github.io/#x15.4.4.19 if (!Array.prototype.map) { Array.prototype.map = function(fun, thisArg) { var a = []; this.forEach(function(val, i, t) { a.push(fun.call(thisArg || void 0, val, i, t)); }); return a; }; } // ES5 isArray if (!Array.isArray) { // Function Array.isArray Array.isArray = function(o) { return Object.prototype.toString.call(o) === '[object Array]'; }; } // Test for location.assign if (typeof window === 'object' && typeof window.location === 'object' && !window.location.assign) { window.location.assign = function(url) { window.location = url; }; } // Test for Function.bind if (!Function.prototype.bind) { // MDN // Polyfill IE8, does not support native Function.bind Function.prototype.bind = function(b) { if (typeof this !== 'function') { throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } function C() {} var a = [].slice; var f = a.call(arguments, 1); var _this = this; var D = function() { return _this.apply(this instanceof C ? this : b || window, f.concat(a.call(arguments))); }; C.prototype = this.prototype; D.prototype = new C(); return D; }; } /** * @hello.js * * HelloJS is a client side Javascript SDK for making OAuth2 logins and subsequent REST calls. * * @author Andrew Dodson * @website https://adodson.com/hello.js/ * * @copyright Andrew Dodson, 2012 - 2015 * @license MIT: You are free to use and modify this code for any use, on the condition that this copyright notice remains. */ var hello = function(name) { return hello.use(name); }; hello.utils = { // Extend the first object with the properties and methods of the second extend: function(r /*, a[, b[, ...]] */) { // Get the arguments as an array but ommit the initial item Array.prototype.slice.call(arguments, 1).forEach(function(a) { if (Array.isArray(r) && Array.isArray(a)) { Array.prototype.push.apply(r, a); } else if (r && (r instanceof Object || typeof r === 'object') && a && (a instanceof Object || typeof a === 'object') && r !== a) { for (var x in a) { r[x] = hello.utils.extend(r[x], a[x]); } } else { if (Array.isArray(a)) { // Clone it a = a.slice(0); } r = a; } }); return r; } }; // Core library hello.utils.extend(hello, { settings: { // OAuth2 authentication defaults redirect_uri: window.location.href.split('#')[0], response_type: 'token', display: 'popup', state: '', // OAuth1 shim // The path to the OAuth1 server for signing user requests // Want to recreate your own? Checkout https://github.com/MrSwitch/node-oauth-shim oauth_proxy: 'https://auth-server.herokuapp.com/proxy', // API timeout in milliseconds timeout: 20000, // Popup Options popup: { resizable: 1, scrollbars: 1, width: 500, height: 550 }, // Default scope // Many services require atleast a profile scope, // HelloJS automatially includes the value of provider.scope_map.basic // If that's not required it can be removed via hello.settings.scope.length = 0; scope: ['basic'], // Scope Maps // This is the default module scope, these are the defaults which each service is mapped too. // By including them here it prevents the scope from being applied accidentally scope_map: { basic: '' }, // Default service / network default_service: null, // Force authentication // When hello.login is fired. // (null): ignore current session expiry and continue with login // (true): ignore current session expiry and continue with login, ask for user to reauthenticate // (false): if the current session looks good for the request scopes return the current session. force: null, // Page URL // When 'display=page' this property defines where the users page should end up after redirect_uri // Ths could be problematic if the redirect_uri is indeed the final place, // Typically this circumvents the problem of the redirect_url being a dumb relay page. page_uri: window.location.href }, // Service configuration objects services: {}, // Use // Define a new instance of the HelloJS library with a default service use: function(service) { // Create self, which inherits from its parent var self = Object.create(this); // Inherit the prototype from its parent self.settings = Object.create(this.settings); // Define the default service if (service) { self.settings.default_service = service; } // Create an instance of Events self.utils.Event.call(self); return self; }, // Initialize // Define the client_ids for the endpoint services // @param object o, contains a key value pair, service => clientId // @param object opts, contains a key value pair of options used for defining the authentication defaults // @param number timeout, timeout in seconds init: function(services, options) { var utils = this.utils; if (!services) { return this.services; } // Define provider credentials // Reformat the ID field for (var x in services) {if (services.hasOwnProperty(x)) { if (typeof (services[x]) !== 'object') { services[x] = {id: services[x]}; } }} // Merge services if there already exists some utils.extend(this.services, services); // Update the default settings with this one. if (options) { utils.extend(this.settings, options); // Do this immediatly incase the browser changes the current path. if ('redirect_uri' in options) { this.settings.redirect_uri = utils.url(options.redirect_uri).href; } } return this; }, // Login // Using the endpoint // @param network stringify name to connect to // @param options object (optional) {display mode, is either none|popup(default)|page, scope: email,birthday,publish, .. } // @param callback function (optional) fired on signin login: function() { // Create an object which inherits its parent as the prototype and constructs a new event chain. var _this = this; var utils = _this.utils; var error = utils.error; var promise = utils.Promise(); // Get parameters var p = utils.args({network: 's', options: 'o', callback: 'f'}, arguments); // Local vars var url; // Get all the custom options and store to be appended to the querystring var qs = utils.diffKey(p.options, _this.settings); // Merge/override options with app defaults var opts = p.options = utils.merge(_this.settings, p.options || {}); // Merge/override options with app defaults opts.popup = utils.merge(_this.settings.popup, p.options.popup || {}); // Network p.network = p.network || _this.settings.default_service; // Bind callback to both reject and fulfill states promise.proxy.then(p.callback, p.callback); // Trigger an event on the global listener function emit(s, value) { hello.emit(s, value); } promise.proxy.then(emit.bind(this, 'auth.login auth'), emit.bind(this, 'auth.failed auth')); // Is our service valid? if (typeof (p.network) !== 'string' || !(p.network in _this.services)) { // Trigger the default login. // Ahh we dont have one. return promise.reject(error('invalid_network', 'The provided network was not recognized')); } var provider = _this.services[p.network]; // Create a global listener to capture events triggered out of scope var callbackId = utils.globalEvent(function(str) { // The responseHandler returns a string, lets save this locally var obj; if (str) { obj = JSON.parse(str); } else { obj = error('cancelled', 'The authentication was not completed'); } // Handle these response using the local // Trigger on the parent if (!obj.error) { // Save on the parent window the new credentials // This fixes an IE10 bug i think... atleast it does for me. utils.store(obj.network, obj); // Fulfill a successful login promise.fulfill({ network: obj.network, authResponse: obj }); } else { // Reject a successful login promise.reject(obj); } }); var redirectUri = utils.url(opts.redirect_uri).href; // May be a space-delimited list of multiple, complementary types var responseType = provider.oauth.response_type || opts.response_type; // Fallback to token if the module hasn't defined a grant url if (/\bcode\b/.test(responseType) && !provider.oauth.grant) { responseType = responseType.replace(/\bcode\b/, 'token'); } // Query string parameters, we may pass our own arguments to form the querystring p.qs = utils.merge(qs, { client_id: encodeURIComponent(provider.id), response_type: encodeURIComponent(responseType), redirect_uri: encodeURIComponent(redirectUri), state: { client_id: provider.id, network: p.network, display: opts.display, callback: callbackId, state: opts.state, redirect_uri: redirectUri } }); // Get current session for merging scopes, and for quick auth response var session = utils.store(p.network); // Scopes (authentication permisions) // Ensure this is a string - IE has a problem moving Arrays between windows // Append the setup scope var SCOPE_SPLIT = /[,\s]+/; // Include default scope settings (cloned). var scope = _this.settings.scope ? [_this.settings.scope.toString()] : []; // Extend the providers scope list with the default var scopeMap = utils.merge(_this.settings.scope_map, provider.scope || {}); // Add user defined scopes... if (opts.scope) { scope.push(opts.scope.toString()); } // Append scopes from a previous session. // This helps keep app credentials constant, // Avoiding having to keep tabs on what scopes are authorized if (session && 'scope' in session && session.scope instanceof String) { scope.push(session.scope); } // Join and Split again scope = scope.join(',').split(SCOPE_SPLIT); // Format remove duplicates and empty values scope = utils.unique(scope).filter(filterEmpty); // Save the the scopes to the state with the names that they were requested with. p.qs.state.scope = scope.join(','); // Map scopes to the providers naming convention scope = scope.map(function(item) { // Does this have a mapping? return (item in scopeMap) ? scopeMap[item] : item; }); // Stringify and Arrayify so that double mapped scopes are given the chance to be formatted scope = scope.join(',').split(SCOPE_SPLIT); // Again... // Format remove duplicates and empty values scope = utils.unique(scope).filter(filterEmpty); // Join with the expected scope delimiter into a string p.qs.scope = scope.join(provider.scope_delim || ','); // Is the user already signed in with the appropriate scopes, valid access_token? if (opts.force === false) { if (session && 'access_token' in session && session.access_token && 'expires' in session && session.expires > ((new Date()).getTime() / 1e3)) { // What is different about the scopes in the session vs the scopes in the new login? var diff = utils.diff((session.scope || '').split(SCOPE_SPLIT), (p.qs.state.scope || '').split(SCOPE_SPLIT)); if (diff.length === 0) { // OK trigger the callback promise.fulfill({ unchanged: true, network: p.network, authResponse: session }); // Nothing has changed return promise; } } } // Page URL if (opts.display === 'page' && opts.page_uri) { // Add a page location, place to endup after session has authenticated p.qs.state.page_uri = utils.url(opts.page_uri).href; } // Bespoke // Override login querystrings from auth_options if ('login' in provider && typeof (provider.login) === 'function') { // Format the paramaters according to the providers formatting function provider.login(p); } // Add OAuth to state // Where the service is going to take advantage of the oauth_proxy if (!/\btoken\b/.test(responseType) || parseInt(provider.oauth.version, 10) < 2 || (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token)) { // Add the oauth endpoints p.qs.state.oauth = provider.oauth; // Add the proxy url p.qs.state.oauth_proxy = opts.oauth_proxy; } // Convert state to a string p.qs.state = encodeURIComponent(JSON.stringify(p.qs.state)); // URL if (parseInt(provider.oauth.version, 10) === 1) { // Turn the request to the OAuth Proxy for 3-legged auth url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); } // Refresh token else if (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token) { // Add the refresh_token to the request p.qs.refresh_token = session.refresh_token; // Define the request path url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); } else { url = utils.qs(provider.oauth.auth, p.qs, encodeFunction); } // Broadcast this event as an auth:init emit('auth.init', p); // Execute // Trigger how we want self displayed if (opts.display === 'none') { // Sign-in in the background, iframe utils.iframe(url, redirectUri); } // Triggering popup? else if (opts.display === 'popup') { var popup = utils.popup(url, redirectUri, opts.popup); var timer = setInterval(function() { if (!popup || popup.closed) { clearInterval(timer); if (!promise.state) { var response = error('cancelled', 'Login has been cancelled'); if (!popup) { response = error('blocked', 'Popup was blocked'); } response.network = p.network; promise.reject(response); } } }, 100); } else { window.location = url; } return promise.proxy; function encodeFunction(s) {return s;} function filterEmpty(s) {return !!s;} }, // Remove any data associated with a given service // @param string name of the service // @param function callback logout: function() { var _this = this; var utils = _this.utils; var error = utils.error; // Create a new promise var promise = utils.Promise(); var p = utils.args({name:'s', options: 'o', callback: 'f'}, arguments); p.options = p.options || {}; // Add callback to events promise.proxy.then(p.callback, p.callback); // Trigger an event on the global listener function emit(s, value) { hello.emit(s, value); } promise.proxy.then(emit.bind(this, 'auth.logout auth'), emit.bind(this, 'error')); // Network p.name = p.name || this.settings.default_service; p.authResponse = utils.store(p.name); if (p.name && !(p.name in _this.services)) { promise.reject(error('invalid_network', 'The network was unrecognized')); } else if (p.name && p.authResponse) { // Define the callback var callback = function(opts) { // Remove from the store utils.store(p.name, null); // Emit events by default promise.fulfill(hello.utils.merge({network:p.name}, opts || {})); }; // Run an async operation to remove the users session var _opts = {}; if (p.options.force) { var logout = _this.services[p.name].logout; if (logout) { // Convert logout to URL string, // If no string is returned, then this function will handle the logout async style if (typeof (logout) === 'function') { logout = logout(callback, p); } // If logout is a string then assume URL and open in iframe. if (typeof (logout) === 'string') { utils.iframe(logout); _opts.force = null; _opts.message = 'Logout success on providers site was indeterminate'; } else if (logout === undefined) { // The callback function will handle the response. return promise.proxy; } } } // Remove local credentials callback(_opts); } else { promise.reject(error('invalid_session', 'There was no session to remove')); } return promise.proxy; }, // Returns all the sessions that are subscribed too // @param string optional, name of the service to get information about. getAuthResponse: function(service) { // If the service doesn't exist service = service || this.settings.default_service; if (!service || !(service in this.services)) { return null; } return this.utils.store(service) || null; }, // Events: placeholder for the events events: {} }); // Core utilities hello.utils.extend(hello.utils, { // Error error: function(code, message) { return { error: { code: code, message: message } }; }, // Append the querystring to a url // @param string url // @param object parameters qs: function(url, params, formatFunction) { if (params) { // Set default formatting function formatFunction = formatFunction || encodeURIComponent; // Override the items in the URL which already exist for (var x in params) { var str = '([\\?\\&])' + x + '=[^\\&]*'; var reg = new RegExp(str); if (url.match(reg)) { url = url.replace(reg, '$1' + x + '=' + formatFunction(params[x])); delete params[x]; } } } if (!this.isEmpty(params)) { return url + (url.indexOf('?') > -1 ? '&' : '?') + this.param(params, formatFunction); } return url; }, // Param // Explode/encode the parameters of an URL string/object // @param string s, string to decode param: function(s, formatFunction) { var b; var a = {}; var m; if (typeof (s) === 'string') { formatFunction = formatFunction || decodeURIComponent; m = s.replace(/^[\#\?]/, '').match(/([^=\/\&]+)=([^\&]+)/g); if (m) { for (var i = 0; i < m.length; i++) { b = m[i].match(/([^=]+)=(.*)/); a[b[1]] = formatFunction(b[2]); } } return a; } else { formatFunction = formatFunction || encodeURIComponent; var o = s; a = []; for (var x in o) {if (o.hasOwnProperty(x)) { if (o.hasOwnProperty(x)) { a.push([x, o[x] === '?' ? '?' : formatFunction(o[x])].join('=')); } }} return a.join('&'); } }, // Local storage facade store: (function() { var a = ['localStorage', 'sessionStorage']; var i = -1; var prefix = 'test'; // Set LocalStorage var localStorage; while (a[++i]) { try { // In Chrome with cookies blocked, calling localStorage throws an error localStorage = window[a[i]]; localStorage.setItem(prefix + i, i); localStorage.removeItem(prefix + i); break; } catch (e) { localStorage = null; } } if (!localStorage) { var cache = null; localStorage = { getItem: function(prop) { prop = prop + '='; var m = document.cookie.split(';'); for (var i = 0; i < m.length; i++) { var _m = m[i].replace(/(^\s+|\s+$)/, ''); if (_m && _m.indexOf(prop) === 0) { return _m.substr(prop.length); } } return cache; }, setItem: function(prop, value) { cache = value; document.cookie = prop + '=' + value; } }; // Fill the cache up cache = localStorage.getItem('hello'); } function get() { var json = {}; try { json = JSON.parse(localStorage.getItem('hello')) || {}; } catch (e) {} return json; } function set(json) { localStorage.setItem('hello', JSON.stringify(json)); } // Check if the browser support local storage return function(name, value, days) { // Local storage var json = get(); if (name && value === undefined) { return json[name] || null; } else if (name && value === null) { try { delete json[name]; } catch (e) { json[name] = null; } } else if (name) { json[name] = value; } else { return json; } set(json); return json || null; }; })(), // Create and Append new DOM elements // @param node string // @param attr object literal // @param dom/string append: function(node, attr, target) { var n = typeof (node) === 'string' ? document.createElement(node) : node; if (typeof (attr) === 'object') { if ('tagName' in attr) { target = attr; } else { for (var x in attr) {if (attr.hasOwnProperty(x)) { if (typeof (attr[x]) === 'object') { for (var y in attr[x]) {if (attr[x].hasOwnProperty(y)) { n[x][y] = attr[x][y]; }} } else if (x === 'html') { n.innerHTML = attr[x]; } // IE doesn't like us setting methods with setAttribute else if (!/^on/.test(x)) { n.setAttribute(x, attr[x]); } else { n[x] = attr[x]; } }} } } if (target === 'body') { (function self() { if (document.body) { document.body.appendChild(n); } else { setTimeout(self, 16); } })(); } else if (typeof (target) === 'object') { target.appendChild(n); } else if (typeof (target) === 'string') { document.getElementsByTagName(target)[0].appendChild(n); } return n; }, // An easy way to create a hidden iframe // @param string src iframe: function(src) { this.append('iframe', {src: src, style: {position:'absolute', left: '-1000px', bottom: 0, height: '1px', width: '1px'}}, 'body'); }, // Recursive merge two objects into one, second parameter overides the first // @param a array merge: function(/* Args: a, b, c, .. n */) { var args = Array.prototype.slice.call(arguments); args.unshift({}); return this.extend.apply(null, args); }, // Makes it easier to assign parameters, where some are optional // @param o object // @param a arguments args: function(o, args) { var p = {}; var i = 0; var t = null; var x = null; // 'x' is the first key in the list of object parameters for (x in o) {if (o.hasOwnProperty(x)) { break; }} // Passing in hash object of arguments? // Where the first argument can't be an object if ((args.length === 1) && (typeof (args[0]) === 'object') && o[x] != 'o!') { // Could this object still belong to a property? // Check the object keys if they match any of the property keys for (x in args[0]) {if (o.hasOwnProperty(x)) { // Does this key exist in the property list? if (x in o) { // Yes this key does exist so its most likely this function has been invoked with an object parameter // Return first argument as the hash of all arguments return args[0]; } }} } // Else loop through and account for the missing ones. for (x in o) {if (o.hasOwnProperty(x)) { t = typeof (args[i]); if ((typeof (o[x]) === 'function' && o[x].test(args[i])) || (typeof (o[x]) === 'string' && ( (o[x].indexOf('s') > -1 && t === 'string') || (o[x].indexOf('o') > -1 && t === 'object') || (o[x].indexOf('i') > -1 && t === 'number') || (o[x].indexOf('a') > -1 && t === 'object') || (o[x].indexOf('f') > -1 && t === 'function') )) ) { p[x] = args[i++]; } else if (typeof (o[x]) === 'string' && o[x].indexOf('!') > -1) { return false; } }} return p; }, // Returns a URL instance url: function(path) { // If the path is empty if (!path) { return window.location; } // Chrome and FireFox support new URL() to extract URL objects else if (window.URL && URL instanceof Function && URL.length !== 0) { return new URL(path, window.location); } // Ugly shim, it works! else { var a = document.createElement('a'); a.href = path; return a.cloneNode(false); } }, diff: function(a, b) { return b.filter(function(item) { return a.indexOf(item) === -1; }); }, // Get the different hash of properties unique to `a`, and not in `b` diffKey: function(a, b) { if (a || !b) { var r = {}; for (var x in a) { // Does the property not exist? if (!(x in b)) { r[x] = a[x]; } } return r; } return a; }, // Unique // Remove duplicate and null values from an array // @param a array unique: function(a) { if (!Array.isArray(a)) { return []; } return a.filter(function(item, index) { // Is this the first location of item return a.indexOf(item) === index; }); }, isEmpty: function(obj) { // Scalar if (!obj) return true; // Array if (Array.isArray(obj)) { return !obj.length; } else if (typeof (obj) === 'object') { // Object for (var key in obj) { if (obj.hasOwnProperty(key)) { return false; } } } return true; }, //jscs:disable /*! ** Thenable -- Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable ** Copyright (c) 2013-2014 Ralf S. Engelschall ** Licensed under The MIT License ** Source-Code distributed on */ Promise: (function(){ /* promise states [Promises/A+ 2.1] */ var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */ var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */ var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */ /* promise object constructor */ var api = function (executor) { /* optionally support non-constructor/plain-function call */ if (!(this instanceof api)) return new api(executor); /* initialize object */ this.id = "Thenable/1.0.6"; this.state = STATE_PENDING; /* initial state */ this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */ this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */ this.onFulfilled = []; /* initial handlers */ this.onRejected = []; /* initial handlers */ /* provide optional information-hiding proxy */ this.proxy = { then: this.then.bind(this) }; /* support optional executor function */ if (typeof executor === "function") executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); }; /* promise API methods */ api.prototype = { /* promise resolving methods */ fulfill: function (value) { return deliver(this, STATE_FULFILLED, "fulfillValue", value); }, reject: function (value) { return deliver(this, STATE_REJECTED, "rejectReason", value); }, /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ then: function (onFulfilled, onRejected) { var curr = this; var next = new api(); /* [Promises/A+ 2.2.7] */ curr.onFulfilled.push( resolver(onFulfilled, next, "fulfill")); /* [Promises/A+ 2.2.2/2.2.6] */ curr.onRejected.push( resolver(onRejected, next, "reject" )); /* [Promises/A+ 2.2.3/2.2.6] */ execute(curr); return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */ } }; /* deliver an action */ var deliver = function (curr, state, name, value) { if (curr.state === STATE_PENDING) { curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ curr[name] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ execute(curr); } return curr; }; /* execute all handlers */ var execute = function (curr) { if (curr.state === STATE_FULFILLED) execute_handlers(curr, "onFulfilled", curr.fulfillValue); else if (curr.state === STATE_REJECTED) execute_handlers(curr, "onRejected", curr.rejectReason); }; /* execute particular set of handlers */ var execute_handlers = function (curr, name, value) { /* global process: true */ /* global setImmediate: true */ /* global setTimeout: true */ /* short-circuit processing */ if (curr[name].length === 0) return; /* iterate over all handlers, exactly once */ var handlers = curr[name]; curr[name] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ var func = function () { for (var i = 0; i < handlers.length; i++) handlers[i](value); /* [Promises/A+ 2.2.5] */ }; /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */ if (typeof process === "object" && typeof process.nextTick === "function") process.nextTick(func); else if (typeof setImmediate === "function") setImmediate(func); else setTimeout(func, 0); }; /* generate a resolver function */ var resolver = function (cb, next, method) { return function (value) { if (typeof cb !== "function") /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ next[method].call(next, value); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ else { var result; try { result = cb(value); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ catch (e) { next.reject(e); /* [Promises/A+ 2.2.7.2] */ return; } resolve(next, result); /* [Promises/A+ 2.2.7.1] */ } }; }; /* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */ var resolve = function (promise, x) { /* sanity check arguments */ /* [Promises/A+ 2.3.1] */ if (promise === x || promise.proxy === x) { promise.reject(new TypeError("cannot resolve promise with itself")); return; } /* surgically check for a "then" method (mainly to just call the "getter" of "then" only once) */ var then; if ((typeof x === "object" && x !== null) || typeof x === "function") { try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */ catch (e) { promise.reject(e); /* [Promises/A+ 2.3.3.2] */ return; } } /* handle own Thenables [Promises/A+ 2.3.2] and similar "thenables" [Promises/A+ 2.3.3] */ if (typeof then === "function") { var resolved = false; try { /* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */ then.call(x, /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */ function (y) { if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ if (y === x) /* [Promises/A+ 3.6] */ promise.reject(new TypeError("circular thenable chain")); else resolve(promise, y); }, /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */ function (r) { if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ promise.reject(r); } ); } catch (e) { if (!resolved) /* [Promises/A+ 2.3.3.3.3] */ promise.reject(e); /* [Promises/A+ 2.3.3.3.4] */ } return; } /* handle other values */ promise.fulfill(x); /* [Promises/A+ 2.3.4, 2.3.3.4] */ }; /* export API */ return api; })(), //jscs:enable // Event // A contructor superclass for adding event menthods, on, off, emit. Event: function() { var separator = /[\s\,]+/; // If this doesn't support getPrototype then we can't get prototype.events of the parent // So lets get the current instance events, and add those to a parent property this.parent = { events: this.events, findEvents: this.findEvents, parent: this.parent, utils: this.utils }; this.events = {}; // On, subscribe to events // @param evt string // @param callback function this.on = function(evt, callback) { if (callback && typeof (callback) === 'function') { var a = evt.split(separator); for (var i = 0; i < a.length; i++) { // Has this event already been fired on this instance? this.events[a[i]] = [callback].concat(this.events[a[i]] || []); } } return this; }; // Off, unsubscribe to events // @param evt string // @param callback function this.off = function(evt, callback) { this.findEvents(evt, function(name, index) { if (!callback || this.events[name][index] === callback) { this.events[name][index] = null; } }); return this; }; // Emit // Triggers any subscribed events this.emit = function(evt /*, data, ... */) { // Get arguments as an Array, knock off the first one var args = Array.prototype.slice.call(arguments, 1); args.push(evt); // Handler var handler = function(name, index) { // Replace the last property with the event name args[args.length - 1] = (name === '*' ? evt : name); // Trigger this.events[name][index].apply(this, args); }; // Find the callbacks which match the condition and call var _this = this; while (_this && _this.findEvents) { // Find events which match _this.findEvents(evt + ',*', handler); _this = _this.parent; } return this; }; // // Easy functions this.emitAfter = function() { var _this = this; var args = arguments; setTimeout(function() { _this.emit.apply(_this, args); }, 0); return this; }; this.findEvents = function(evt, callback) { var a = evt.split(separator); for (var name in this.events) {if (this.events.hasOwnProperty(name)) { if (a.indexOf(name) > -1) { for (var i = 0; i < this.events[name].length; i++) { // Does the event handler exist? if (this.events[name][i]) { // Emit on the local instance of this callback.call(this, name, i); } } } }} }; return this; }, // Global Events // Attach the callback to the window object // Return its unique reference globalEvent: function(callback, guid) { // If the guid has not been supplied then create a new one. guid = guid || '_hellojs_' + parseInt(Math.random() * 1e12, 10).toString(36); // Define the callback function window[guid] = function() { // Trigger the callback try { if (callback.apply(this, arguments)) { delete window[guid]; } } catch (e) { console.error(e); } }; return guid; }, // Trigger a clientside popup // This has been augmented to support PhoneGap popup: function(url, redirectUri, options) { var documentElement = document.documentElement; // Multi Screen Popup Positioning (http://stackoverflow.com/a/16861050) // Credit: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html // Fixes dual-screen position Most browsers Firefox if (options.height) { var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; var height = screen.height || window.innerHeight || documentElement.clientHeight; options.top = (options.top) ? options.top : parseInt((height - options.height) / 2, 10) + dualScreenTop; } if (options.width) { var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; var width = screen.width || window.innerWidth || documentElement.clientWidth; options.left = (options.left) ? options.left : parseInt((width - options.width) / 2, 10) + dualScreenLeft; } // Convert options into an array var optionsArray = []; Object.keys(options).forEach(function(name) { var value = options[name]; optionsArray.push(name + (value !== null ? '=' + value : '')); }); // Call the open() function with the initial path // // OAuth redirect, fixes URI fragments from being lost in Safari // (URI Fragments within 302 Location URI are lost over HTTPS) // Loading the redirect.html before triggering the OAuth Flow seems to fix it. // // Firefox decodes URL fragments when calling location.hash. // - This is bad if the value contains break points which are escaped // - Hence the url must be encoded twice as it contains breakpoints. if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { url = redirectUri + '#oauth_redirect=' + encodeURIComponent(encodeURIComponent(url)); } var popup = window.open( url, '_blank', optionsArray.join(',') ); if (popup && popup.focus) { popup.focus(); } return popup; }, // OAuth and API response handler responseHandler: function(window, parent) { var _this = this; var p; var location = window.location; // Is this an auth relay message which needs to call the proxy? p = _this.param(location.search); // OAuth2 or OAuth1 server response? if (p && p.state && (p.code || p.oauth_token)) { var state = JSON.parse(p.state); // Add this path as the redirect_uri p.redirect_uri = state.redirect_uri || location.href.replace(/[\?\#].*$/, ''); // Redirect to the host var path = _this.qs(state.oauth_proxy, p); location.assign(path); return; } // Save session, from redirected authentication // #access_token has come in? // // FACEBOOK is returning auth errors within as a query_string... thats a stickler for consistency. // SoundCloud is the state in the querystring and the token in the hashtag, so we'll mix the two together p = _this.merge(_this.param(location.search || ''), _this.param(location.hash || '')); // If p.state if (p && 'state' in p) { // Remove any addition information // E.g. p.state = 'facebook.page'; try { var a = JSON.parse(p.state); _this.extend(p, a); } catch (e) { var stateDecoded = decodeURIComponent(p.state); try { var b = JSON.parse(stateDecoded); _this.extend(p, b); } catch (e) { console.error('Could not decode state parameter'); } } // Access_token? if (('access_token' in p && p.access_token) && p.network) { if (!p.expires_in || parseInt(p.expires_in, 10) === 0) { // If p.expires_in is unset, set to 0 p.expires_in = 0; } p.expires_in = parseInt(p.expires_in, 10); p.expires = ((new Date()).getTime() / 1e3) + (p.expires_in || (60 * 60 * 24 * 365)); // Lets use the "state" to assign it to one of our networks authCallback(p, window, parent); } // Error=? // &error_description=? // &state=? else if (('error' in p && p.error) && p.network) { p.error = { code: p.error, message: p.error_message || p.error_description }; // Let the state handler handle it authCallback(p, window, parent); } // API call, or a cancelled login // Result is serialized JSON string else if (p.callback && p.callback in parent) { // Trigger a function in the parent var res = 'result' in p && p.result ? JSON.parse(p.result) : false; // Trigger the callback on the parent callback(parent, p.callback)(res); closeWindow(); } // If this page is still open if (p.page_uri) { location.assign(p.page_uri); } } // OAuth redirect, fixes URI fragments from being lost in Safari // (URI Fragments within 302 Location URI are lost over HTTPS) // Loading the redirect.html before triggering the OAuth Flow seems to fix it. else if ('oauth_redirect' in p) { location.assign(decodeURIComponent(p.oauth_redirect)); return; } // Trigger a callback to authenticate function authCallback(obj, window, parent) { var cb = obj.callback; var network = obj.network; // Trigger the callback on the parent _this.store(network, obj); // If this is a page request it has no parent or opener window to handle callbacks if (('display' in obj) && obj.display === 'page') { return; } // Remove from session object if (parent && cb && cb in parent) { try { delete obj.callback; } catch (e) {} // Update store _this.store(network, obj); // Call the globalEvent function on the parent // It's safer to pass back a string to the parent, // Rather than an object/array (better for IE8) var str = JSON.stringify(obj); try { callback(parent, cb)(str); } catch (e) { // Error thrown whilst executing parent callback } } closeWindow(); } function callback(parent, callbackID) { if (callbackID.indexOf('_hellojs_') !== 0) { return function() { throw 'Could not execute callback ' + callbackID; }; } return parent[callbackID]; } function closeWindow() { if (window.frameElement) { // Inside an iframe, remove from parent parent.document.body.removeChild(window.frameElement); } else { // Close this current window try { window.close(); } catch (e) {} // IOS bug wont let us close a popup if still loading if (window.addEventListener) { window.addEventListener('load', function() { window.close(); }); } } } } }); // Events // Extend the hello object with its own event instance hello.utils.Event.call(hello); /////////////////////////////////// // Monitoring session state // Check for session changes /////////////////////////////////// (function(hello) { // Monitor for a change in state and fire var oldSessions = {}; // Hash of expired tokens var expired = {}; // Listen to other triggers to Auth events, use these to update this hello.on('auth.login, auth.logout', function(auth) { if (auth && typeof (auth) === 'object' && auth.network) { oldSessions[auth.network] = hello.utils.store(auth.network) || {}; } }); (function self() { var CURRENT_TIME = ((new Date()).getTime() / 1e3); var emit = function(eventName) { hello.emit('auth.' + eventName, { network: name, authResponse: session }); }; // Loop through the services for (var name in hello.services) {if (hello.services.hasOwnProperty(name)) { if (!hello.services[name].id) { // We haven't attached an ID so dont listen. continue; } // Get session var session = hello.utils.store(name) || {}; var provider = hello.services[name]; var oldSess = oldSessions[name] || {}; // Listen for globalEvents that did not get triggered from the child if (session && 'callback' in session) { // To do remove from session object... var cb = session.callback; try { delete session.callback; } catch (e) {} // Update store // Removing the callback hello.utils.store(name, session); // Emit global events try { window[cb](session); } catch (e) {} } // Refresh token if (session && ('expires' in session) && session.expires < CURRENT_TIME) { // If auto refresh is possible // Either the browser supports var refresh = provider.refresh || session.refresh_token; // Has the refresh been run recently? if (refresh && (!(name in expired) || expired[name] < CURRENT_TIME)) { // Try to resignin hello.emit('notice', name + ' has expired trying to resignin'); hello.login(name, {display: 'none', force: false}); // Update expired, every 10 minutes expired[name] = CURRENT_TIME + 600; } // Does this provider not support refresh else if (!refresh && !(name in expired)) { // Label the event emit('expired'); expired[name] = true; } // If session has expired then we dont want to store its value until it can be established that its been updated continue; } // Has session changed? else if (oldSess.access_token === session.access_token && oldSess.expires === session.expires) { continue; } // Access_token has been removed else if (!session.access_token && oldSess.access_token) { emit('logout'); } // Access_token has been created else if (session.access_token && !oldSess.access_token) { emit('login'); } // Access_token has been updated else if (session.expires !== oldSess.expires) { emit('update'); } // Updated stored session oldSessions[name] = session; // Remove the expired flags if (name in expired) { delete expired[name]; } }} // Check error events setTimeout(self, 1000); })(); })(hello); // EOF CORE lib ////////////////////////////////// ///////////////////////////////////////// // API // @param path string // @param query object (optional) // @param method string (optional) // @param data object (optional) // @param timeout integer (optional) // @param callback function (optional) hello.api = function() { // Shorthand var _this = this; var utils = _this.utils; var error = utils.error; // Construct a new Promise object var promise = utils.Promise(); // Arguments var p = utils.args({path: 's!', query: 'o', method: 's', data: 'o', timeout: 'i', callback: 'f'}, arguments); // Method p.method = (p.method || 'get').toLowerCase(); // Headers p.headers = p.headers || {}; // Query p.query = p.query || {}; // If get, put all parameters into query if (p.method === 'get' || p.method === 'delete') { utils.extend(p.query, p.data); p.data = {}; } var data = p.data = p.data || {}; // Completed event callback promise.then(p.callback, p.callback); // Remove the network from path, e.g. facebook:/me/friends // Results in { network : facebook, path : me/friends } if (!p.path) { return promise.reject(error('invalid_path', 'Missing the path parameter from the request')); } p.path = p.path.replace(/^\/+/, ''); var a = (p.path.split(/[\/\:]/, 2) || [])[0].toLowerCase(); if (a in _this.services) { p.network = a; var reg = new RegExp('^' + a + ':?\/?'); p.path = p.path.replace(reg, ''); } // Network & Provider // Define the network that this request is made for p.network = _this.settings.default_service = p.network || _this.settings.default_service; var o = _this.services[p.network]; // INVALID // Is there no service by the given network name? if (!o) { return promise.reject(error('invalid_network', 'Could not match the service requested: ' + p.network)); } // PATH // As long as the path isn't flagged as unavaiable, e.g. path == false if (!(!(p.method in o) || !(p.path in o[p.method]) || o[p.method][p.path] !== false)) { return promise.reject(error('invalid_path', 'The provided path is not available on the selected network')); } // PROXY // OAuth1 calls always need a proxy if (!p.oauth_proxy) { p.oauth_proxy = _this.settings.oauth_proxy; } if (!('proxy' in p)) { p.proxy = p.oauth_proxy && o.oauth && parseInt(o.oauth.version, 10) === 1; } // TIMEOUT // Adopt timeout from global settings by default if (!('timeout' in p)) { p.timeout = _this.settings.timeout; } // Format response // Whether to run the raw response through post processing. if (!('formatResponse' in p)) { p.formatResponse = true; } // Get the current session // Append the access_token to the query p.authResponse = _this.getAuthResponse(p.network); if (p.authResponse && p.authResponse.access_token) { p.query.access_token = p.authResponse.access_token; } var url = p.path; var m; // Store the query as options // This is used to populate the request object before the data is augmented by the prewrap handlers. p.options = utils.clone(p.query); // Clone the data object // Prevent this script overwriting the data of the incoming object. // Ensure that everytime we run an iteration the callbacks haven't removed some data p.data = utils.clone(data); // URL Mapping // Is there a map for the given URL? var actions = o[{'delete': 'del'}[p.method] || p.method] || {}; // Extrapolate the QueryString // Provide a clean path // Move the querystring into the data if (p.method === 'get') { var query = url.split(/[\?#]/)[1]; if (query) { utils.extend(p.query, utils.param(query)); // Remove the query part from the URL url = url.replace(/\?.*?(#|$)/, '$1'); } } // Is the hash fragment defined if ((m = url.match(/#(.+)/, ''))) { url = url.split('#')[0]; p.path = m[1]; } else if (url in actions) { p.path = url; url = actions[url]; } else if ('default' in actions) { url = actions['default']; } // Redirect Handler // This defines for the Form+Iframe+Hash hack where to return the results too. p.redirect_uri = _this.settings.redirect_uri; // Define FormatHandler // The request can be procesed in a multitude of ways // Here's the options - depending on the browser and endpoint p.xhr = o.xhr; p.jsonp = o.jsonp; p.form = o.form; // Make request if (typeof (url) === 'function') { // Does self have its own callback? url(p, getPath); } else { // Else the URL is a string getPath(url); } return promise.proxy; // If url needs a base // Wrap everything in function getPath(url) { // Format the string if it needs it url = url.replace(/\@\{([a-z\_\-]+)(\|.*?)?\}/gi, function(m, key, defaults) { var val = defaults ? defaults.replace(/^\|/, '') : ''; if (key in p.query) { val = p.query[key]; delete p.query[key]; } else if (p.data && key in p.data) { val = p.data[key]; delete p.data[key]; } else if (!defaults) { promise.reject(error('missing_attribute', 'The attribute ' + key + ' is missing from the request')); } return val; }); // Add base if (!url.match(/^https?:\/\//)) { url = o.base + url; } // Define the request URL p.url = url; // Make the HTTP request with the curated request object // CALLBACK HANDLER // @ response object // @ statusCode integer if available utils.request(p, function(r, headers) { // Is this a raw response? if (!p.formatResponse) { // Bad request? error statusCode or otherwise contains an error response vis JSONP? if (typeof headers === 'object' ? (headers.statusCode >= 400) : (typeof r === 'object' && 'error' in r)) { promise.reject(r); } else { promise.fulfill(r); } return; } // Should this be an object if (r === true) { r = {success:true}; } else if (!r) { r = {}; } // The delete callback needs a better response if (p.method === 'delete') { r = (!r || utils.isEmpty(r)) ? {success:true} : r; } // FORMAT RESPONSE? // Does self request have a corresponding formatter if (o.wrap && ((p.path in o.wrap) || ('default' in o.wrap))) { var wrap = (p.path in o.wrap ? p.path : 'default'); var time = (new Date()).getTime(); // FORMAT RESPONSE var b = o.wrap[wrap](r, headers, p); // Has the response been utterly overwritten? // Typically self augments the existing object.. but for those rare occassions if (b) { r = b; } } // Is there a next_page defined in the response? if (r && 'paging' in r && r.paging.next) { // Add the relative path if it is missing from the paging/next path if (r.paging.next[0] === '?') { r.paging.next = p.path + r.paging.next; } // The relative path has been defined, lets markup the handler in the HashFragment else { r.paging.next += '#' + p.path; } } // Dispatch to listeners // Emit events which pertain to the formatted response if (!r || 'error' in r) { promise.reject(r); } else { promise.fulfill(r); } }); } }; // API utilities hello.utils.extend(hello.utils, { // Make an HTTP request request: function(p, callback) { var _this = this; var error = _this.error; // This has to go through a POST request if (!_this.isEmpty(p.data) && !('FileList' in window) && _this.hasBinary(p.data)) { // Disable XHR and JSONP p.xhr = false; p.jsonp = false; } // Check if the browser and service support CORS var cors = this.request_cors(function() { // If it does then run this... return ((p.xhr === undefined) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query)))); }); if (cors) { formatUrl(p, function(url) { var x = _this.xhr(p.method, url, p.headers, p.data, callback); x.onprogress = p.onprogress || null; // Windows Phone does not support xhr.upload, see #74 // Feature detect if (x.upload && p.onuploadprogress) { x.upload.onprogress = p.onuploadprogress; } }); return; } // Clone the query object // Each request modifies the query object and needs to be tared after each one. var _query = p.query; p.query = _this.clone(p.query); // Assign a new callbackID p.callbackID = _this.globalEvent(); // JSONP if (p.jsonp !== false) { // Clone the query object p.query.callback = p.callbackID; // If the JSONP is a function then run it if (typeof (p.jsonp) === 'function') { p.jsonp(p, p.query); } // Lets use JSONP if the method is 'get' if (p.method === 'get') { formatUrl(p, function(url) { _this.jsonp(url, callback, p.callbackID, p.timeout); }); return; } else { // It's not compatible reset query p.query = _query; } } // Otherwise we're on to the old school, iframe hacks and JSONP if (p.form !== false) { // Add some additional query parameters to the URL // We're pretty stuffed if the endpoint doesn't like these p.query.redirect_uri = p.redirect_uri; p.query.state = JSON.stringify({callback:p.callbackID}); var opts; if (typeof (p.form) === 'function') { // Format the request opts = p.form(p, p.query); } if (p.method === 'post' && opts !== false) { formatUrl(p, function(url) { _this.post(url, p.data, opts, callback, p.callbackID, p.timeout); }); return; } } // None of the methods were successful throw an error callback(error('invalid_request', 'There was no mechanism for handling this request')); return; // Format URL // Constructs the request URL, optionally wraps the URL through a call to a proxy server // Returns the formatted URL function formatUrl(p, callback) { // Are we signing the request? var sign; // OAuth1 // Remove the token from the query before signing if (p.authResponse && p.authResponse.oauth && parseInt(p.authResponse.oauth.version, 10) === 1) { // OAUTH SIGNING PROXY sign = p.query.access_token; // Remove the access_token delete p.query.access_token; // Enfore use of Proxy p.proxy = true; } // POST body to querystring if (p.data && (p.method === 'get' || p.method === 'delete')) { // Attach the p.data to the querystring. _this.extend(p.query, p.data); p.data = null; } // Construct the path var path = _this.qs(p.url, p.query); // Proxy the request through a server // Used for signing OAuth1 // And circumventing services without Access-Control Headers if (p.proxy) { // Use the proxy as a path path = _this.qs(p.oauth_proxy, { path: path, access_token: sign || '', // This will prompt the request to be signed as though it is OAuth1 then: p.proxy_response_type || (p.method.toLowerCase() === 'get' ? 'redirect' : 'proxy'), method: p.method.toLowerCase(), suppress_response_codes: true }); } callback(path); } }, // Test whether the browser supports the CORS response request_cors: function(callback) { return 'withCredentials' in new XMLHttpRequest() && callback(); }, // Return the type of DOM object domInstance: function(type, data) { var test = 'HTML' + (type || '').replace( /^[a-z]/, function(m) { return m.toUpperCase(); } ) + 'Element'; if (!data) { return false; } if (window[test]) { return data instanceof window[test]; } else if (window.Element) { return data instanceof window.Element && (!type || (data.tagName && data.tagName.toLowerCase() === type)); } else { return (!(data instanceof Object || data instanceof Array || data instanceof String || data instanceof Number) && data.tagName && data.tagName.toLowerCase() === type); } }, // Create a clone of an object clone: function(obj) { // Does not clone DOM elements, nor Binary data, e.g. Blobs, Filelists if (obj === null || typeof (obj) !== 'object' || obj instanceof Date || 'nodeName' in obj || this.isBinary(obj) || (typeof FormData === 'function' && obj instanceof FormData)) { return obj; } if (Array.isArray(obj)) { // Clone each item in the array return obj.map(this.clone.bind(this)); } // But does clone everything else. var clone = {}; for (var x in obj) { clone[x] = this.clone(obj[x]); } return clone; }, // XHR: uses CORS to make requests xhr: function(method, url, headers, data, callback) { var r = new XMLHttpRequest(); var error = this.error; // Binary? var binary = false; if (method === 'blob') { binary = method; method = 'GET'; } method = method.toUpperCase(); // Xhr.responseType 'json' is not supported in any of the vendors yet. r.onload = function(e) { var json = r.response; try { json = JSON.parse(r.responseText); } catch (_e) { if (r.status === 401) { json = error('access_denied', r.statusText); } } var headers = headersToJSON(r.getAllResponseHeaders()); headers.statusCode = r.status; callback(json || (method === 'GET' ? error('empty_response', 'Could not get resource') : {}), headers); }; r.onerror = function(e) { var json = r.responseText; try { json = JSON.parse(r.responseText); } catch (_e) {} callback(json || error('access_denied', 'Could not get resource')); }; var x; // Should we add the query to the URL? if (method === 'GET' || method === 'DELETE') { data = null; } else if (data && typeof (data) !== 'string' && !(data instanceof FormData) && !(data instanceof File) && !(data instanceof Blob)) { // Loop through and add formData var f = new FormData(); for (x in data) if (data.hasOwnProperty(x)) { if (data[x] instanceof HTMLInputElement) { if ('files' in data[x] && data[x].files.length > 0) { f.append(x, data[x].files[0]); } } else if (data[x] instanceof Blob) { f.append(x, data[x], data.name); } else { f.append(x, data[x]); } } data = f; } // Open the path, async r.open(method, url, true); if (binary) { if ('responseType' in r) { r.responseType = binary; } else { r.overrideMimeType('text/plain; charset=x-user-defined'); } } // Set any bespoke headers if (headers) { for (x in headers) { r.setRequestHeader(x, headers[x]); } } r.send(data); return r; // Headers are returned as a string function headersToJSON(s) { var r = {}; var reg = /([a-z\-]+):\s?(.*);?/gi; var m; while ((m = reg.exec(s))) { r[m[1]] = m[2]; } return r; } }, // JSONP // Injects a script tag into the DOM to be executed and appends a callback function to the window object // @param string/function pathFunc either a string of the URL or a callback function pathFunc(querystringhash, continueFunc); // @param function callback a function to call on completion; jsonp: function(url, callback, callbackID, timeout) { var _this = this; var error = _this.error; // Change the name of the callback var bool = 0; var head = document.getElementsByTagName('head')[0]; var operaFix; var result = error('server_error', 'server_error'); var cb = function() { if (!(bool++)) { window.setTimeout(function() { callback(result); head.removeChild(script); }, 0); } }; // Add callback to the window object callbackID = _this.globalEvent(function(json) { result = json; return true; // Mark callback as done }, callbackID); // The URL is a function for some cases and as such // Determine its value with a callback containing the new parameters of this function. url = url.replace(new RegExp('=\\?(&|$)'), '=' + callbackID + '$1'); // Build script tag var script = _this.append('script', { id: callbackID, name: callbackID, src: url, async: true, onload: cb, onerror: cb, onreadystatechange: function() { if (/loaded|complete/i.test(this.readyState)) { cb(); } } }); // Opera fix error // Problem: If an error occurs with script loading Opera fails to trigger the script.onerror handler we specified // // Fix: // By setting the request to synchronous we can trigger the error handler when all else fails. // This action will be ignored if we've already called the callback handler "cb" with a successful onload event if (window.navigator.userAgent.toLowerCase().indexOf('opera') > -1) { operaFix = _this.append('script', { text: 'document.getElementById(\'' + callbackID + '\').onerror();' }); script.async = false; } // Add timeout if (timeout) { window.setTimeout(function() { result = error('timeout', 'timeout'); cb(); }, timeout); } // TODO: add fix for IE, // However: unable recreate the bug of firing off the onreadystatechange before the script content has been executed and the value of "result" has been defined. // Inject script tag into the head element head.appendChild(script); // Append Opera Fix to run after our script if (operaFix) { head.appendChild(operaFix); } }, // Post // Send information to a remote location using the post mechanism // @param string uri path // @param object data, key value data to send // @param function callback, function to execute in response post: function(url, data, options, callback, callbackID, timeout) { var _this = this; var error = _this.error; var doc = document; // This hack needs a form var form = null; var reenableAfterSubmit = []; var newform; var i = 0; var x = null; var bool = 0; var cb = function(r) { if (!(bool++)) { callback(r); } }; // What is the name of the callback to contain // We'll also use this to name the iframe _this.globalEvent(cb, callbackID); // Build the iframe window var win; try { // IE7 hack, only lets us define the name here, not later. win = doc.createElement('