linear
Version:
A simple setup micro-forum built in Node.js with Express and MongoDB.
1,647 lines (1,317 loc) • 1.31 MB
JavaScript
(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() {