UNPKG

linear

Version:

A simple setup micro-forum built in Node.js with Express and MongoDB.

1,647 lines (1,317 loc) 1.31 MB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (process,__filename){ /** vim: et:ts=4:sw=4:sts=4 * @license amdefine 1.0.1 Copyright (c) 2011-2016, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/amdefine for details */ /*jslint node: true */ /*global module, process */ 'use strict'; /** * Creates a define for node. * @param {Object} module the "module" object that is defined by Node for the * current module. * @param {Function} [requireFn]. Node's require function for the current module. * It only needs to be passed in Node versions before 0.5, when module.require * did not exist. * @returns {Function} a define function that is usable for the current node * module. */ function amdefine(module, requireFn) { 'use strict'; var defineCache = {}, loaderCache = {}, alreadyCalled = false, path = require('path'), makeRequire, stringRequire; /** * Trims the . and .. from an array of path segments. * It will keep a leading path segment if a .. will become * the first path segment, to help with module name lookups, * which act like paths, but can be remapped. But the end result, * all paths that use this function should look normalized. * NOTE: this method MODIFIES the input array. * @param {Array} ary the array of path segments. */ function trimDots(ary) { var i, part; for (i = 0; ary[i]; i+= 1) { part = ary[i]; if (part === '.') { ary.splice(i, 1); i -= 1; } else if (part === '..') { if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { //End of the line. Keep at least one non-dot //path segment at the front so it can be mapped //correctly to disk. Otherwise, there is likely //no path mapping for a path starting with '..'. //This can still fail, but catches the most reasonable //uses of .. break; } else if (i > 0) { ary.splice(i - 1, 2); i -= 2; } } } } function normalize(name, baseName) { var baseParts; //Adjust any relative paths. if (name && name.charAt(0) === '.') { //If have a base name, try to normalize against it, //otherwise, assume it is a top-level require that will //be relative to baseUrl in the end. if (baseName) { baseParts = baseName.split('/'); baseParts = baseParts.slice(0, baseParts.length - 1); baseParts = baseParts.concat(name.split('/')); trimDots(baseParts); name = baseParts.join('/'); } } return name; } /** * Create the normalize() function passed to a loader plugin's * normalize method. */ function makeNormalize(relName) { return function (name) { return normalize(name, relName); }; } function makeLoad(id) { function load(value) { loaderCache[id] = value; } load.fromText = function (id, text) { //This one is difficult because the text can/probably uses //define, and any relative paths and requires should be relative //to that id was it would be found on disk. But this would require //bootstrapping a module/require fairly deeply from node core. //Not sure how best to go about that yet. throw new Error('amdefine does not implement load.fromText'); }; return load; } makeRequire = function (systemRequire, exports, module, relId) { function amdRequire(deps, callback) { if (typeof deps === 'string') { //Synchronous, single module require('') return stringRequire(systemRequire, exports, module, deps, relId); } else { //Array of dependencies with a callback. //Convert the dependencies to modules. deps = deps.map(function (depName) { return stringRequire(systemRequire, exports, module, depName, relId); }); //Wait for next tick to call back the require call. if (callback) { process.nextTick(function () { callback.apply(null, deps); }); } } } amdRequire.toUrl = function (filePath) { if (filePath.indexOf('.') === 0) { return normalize(filePath, path.dirname(module.filename)); } else { return filePath; } }; return amdRequire; }; //Favor explicit value, passed in if the module wants to support Node 0.4. requireFn = requireFn || function req() { return module.require.apply(module, arguments); }; function runFactory(id, deps, factory) { var r, e, m, result; if (id) { e = loaderCache[id] = {}; m = { id: id, uri: __filename, exports: e }; r = makeRequire(requireFn, e, m, id); } else { //Only support one define call per file if (alreadyCalled) { throw new Error('amdefine with no module ID cannot be called more than once per file.'); } alreadyCalled = true; //Use the real variables from node //Use module.exports for exports, since //the exports in here is amdefine exports. e = module.exports; m = module; r = makeRequire(requireFn, e, m, module.id); } //If there are dependencies, they are strings, so need //to convert them to dependency values. if (deps) { deps = deps.map(function (depName) { return r(depName); }); } //Call the factory with the right dependencies. if (typeof factory === 'function') { result = factory.apply(m.exports, deps); } else { result = factory; } if (result !== undefined) { m.exports = result; if (id) { loaderCache[id] = m.exports; } } } stringRequire = function (systemRequire, exports, module, id, relId) { //Split the ID by a ! so that var index = id.indexOf('!'), originalId = id, prefix, plugin; if (index === -1) { id = normalize(id, relId); //Straight module lookup. If it is one of the special dependencies, //deal with it, otherwise, delegate to node. if (id === 'require') { return makeRequire(systemRequire, exports, module, relId); } else if (id === 'exports') { return exports; } else if (id === 'module') { return module; } else if (loaderCache.hasOwnProperty(id)) { return loaderCache[id]; } else if (defineCache[id]) { runFactory.apply(null, defineCache[id]); return loaderCache[id]; } else { if(systemRequire) { return systemRequire(originalId); } else { throw new Error('No module with ID: ' + id); } } } else { //There is a plugin in play. prefix = id.substring(0, index); id = id.substring(index + 1, id.length); plugin = stringRequire(systemRequire, exports, module, prefix, relId); if (plugin.normalize) { id = plugin.normalize(id, makeNormalize(relId)); } else { //Normalize the ID normally. id = normalize(id, relId); } if (loaderCache[id]) { return loaderCache[id]; } else { plugin.load(id, makeRequire(systemRequire, exports, module, relId), makeLoad(id), {}); return loaderCache[id]; } } }; //Create a define function specific to the module asking for amdefine. function define(id, deps, factory) { if (Array.isArray(id)) { factory = deps; deps = id; id = undefined; } else if (typeof id !== 'string') { factory = id; id = deps = undefined; } if (deps && !Array.isArray(deps)) { factory = deps; deps = undefined; } if (!deps) { deps = ['require', 'exports', 'module']; } //Set up properties for this module. If an ID, then use //internal cache. If no ID, then use the external variables //for this node module. if (id) { //Put the module in deep freeze until there is a //require call for it. defineCache[id] = [id, deps, factory]; } else { runFactory(id, deps, factory); } } //define.require, which has access to all the values in the //cache. Useful for AMD modules that all have IDs in the file, //but need to finally export a value to node based on one of those //IDs. define.require = function (id) { if (loaderCache[id]) { return loaderCache[id]; } if (defineCache[id]) { runFactory.apply(null, defineCache[id]); return loaderCache[id]; } }; define.amd = {}; return define; } module.exports = amdefine; }).call(this,require('_process'),"/node_modules/amdefine/amdefine.js") },{"_process":106,"path":105}],2:[function(require,module,exports){ // MarionetteJS (Backbone.Marionette) // ---------------------------------- // v3.1.0 // // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC. // Distributed under MIT license // // http://marionettejs.com (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('underscore'), require('backbone.radio')) : typeof define === 'function' && define.amd ? define(['backbone', 'underscore', 'backbone.radio'], factory) : (global.Marionette = global['Mn'] = factory(global.Backbone,global._,global.Backbone.Radio)); }(this, function (Backbone,_,Radio) { 'use strict'; Backbone = 'default' in Backbone ? Backbone['default'] : Backbone; _ = 'default' in _ ? _['default'] : _; Radio = 'default' in Radio ? Radio['default'] : Radio; var version = "3.1.0"; //Internal utility for creating context style global utils var proxy = function proxy(method) { return function (context) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } return method.apply(context, args); }; }; // Borrow the Backbone `extend` method so we can use it as needed var extend = Backbone.Model.extend; var deprecate = function deprecate(message, test) { if (_.isObject(message)) { message = message.prev + ' is going to be removed in the future. ' + 'Please use ' + message.next + ' instead.' + (message.url ? ' See: ' + message.url : ''); } if (!Marionette.DEV_MODE) { return; } if ((test === undefined || !test) && !deprecate._cache[message]) { deprecate._warn('Deprecation warning: ' + message); deprecate._cache[message] = true; } }; deprecate._console = typeof console !== 'undefined' ? console : {}; deprecate._warn = function () { var warn = deprecate._console.warn || deprecate._console.log || _.noop; return warn.apply(deprecate._console, arguments); }; deprecate._cache = {}; // Determine if `el` is a child of the document var isNodeAttached = function isNodeAttached(el) { return Backbone.$.contains(document.documentElement, el); }; // Merge `keys` from `options` onto `this` var mergeOptions = function mergeOptions(options, keys) { var _this = this; if (!options) { return; } _.each(keys, function (key) { var option = options[key]; if (option !== undefined) { _this[key] = option; } }); }; // Marionette.getOption // -------------------- // Retrieve an object, function or other value from the // object or its `options`, with `options` taking precedence. var getOption = function getOption(optionName) { if (!optionName) { return; } if (this.options && this.options[optionName] !== undefined) { return this.options[optionName]; } else { return this[optionName]; } }; // Marionette.normalizeMethods // ---------------------- // Pass in a mapping of events => functions or function names // and return a mapping of events => functions var normalizeMethods = function normalizeMethods(hash) { var _this = this; return _.reduce(hash, function (normalizedHash, method, name) { if (!_.isFunction(method)) { method = _this[method]; } if (method) { normalizedHash[name] = method; } return normalizedHash; }, {}); }; // split the event name on the ":" var splitter = /(^|:)(\w)/gi; // take the event section ("section1:section2:section3") // and turn it in to uppercase name onSection1Section2Section3 function getEventName(match, prefix, eventName) { return eventName.toUpperCase(); } var getOnMethodName = _.memoize(function (event) { return 'on' + event.replace(splitter, getEventName); }); // Trigger an event and/or a corresponding method name. Examples: // // `this.triggerMethod("foo")` will trigger the "foo" event and // call the "onFoo" method. // // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and // call the "onFooBar" method. function triggerMethod(event) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } // get the method name from the event name var methodName = getOnMethodName(event); var method = getOption.call(this, methodName); var result = void 0; // call the onMethodName if it exists if (_.isFunction(method)) { // pass all args, except the event name result = method.apply(this, args); } // trigger the event this.trigger.apply(this, arguments); return result; } // triggerMethodOn invokes triggerMethod on a specific context // // e.g. `Marionette.triggerMethodOn(view, 'show')` // will trigger a "show" event or invoke onShow the view. function triggerMethodOn(context) { for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } if (_.isFunction(context.triggerMethod)) { return context.triggerMethod.apply(context, args); } return triggerMethod.apply(context, args); } // Trigger method on children unless a pure Backbone.View function triggerMethodChildren(view, event, shouldTrigger) { if (!view._getImmediateChildren) { return; } _.each(view._getImmediateChildren(), function (child) { if (!shouldTrigger(child)) { return; } triggerMethodOn(child, event, child); }); } function shouldTriggerAttach(view) { return !view._isAttached; } function shouldAttach(view) { if (!shouldTriggerAttach(view)) { return false; } view._isAttached = true; return true; } function shouldTriggerDetach(view) { return view._isAttached; } function shouldDetach(view) { if (!shouldTriggerDetach(view)) { return false; } view._isAttached = false; return true; } function triggerDOMRefresh(view) { if (view._isAttached && view._isRendered) { triggerMethodOn(view, 'dom:refresh', view); } } function handleBeforeAttach() { triggerMethodChildren(this, 'before:attach', shouldTriggerAttach); } function handleAttach() { triggerMethodChildren(this, 'attach', shouldAttach); triggerDOMRefresh(this); } function handleBeforeDetach() { triggerMethodChildren(this, 'before:detach', shouldTriggerDetach); } function handleDetach() { triggerMethodChildren(this, 'detach', shouldDetach); } function handleRender() { triggerDOMRefresh(this); } // Monitor a view's state, propagating attach/detach events to children and firing dom:refresh // whenever a rendered view is attached or an attached view is rendered. function monitorViewEvents(view) { if (view._areViewEventsMonitored) { return; } view._areViewEventsMonitored = true; view.on({ 'before:attach': handleBeforeAttach, 'attach': handleAttach, 'before:detach': handleBeforeDetach, 'detach': handleDetach, 'render': handleRender }); } var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; var MarionetteError = extend.call(Error, { urlRoot: 'http://marionettejs.com/docs/v' + version + '/', constructor: function constructor(message, options) { if (_.isObject(message)) { options = message; message = options.message; } else if (!options) { options = {}; } var error = Error.call(this, message); _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); this.captureStackTrace(); if (options.url) { this.url = this.urlRoot + options.url; } }, captureStackTrace: function captureStackTrace() { if (Error.captureStackTrace) { Error.captureStackTrace(this, MarionetteError); } }, toString: function toString() { return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : ''); } }); MarionetteError.extend = extend; // Bind/unbind the event to handlers specified as a string of // handler names on the target object function bindFromStrings(target, entity, evt, methods, actionName) { var methodNames = methods.split(/\s+/); _.each(methodNames, function (methodName) { var method = target[methodName]; if (!method) { throw new MarionetteError('Method "' + methodName + '" was configured as an event handler, but does not exist.'); } target[actionName](entity, evt, method); }); } // generic looping function function iterateEvents(target, entity, bindings, actionName) { if (!entity || !bindings) { return; } // type-check bindings if (!_.isObject(bindings)) { throw new MarionetteError({ message: 'Bindings must be an object.', url: 'marionette.functions.html#marionettebindevents' }); } // iterate the bindings and bind/unbind them _.each(bindings, function (method, evt) { // allow for a list of method names as a string if (_.isString(method)) { bindFromStrings(target, entity, evt, method, actionName); return; } target[actionName](entity, evt, method); }); } function bindEvents(entity, bindings) { iterateEvents(this, entity, bindings, 'listenTo'); return this; } function unbindEvents(entity, bindings) { iterateEvents(this, entity, bindings, 'stopListening'); return this; } function iterateReplies(target, channel, bindings, actionName) { if (!channel || !bindings) { return; } // type-check bindings if (!_.isObject(bindings)) { throw new MarionetteError({ message: 'Bindings must be an object.', url: 'marionette.functions.html#marionettebindrequests' }); } var normalizedRadioRequests = normalizeMethods.call(target, bindings); channel[actionName](normalizedRadioRequests, target); } function bindRequests(channel, bindings) { iterateReplies(this, channel, bindings, 'reply'); return this; } function unbindRequests(channel, bindings) { iterateReplies(this, channel, bindings, 'stopReplying'); return this; } // Internal utility for setting options consistently across Mn var setOptions = function setOptions() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } this.options = _.extend.apply(_, [{}, _.result(this, 'options')].concat(args)); }; var CommonMixin = { // Imports the "normalizeMethods" to transform hashes of // events=>function references/names to a hash of events=>function references normalizeMethods: normalizeMethods, _setOptions: setOptions, // A handy way to merge passed-in options onto the instance mergeOptions: mergeOptions, // Enable getting options from this or this.options by name. getOption: getOption, // Enable binding view's events from another entity. bindEvents: bindEvents, // Enable unbinding view's events from another entity. unbindEvents: unbindEvents }; // MixinOptions // - channelName // - radioEvents // - radioRequests var RadioMixin = { _initRadio: function _initRadio() { var channelName = _.result(this, 'channelName'); if (!channelName) { return; } /* istanbul ignore next */ if (!Radio) { throw new MarionetteError({ name: 'BackboneRadioMissing', message: 'The dependency "backbone.radio" is missing.' }); } var channel = this._channel = Radio.channel(channelName); var radioEvents = _.result(this, 'radioEvents'); this.bindEvents(channel, radioEvents); var radioRequests = _.result(this, 'radioRequests'); this.bindRequests(channel, radioRequests); this.on('destroy', this._destroyRadio); }, _destroyRadio: function _destroyRadio() { this._channel.stopReplying(null, null, this); }, getChannel: function getChannel() { return this._channel; }, // Proxy `bindEvents` bindEvents: bindEvents, // Proxy `unbindEvents` unbindEvents: unbindEvents, // Proxy `bindRequests` bindRequests: bindRequests, // Proxy `unbindRequests` unbindRequests: unbindRequests }; var ClassOptions = ['channelName', 'radioEvents', 'radioRequests']; // A Base Class that other Classes should descend from. // Object borrows many conventions and utilities from Backbone. var MarionetteObject = function MarionetteObject(options) { this._setOptions(options); this.mergeOptions(options, ClassOptions); this.cid = _.uniqueId(this.cidPrefix); this._initRadio(); this.initialize.apply(this, arguments); }; MarionetteObject.extend = extend; // Object Methods // -------------- // Ensure it can trigger events with Backbone.Events _.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, { cidPrefix: 'mno', // for parity with Marionette.AbstractView lifecyle _isDestroyed: false, isDestroyed: function isDestroyed() { return this._isDestroyed; }, //this is a noop method intended to be overridden by classes that extend from this base initialize: function initialize() {}, destroy: function destroy() { if (this._isDestroyed) { return this; } for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } this.triggerMethod.apply(this, ['before:destroy', this].concat(args)); this._isDestroyed = true; this.triggerMethod.apply(this, ['destroy', this].concat(args)); this.stopListening(); return this; }, triggerMethod: triggerMethod }); // Manage templates stored in `<script>` blocks, // caching them for faster access. var TemplateCache = function TemplateCache(templateId) { this.templateId = templateId; }; // TemplateCache object-level methods. Manage the template // caches from these method calls instead of creating // your own TemplateCache instances _.extend(TemplateCache, { templateCaches: {}, // Get the specified template by id. Either // retrieves the cached version, or loads it // from the DOM. get: function get(templateId, options) { var cachedTemplate = this.templateCaches[templateId]; if (!cachedTemplate) { cachedTemplate = new TemplateCache(templateId); this.templateCaches[templateId] = cachedTemplate; } return cachedTemplate.load(options); }, // Clear templates from the cache. If no arguments // are specified, clears all templates: // `clear()` // // If arguments are specified, clears each of the // specified templates from the cache: // `clear("#t1", "#t2", "...")` clear: function clear() { var i = void 0; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var length = args.length; if (length > 0) { for (i = 0; i < length; i++) { delete this.templateCaches[args[i]]; } } else { this.templateCaches = {}; } } }); // TemplateCache instance methods, allowing each // template cache object to manage its own state // and know whether or not it has been loaded _.extend(TemplateCache.prototype, { // Internal method to load the template load: function load(options) { // Guard clause to prevent loading this template more than once if (this.compiledTemplate) { return this.compiledTemplate; } // Load the template and compile it var template = this.loadTemplate(this.templateId, options); this.compiledTemplate = this.compileTemplate(template, options); return this.compiledTemplate; }, // Load a template from the DOM, by default. Override // this method to provide your own template retrieval // For asynchronous loading with AMD/RequireJS, consider // using a template-loader plugin as described here: // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs loadTemplate: function loadTemplate(templateId, options) { var $template = Backbone.$(templateId); if (!$template.length) { throw new MarionetteError({ name: 'NoTemplateError', message: 'Could not find template: "' + templateId + '"' }); } return $template.html(); }, // Pre-compile the template before caching it. Override // this method if you do not need to pre-compile a template // (JST / RequireJS for example) or if you want to change // the template engine used (Handebars, etc). compileTemplate: function compileTemplate(rawTemplate, options) { return _.template(rawTemplate, options); } }); var _invoke = _.invokeMap || _.invoke; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // MixinOptions // - behaviors // Takes care of getting the behavior class // given options and a key. // If a user passes in options.behaviorClass // default to using that. // If a user passes in a Behavior Class directly, use that // Otherwise delegate the lookup to the users `behaviorsLookup` implementation. function getBehaviorClass(options, key) { if (options.behaviorClass) { return options.behaviorClass; //treat functions as a Behavior constructor } else if (_.isFunction(options)) { return options; } // behaviorsLookup can be either a flat object or a method if (_.isFunction(Marionette.Behaviors.behaviorsLookup)) { return Marionette.Behaviors.behaviorsLookup(options, key)[key]; } return Marionette.Behaviors.behaviorsLookup[key]; } // Iterate over the behaviors object, for each behavior // instantiate it and get its grouped behaviors. // This accepts a list of behaviors in either an object or array form function parseBehaviors(view, behaviors) { return _.chain(behaviors).map(function (options, key) { var BehaviorClass = getBehaviorClass(options, key); //if we're passed a class directly instead of an object var _options = options === BehaviorClass ? {} : options; var behavior = new BehaviorClass(_options, view); var nestedBehaviors = parseBehaviors(view, _.result(behavior, 'behaviors')); return [behavior].concat(nestedBehaviors); }).flatten().value(); } var BehaviorsMixin = { _initBehaviors: function _initBehaviors() { var behaviors = _.result(this, 'behaviors'); // Behaviors defined on a view can be a flat object literal // or it can be a function that returns an object. this._behaviors = _.isObject(behaviors) ? parseBehaviors(this, behaviors) : {}; }, _getBehaviorTriggers: function _getBehaviorTriggers() { var triggers = _invoke(this._behaviors, 'getTriggers'); return _.extend.apply(_, [{}].concat(_toConsumableArray(triggers))); }, _getBehaviorEvents: function _getBehaviorEvents() { var events = _invoke(this._behaviors, 'getEvents'); return _.extend.apply(_, [{}].concat(_toConsumableArray(events))); }, // proxy behavior $el to the view's $el. _proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() { _invoke(this._behaviors, 'proxyViewProperties'); }, // delegate modelEvents and collectionEvents _delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() { _invoke(this._behaviors, 'delegateEntityEvents'); }, // undelegate modelEvents and collectionEvents _undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() { _invoke(this._behaviors, 'undelegateEntityEvents'); }, _destroyBehaviors: function _destroyBehaviors(args) { // Call destroy on each behavior after // destroying the view. // This unbinds event listeners // that behaviors have registered for. _invoke.apply(undefined, [this._behaviors, 'destroy'].concat(_toConsumableArray(args))); }, _bindBehaviorUIElements: function _bindBehaviorUIElements() { _invoke(this._behaviors, 'bindUIElements'); }, _unbindBehaviorUIElements: function _unbindBehaviorUIElements() { _invoke(this._behaviors, 'unbindUIElements'); }, _triggerEventOnBehaviors: function _triggerEventOnBehaviors() { var behaviors = this._behaviors; // Use good ol' for as this is a very hot function for (var i = 0, length = behaviors && behaviors.length; i < length; i++) { triggerMethod.apply(behaviors[i], arguments); } } }; // MixinOptions // - collectionEvents // - modelEvents var DelegateEntityEventsMixin = { // Handle `modelEvents`, and `collectionEvents` configuration _delegateEntityEvents: function _delegateEntityEvents(model, collection) { this._undelegateEntityEvents(model, collection); var modelEvents = _.result(this, 'modelEvents'); bindEvents.call(this, model, modelEvents); var collectionEvents = _.result(this, 'collectionEvents'); bindEvents.call(this, collection, collectionEvents); }, _undelegateEntityEvents: function _undelegateEntityEvents(model, collection) { var modelEvents = _.result(this, 'modelEvents'); unbindEvents.call(this, model, modelEvents); var collectionEvents = _.result(this, 'collectionEvents'); unbindEvents.call(this, collection, collectionEvents); } }; // Borrow event splitter from Backbone var delegateEventSplitter = /^(\S+)\s*(.*)$/; function uniqueName(eventName, selector) { return [eventName + _.uniqueId('.evt'), selector].join(' '); } // Set event name to be namespaced using a unique index // to generate a non colliding event namespace // http://api.jquery.com/event.namespace/ var getUniqueEventName = function getUniqueEventName(eventName) { var match = eventName.match(delegateEventSplitter); return uniqueName(match[1], match[2]); }; // Internal method to create an event handler for a given `triggerDef` like // 'click:foo' function buildViewTrigger(view, triggerDef) { if (_.isString(triggerDef)) { triggerDef = { event: triggerDef }; } var eventName = triggerDef.event; var shouldPreventDefault = triggerDef.preventDefault !== false; var shouldStopPropagation = triggerDef.stopPropagation !== false; return function (e) { if (shouldPreventDefault) { e.preventDefault(); } if (shouldStopPropagation) { e.stopPropagation(); } view.triggerMethod(eventName, view); }; } var TriggersMixin = { // Configure `triggers` to forward DOM events to view // events. `triggers: {"click .foo": "do:foo"}` _getViewTriggers: function _getViewTriggers(view, triggers) { // Configure the triggers, prevent default // action and stop propagation of DOM events return _.reduce(triggers, function (events, value, key) { key = getUniqueEventName(key); events[key] = buildViewTrigger(view, value); return events; }, {}); } }; // allows for the use of the @ui. syntax within // a given key for triggers and events // swaps the @ui with the associated selector. // Returns a new, non-mutated, parsed events hash. var _normalizeUIKeys = function _normalizeUIKeys(hash, ui) { return _.reduce(hash, function (memo, val, key) { var normalizedKey = _normalizeUIString(key, ui); memo[normalizedKey] = val; return memo; }, {}); }; // utility method for parsing @ui. syntax strings // into associated selector var _normalizeUIString = function _normalizeUIString(uiString, ui) { return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function (r) { return ui[r.slice(4)]; }); }; // allows for the use of the @ui. syntax within // a given value for regions // swaps the @ui with the associated selector var _normalizeUIValues = function _normalizeUIValues(hash, ui, properties) { _.each(hash, function (val, key) { if (_.isString(val)) { hash[key] = _normalizeUIString(val, ui); } else if (_.isObject(val) && _.isArray(properties)) { _.extend(val, _normalizeUIValues(_.pick(val, properties), ui)); /* Value is an object, and we got an array of embedded property names to normalize. */ _.each(properties, function (property) { var propertyVal = val[property]; if (_.isString(propertyVal)) { val[property] = _normalizeUIString(propertyVal, ui); } }); } }); return hash; }; var UIMixin = { // normalize the keys of passed hash with the views `ui` selectors. // `{"@ui.foo": "bar"}` normalizeUIKeys: function normalizeUIKeys(hash) { var uiBindings = this._getUIBindings(); return _normalizeUIKeys(hash, uiBindings); }, // normalize the passed string with the views `ui` selectors. // `"@ui.bar"` normalizeUIString: function normalizeUIString(uiString) { var uiBindings = this._getUIBindings(); return _normalizeUIString(uiString, uiBindings); }, // normalize the values of passed hash with the views `ui` selectors. // `{foo: "@ui.bar"}` normalizeUIValues: function normalizeUIValues(hash, properties) { var uiBindings = this._getUIBindings(); return _normalizeUIValues(hash, uiBindings, properties); }, _getUIBindings: function _getUIBindings() { var uiBindings = _.result(this, '_uiBindings'); var ui = _.result(this, 'ui'); return uiBindings || ui; }, // This method binds the elements specified in the "ui" hash inside the view's code with // the associated jQuery selectors. _bindUIElements: function _bindUIElements() { var _this = this; if (!this.ui) { return; } // store the ui hash in _uiBindings so they can be reset later // and so re-rendering the view will be able to find the bindings if (!this._uiBindings) { this._uiBindings = this.ui; } // get the bindings result, as a function or otherwise var bindings = _.result(this, '_uiBindings'); // empty the ui so we don't have anything to start with this._ui = {}; // bind each of the selectors _.each(bindings, function (selector, key) { _this._ui[key] = _this.$(selector); }); this.ui = this._ui; }, _unbindUIElements: function _unbindUIElements() { var _this2 = this; if (!this.ui || !this._uiBindings) { return; } // delete all of the existing ui bindings _.each(this.ui, function ($el, name) { delete _this2.ui[name]; }); // reset the ui element to the original bindings configuration this.ui = this._uiBindings; delete this._uiBindings; delete this._ui; }, _getUI: function _getUI(name) { return this._ui[name]; } }; // MixinOptions // - behaviors // - childViewEventPrefix // - childViewEvents // - childViewTriggers // - collectionEvents // - modelEvents // - triggers // - ui var ViewMixin = { supportsRenderLifecycle: true, supportsDestroyLifecycle: true, _isDestroyed: false, isDestroyed: function isDestroyed() { return !!this._isDestroyed; }, _isRendered: false, isRendered: function isRendered() { return !!this._isRendered; }, _isAttached: false, isAttached: function isAttached() { return !!this._isAttached; }, // Overriding Backbone.View's `delegateEvents` to handle // `events` and `triggers` delegateEvents: function delegateEvents(eventsArg) { this._proxyBehaviorViewProperties(); this._buildEventProxies(); var viewEvents = this._getEvents(eventsArg); if (typeof eventsArg === 'undefined') { this.events = viewEvents; } var combinedEvents = _.extend({}, this._getBehaviorEvents(), viewEvents, this._getBehaviorTriggers(), this.getTriggers()); Backbone.View.prototype.delegateEvents.call(this, combinedEvents); return this; }, _getEvents: function _getEvents(eventsArg) { var events = eventsArg || this.events; if (_.isFunction(events)) { return this.normalizeUIKeys(events.call(this)); } return this.normalizeUIKeys(events); }, // Configure `triggers` to forward DOM events to view // events. `triggers: {"click .foo": "do:foo"}` getTriggers: function getTriggers() { if (!this.triggers) { return; } // Allow `triggers` to be configured as a function var triggers = this.normalizeUIKeys(_.result(this, 'triggers')); // Configure the triggers, prevent default // action and stop propagation of DOM events return this._getViewTriggers(this, triggers); }, // Handle `modelEvents`, and `collectionEvents` configuration delegateEntityEvents: function delegateEntityEvents() { this._delegateEntityEvents(this.model, this.collection); // bind each behaviors model and collection events this._delegateBehaviorEntityEvents(); return this; }, // Handle unbinding `modelEvents`, and `collectionEvents` configuration undelegateEntityEvents: function undelegateEntityEvents() { this._undelegateEntityEvents(this.model, this.collection); // unbind each behaviors model and collection events this._undelegateBehaviorEntityEvents(); return this; }, // Internal helper method to verify whether the view hasn't been destroyed _ensureViewIsIntact: function _ensureViewIsIntact() { if (this._isDestroyed) { throw new MarionetteError({ name: 'ViewDestroyedError', message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.' }); } }, // Handle destroying the view and its children. destroy: function destroy() { if (this._isDestroyed) { return this; } var shouldTriggerDetach = !!this._isAttached; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } this.triggerMethod.apply(this, ['before:destroy', this].concat(args)); if (shouldTriggerDetach) { this.triggerMethod('before:detach', this); } // unbind UI elements this.unbindUIElements(); // remove the view from the DOM // https://github.com/jashkenas/backbone/blob/1.2.3/backbone.js#L1235 this._removeElement(); if (shouldTriggerDetach) { this._isAttached = false; this.triggerMethod('detach', this); } // remove children after the remove to prevent extra paints this._removeChildren(); this._destroyBehaviors(args); this._isDestroyed = true; this._isRendered = false; this.triggerMethod.apply(this, ['destroy', this].concat(args)); this.stopListening(); return this; }, bindUIElements: function bindUIElements() { this._bindUIElements(); this._bindBehaviorUIElements(); return this; }, // This method unbinds the elements specified in the "ui" hash unbindUIElements: function unbindUIElements() { this._unbindUIElements(); this._unbindBehaviorUIElements(); return this; }, getUI: function getUI(name) { this._ensureViewIsIntact(); return this._getUI(name); }, // used as the prefix for child view events // that are forwarded through the layoutview childViewEventPrefix: 'childview', // import the `triggerMethod` to trigger events with corresponding // methods if the method exists triggerMethod: function triggerMethod$$() { var ret = triggerMethod.apply(this, arguments); this._triggerEventOnBehaviors.apply(this, arguments); this._triggerEventOnParentLayout.apply(this, arguments); return ret; }, // Cache `childViewEvents` and `childViewTriggers` _buildEventProxies: function _buildEventProxies() { this._childViewEvents = _.result(this, 'childViewEvents'); this._childViewTriggers = _.result(this, 'childViewTriggers'); }, _triggerEventOnParentLayout: function _triggerEventOnParentLayout() { var layoutView = this._parentView(); if (!layoutView) { return; } layoutView._childViewEventHandler.apply(layoutView, arguments); }, // Walk the _parent tree until we find a view (if one exists). // Returns the parent view hierarchically closest to this view. _parentView: function _parentView() { var parent = this._parent; while (parent) { if (parent instanceof View) { return parent; } parent = parent._parent; } }, _childViewEventHandler: function _childViewEventHandler(eventName) { var childViewEvents = this.normalizeMethods(this._childViewEvents); // call collectionView childViewEvent if defined for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } if (typeof childViewEvents !== 'undefined' && _.isFunction(childViewEvents[eventName])) { childViewEvents[eventName].apply(this, args); } // use the parent view's proxyEvent handlers var childViewTriggers = this._childViewTriggers; // Call the event with the proxy name on the parent layout if (childViewTriggers && _.isString(childViewTriggers[eventName])) { this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args)); } var prefix = _.result(this, 'childViewEventPrefix'); if (prefix !== false) { var childEventName = prefix + ':' + eventName; this.triggerMethod.apply(this, [childEventName].concat(args)); } } }; _.extend(ViewMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin); function destroyBackboneView(view) { if (!view.supportsDestroyLifecycle) { triggerMethodOn(view, 'before:destroy', view); } var shouldTriggerDetach = !!view._isAttached; if (shouldTriggerDetach) { triggerMethodOn(view, 'before:detach', view); } view.remove(); if (shouldTriggerDetach) { view._isAttached = false; triggerMethodOn(view, 'detach', view); } view._isDestroyed = true; if (!view.supportsDestroyLifecycle) { triggerMethodOn(view, 'destroy', view); } } var ClassOptions$2 = ['allowMissingEl', 'parentEl', 'replaceElement']; var Region = MarionetteObject.extend({ cidPrefix: 'mnr', replaceElement: false, _isReplaced: false, constructor: function constructor(options) { this._setOptions(options); this.mergeOptions(options, ClassOptions$2); // getOption necessary because options.el may be passed as undefined this._initEl = this.el = this.getOption('el'); // Handle when this.el is passed in as a $ wrapped element. this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el; if (!this.el) { throw new MarionetteError({ name: 'NoElError', message: 'An "el" must be specified for a region.' }); } this.$el = this.getEl(this.el); MarionetteObject.call(this, options); }, // Displays a backbone view instance inside of the region. Handles calling the `render` // method for you. Reads content directly from the `el` attribute. The `preventDestroy` // option can be used to prevent a view from the old view being destroyed on show. show: function show(view, options) { if (!this._ensureElement(options)) { return; } this._ensureView(view); if (view === this.currentView) { return this; } this.triggerMethod('before:show', this, view, options); monitorViewEvents(view); this.empty(options); // We need to listen for if a view is destroyed in a way other than through the region. // If this happens we need to remove the reference to the currentView since once a view // has been destroyed we can not reuse it. view.on('destroy', this._empty, this); // Make this region the view's parent. // It's important that this parent binding happens before rendering so that any events // the child may trigger during render can also be triggered on the child's ancestor views. view._parent = this; this._renderView(view); this._attachView(view, options); this.triggerMethod('show', this, view, options); return this; }, _renderView: function _renderView(view) { if (view._isRendered) { return; } if (!view.supportsRenderLifecycle) { triggerMethodOn(view, 'before:render', view); } view.render(); if (!view.supportsRenderLifecycle) { view._isRendered = true; triggerMethodOn(view, 'render', view); } }, _attachView: function _attachView(view) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var shouldTriggerAttach = !view._isAttached && isNodeAttached(this.el); var shouldReplaceEl = typeof options.replaceElement === 'undefined' ? !!_.result(this, 'replaceElement') : !!options.replaceElement; if (shouldTriggerAttach) { triggerMethodOn(view, 'before:attach', view); } if (shouldReplaceEl) { this._replaceEl(view); } else { this.attachHtml(view); } if (shouldTriggerAttach) { view._isAttached = true; triggerMethodOn(view, 'attach', view); } this.currentView = view; }, _ensureElement: function _ensureElement() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!_.isObject(this.el)) { this.$el = this.getEl(this.el); this.el = this.$el[0]; } if (!this.$el || this.$el.length === 0) { var allowMissingEl = typeof options.allowMissingEl === 'undefined' ? !!_.result(this, 'allowMissingEl') : !!options.allowMissingEl; if (allowMissingEl) { return false; } else { throw new MarionetteError('An "el" must exist in DOM for this region ' + this.cid); } } return true; }, _ensureView: function _ensureView(view) { if (!view) { throw new MarionetteError({ name: 'ViewNotValid', message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.' }); } if (view._isDestroyed) { throw new MarionetteError({ name: 'ViewDestroyedError', message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.' }); } }, // Override this method to change how the region finds the DOM element that it manages. Return // a jQuery selector object scoped to a provided parent el or the document if none exists. getEl: function getEl(el) { return Backbone.$(el, _.result(this, 'parentEl')); }, _replaceEl: function _replaceEl(view) { // always restore the el to ensure the regions el is present before replacing this._restoreEl(); var parent = this.el.parentNode; parent.replaceChild(view.el, this.el); this._isReplaced = true; }, // Restore the region's element in the DOM. _restoreEl: function _restoreEl() {