// Backbone.Declarative.Views, v4.1.2 // Copyright (c) 2014-2017 Michael Heim, Zeilenwechsel.de // Distributed under MIT license // http://github.com/hashchange/backbone.declarative.views ;( function ( root, factory ) { "use strict"; // UMD for a Backbone plugin. Supports AMD, Node.js, CommonJS and globals. // // - Code lives in the Backbone namespace. // - The module does not export a meaningful value. // - The module does not create a global. var supportsExports = typeof exports === "object" && exports && !exports.nodeType && typeof module === "object" && module && !module.nodeType; // AMD: // - Some AMD build optimizers like r.js check for condition patterns like the AMD check below, so keep it as is. // - Check for `exports` after `define` in case a build optimizer adds an `exports` object. // - The AMD spec requires the dependencies to be an array **literal** of module IDs. Don't use a variable there, // or optimizers may fail. if ( typeof define === "function" && typeof define.amd === "object" && define.amd ) { // AMD module define( [ "exports", "underscore", "backbone" ], factory ); } else if ( supportsExports ) { // Node module, CommonJS module factory( exports, require( "underscore" ), require( "backbone" ) ); } else { // Global (browser or Rhino) factory( {}, _, Backbone ); } }( this, function ( exports, _, Backbone ) { "use strict"; var originalClearCache, // for Marionette only originalConstructor = Backbone.View, templateCache = {}, instanceCacheAliases = [], enforceTemplateLoading = false, isMarionetteInitialized = false, registeredDataAttributes = { primitives: [], json: [] }, rxElDefinitionComment, rxRegisteredDataAttributes = {}, GenericError = createCustomErrorType( "Backbone.DeclarativeViews.Error" ), TemplateError = createCustomErrorType( "Backbone.DeclarativeViews.TemplateError" ), CompilerError = createCustomErrorType( "Backbone.DeclarativeViews.CompilerError" ), CustomizationError = createCustomErrorType( "Backbone.DeclarativeViews.CustomizationError" ), ConfigurationError = createCustomErrorType( "Backbone.DeclarativeViews.ConfigurationError" ), events = _.clone( Backbone.Events ), $ = Backbone.$; // // Core functionality and API // -------------------------- _.extend( Backbone.View.prototype, { tagName: function () { var data = getViewTemplateData( this ) || {}; return data.tagName || "div"; }, className: function () { var data = getViewTemplateData( this ) || {}; return data.className || undefined; }, id: function () { var data = getViewTemplateData( this ) || {}; return data.id || undefined; }, attributes: function () { var data = getViewTemplateData( this ) || {}; return data.attributes || undefined; } } ); Backbone.View = Backbone.View.extend( { constructor: function ( options ) { if ( options && options.template !== undefined ) this.template = options.template; this.declarativeViews = { meta: { viewId: _.uniqueId( "view-" ) }, getCachedTemplate: _.partial( getViewTemplateData, this ), clearCachedTemplate: _.partial( clearViewTemplateCache, this ) }; _.each( instanceCacheAliases, function ( alias ) { this[alias] = this.declarativeViews; }, this ); if ( enforceTemplateLoading ) getViewTemplateData( this, options ); originalConstructor.apply( this, arguments ); } } ); Backbone.DeclarativeViews = { getCachedTemplate: getTemplateData, clearCachedTemplate: clearCachedTemplate, clearCache: clearCache, joinMarionette: joinMarionette, Error: GenericError, TemplateError: TemplateError, CompilerError: CompilerError, CustomizationError: CustomizationError, ConfigurationError: ConfigurationError, plugins: { registerDataAttribute: _registerDataAttribute, getDataAttributes: _getDataAttributes, updateJqueryDataCache: _updateJQueryDataCache, registerCacheAlias: _registerCacheAlias, enforceTemplateLoading: _enforceTemplateLoading, tryCompileTemplate: _tryCompileTemplate, events: events }, defaults: { loadTemplate: loadTemplate }, custom: { /** @type {Function|undefined} */ loadTemplate: undefined, /** @type {Function|undefined} */ compiler: undefined }, version: "4.1.2" }; // // Initialization // -------------- _registerDataAttribute( "tag-name" ); _registerDataAttribute( "class-name" ); _registerDataAttribute( "id" ); _registerDataAttribute( "attributes", { isJSON: true } ); // // Cache management // ---------------- /** * Returns the template data associated with a template property string. Caches it in the process, or retrieves it * from the cache if already available. Returns undefined if there is no cacheable template data. * * When retrieved from the cache, a copy of the cache entry is returned. It protects the cache entry from * modification in case the data is manipulated later on. The protection also extends to the nested `attributes` * hash. The `_pluginData` property, however, must remain writable, and is returned as is. * * The template data is returned as a hash. For a list of properties, see readme. * * @param {string} templateProp template selector, or raw template HTML, identifying the cache entry * @param {Backbone.View} [view] the view which requested the template * @param {Object} [viewOptions] the options passed to the view during instantiation. For availability, * see getViewTemplateData() * @returns {CachedTemplateData|undefined} */ function getTemplateData ( templateProp, view, viewOptions ) { var data; if ( templateProp && _.isString( templateProp ) ) { data = templateCache[ templateProp ]; if ( ! data ) data = _createTemplateCache( templateProp, view, viewOptions ); if ( data.invalid ) data = undefined; if ( data ) data = _copyCacheEntry( data ); } return data; } /** * Returns the template data associated with a given view, provided that the template is set to a non-empty string. * Otherwise, it returns undefined. Manages caching behind the scenes. * * The template data is returned as a hash. For a list of properties, see readme. * * Events * ------ * * The method fires two events: * * - cacheEntry:view:process * * Fires only once per view, on first access to the template from that particular view. Fires whether or not the * template is already in the cache. * * The event handler receives a **copy** of the returned cache data. Modifications of the data are ineffective, * they don't show up anywhere outside of the handler. * * (That is by design. When setting up the el of a view, the cache is accessed several times - once for each * el-related property. The handler would be able to modify the data during the very first access, when the * `attributes` property is requested, but not for `className`, `tagName`, `id`. That behaviour can be confusing * and cause bugs which are difficult to track down. Hence data modification is actively prevented even during * first access, making the behaviour consistent.) * * But there is an exception: the _pluginData property. If the handler needs to change or store data, it can write * to the _pluginData hash. Changes to the hash are persistent. They are stored in the original cache entry and * hence show up in every subsequent cache query for that entry. * * NB: When `el`-related properties from the cache, like tagName or html, need to be manipulated on the fly, it * must be done in a handler for another event: cacheEntry:view:fetch. That handler _is_ allowed to change the * returned data. It also has access to the _pluginData created during the cacheEntry:view:process event. * * - cacheEntry:view:fetch * * Fires every time data is requested from the cache in the context of a querying view. The event fires on first * access as well. On that occasion, it is preceded by the cacheEntry:view:process event. * * The event handler receives the returned cache data and has full access to it. If the handler modifies the data, * the modifications show up in the returned result. * * However, the original cache entry is protected from modification (with the exception of the _pluginData * property, see above), so changes made by the event handler do not alter the values stored in the cache. * * The events fire only if the cache is accessed with getViewTemplateData(), ie when the cache is queried from a * view: during view instantiation, or when called with `view.declarativeViews.getCachedTemplate()`. * * The events do **not** fire when the cache is queried from the global API, even if a view is provided as an * additional argument, as in `Backbone.DeclarativeViews.getCachedTemplate( "#template", view )`. * * @param {Backbone.View} view * @param {Object} [viewOptions] the options passed to the view during instantiation. Only available when * called during view instantiation, and only if the component has been * configured to enforce template loading, with _enforceTemplateLoading() * @returns {CachedTemplateData|undefined} */ function getViewTemplateData ( view, viewOptions ) { var data, meta = view.declarativeViews.meta; if ( ! meta.processed ) { if ( view.template && _.isString( view.template ) ) { meta.originalTemplateProp = view.template; data = getTemplateData( view.template, view, viewOptions ); meta.processed = true; meta.inGlobalCache = true; if ( data ) events.trigger( "cacheEntry:view:process", _copyCacheEntry( data ), meta.originalTemplateProp, view, viewOptions ); } else { data = undefined; meta.processed = true; meta.inGlobalCache = false; } } else { data = meta.inGlobalCache ? getTemplateData( meta.originalTemplateProp, view, viewOptions ) : undefined; } if ( data ) events.trigger( "cacheEntry:view:fetch", data, meta.originalTemplateProp, view, viewOptions ); return data; } /** * Clears the cache as a whole. * * Also clears the Marionette cache (if Marionette is available). * * @param {boolean} [fromMarionette=false] internal flag to prevent circular calls to and from Marionette */ function clearCache ( fromMarionette ) { templateCache = {}; if ( ! fromMarionette && Backbone.Marionette && Backbone.Marionette.TemplateCache ) Backbone.Marionette.TemplateCache.clear(); } /** * Removes one or more cache entries. * * Arguments * --------- * * The strings identifying the cache entries can be passed in as individual arguments (prop1, prop2, ...), or as an * array. Each string must be * * - a template selector * - raw HTML of a template, if that's what the template property held when a view made use of it. * * A template selector must be identical to the one which was used when creating the cache entry, ie the selector * specified in the template property or template option of a view. Mere selector equivalence (e.g. "#template" and * "script#template") won't match the cache. * * Strings not matching a cache entry are ignored, as are non-string arguments. * * Marionette.TemplateCache * ------------------------ * * When templates are cleared here, they are removed from the Marionette template cache as well (if Marionette is * loaded). * * @param {...string|string[]} [templateProp] template selector(s), or raw template HTML, identifying the cache * entry. NB The last argument can also be an internal "fromMarionette" * flag to prevent circular calls to and from Marionette */ function clearCachedTemplate ( templateProp ) { var fromMarionette = false, args = _.toArray( arguments ), lastArg = _.last( args ); // When called from Marionette, or called recursively, the last argument is a "fromMarionette" boolean. Splice // it off before proceeding. if ( args.length && _.isBoolean( lastArg ) ) fromMarionette = args.pop(); // Handle multiple template props passed in as a varargs list, or as an array, with recursive calls for each // template property. if ( args.length > 1 ) { _.each( args, function ( singleProp ) { clearCachedTemplate( singleProp, fromMarionette ); } ); } else if ( _.isArray( templateProp ) || _.isArguments( templateProp ) ) { _.each( templateProp, function ( singleProp ) { clearCachedTemplate( singleProp, fromMarionette ); } ); } else { if ( ! templateProp ) throw new GenericError( "Missing argument: string identifying the template. The string should be a template selector or the raw HTML of a template, as provided to the template property of a view when the cache entry was created" ); // Dealing with a single templateProp argument. // // Delete the corresponding cache entry. Try to clear it from the Marionette cache as well. The // templateProp must be a string - non-string arguments are quietly ignored. if ( _.isString( templateProp ) ) { _clearCachedTemplate( templateProp ); if ( ! fromMarionette && Backbone.Marionette && Backbone.Marionette.TemplateCache ) { try { Backbone.Marionette.TemplateCache.clear( templateProp ); } catch ( err ) {} } } } } /** * Removes the template cache entry associated with a given view, provided that a cache entry exists. * * @param {Backbone.View} view */ function clearViewTemplateCache ( view ) { var meta = view.declarativeViews.meta; if ( meta.processed ) { if ( meta.inGlobalCache ) _clearCachedTemplate( meta.originalTemplateProp ); } else if ( view.template && _.isString( view.template ) ) { _clearCachedTemplate( view.template ); } } /** * Defines the default template loader. Accepts a selector string and returns the template node (usually a