| /*! |
| * backbone.layoutmanager.js v0.9.5 |
| * Copyright 2013, Tim Branyen (@tbranyen) |
| * backbone.layoutmanager.js may be freely distributed under the MIT license. |
| */ |
| (function(window, factory) { |
| "use strict"; |
| var Backbone = window.Backbone; |
| |
| // AMD. Register as an anonymous module. Wrap in function so we have access |
| // to root via `this`. |
| if (typeof define === "function" && define.amd) { |
| return define(["backbone", "underscore", "jquery"], function() { |
| return factory.apply(window, arguments); |
| }); |
| } |
| |
| // Browser globals. |
| Backbone.Layout = factory.call(window, Backbone, window._, Backbone.$); |
| }(typeof global === "object" ? global : this, function (Backbone, _, $) { |
| "use strict"; |
| |
| // Create a reference to the global object. In browsers, it will map to the |
| // `window` object; in Node, it will be `global`. |
| var window = this; |
| |
| // Maintain reference to the original constructor. |
| var ViewConstructor = Backbone.View; |
| |
| // Cache these methods for performance. |
| var aPush = Array.prototype.push; |
| var aConcat = Array.prototype.concat; |
| var aSplice = Array.prototype.splice; |
| var trim = String.prototype.trim ? |
| _.bind(String.prototype.trim.call, String.prototype.trim) : |
| $.trim; |
| |
| // LayoutManager is a wrapper around a `Backbone.View`. |
| // Backbone.View.extend takes options (protoProps, staticProps) |
| var LayoutManager = Backbone.View.extend({ |
| _render: function() { |
| // Keep the view consistent between callbacks and deferreds. |
| var view = this; |
| // Shorthand the manager. |
| var manager = view.__manager__; |
| // Cache these properties. |
| var beforeRender = view.beforeRender; |
| // Create a deferred instead of going off |
| var def = view.deferred(); |
| |
| // Ensure all nested Views are properly scrubbed if re-rendering. |
| if (view.hasRendered) { |
| view._removeViews(); |
| } |
| |
| // This continues the render flow after `beforeRender` has completed. |
| manager.callback = function() { |
| // Clean up asynchronous manager properties. |
| delete manager.isAsync; |
| delete manager.callback; |
| |
| // Always emit a beforeRender event. |
| view.trigger("beforeRender", view); |
| |
| // Render! |
| view._viewRender(manager).render().then(function() { |
| // Complete this deferred once resolved. |
| def.resolve(); |
| }); |
| }; |
| |
| // If a beforeRender function is defined, call it. |
| if (beforeRender) { |
| beforeRender.call(view, view); |
| } |
| |
| if (!manager.isAsync) { |
| manager.callback(); |
| } |
| |
| // Return this intermediary promise. |
| return def.promise(); |
| }, |
| |
| // This function is responsible for pairing the rendered template into the |
| // DOM element. |
| _applyTemplate: function(rendered, manager, def) { |
| // Actually put the rendered contents into the element. |
| if (_.isString(rendered)) { |
| // If no container is specified, we must replace the content. |
| if (manager.noel) { |
| rendered = $.parseHTML(rendered, true); |
| |
| // Remove extra root elements. |
| this.$el.slice(1).remove(); |
| |
| // Swap out the View on the first top level element to avoid |
| // duplication. |
| this.$el.replaceWith(rendered); |
| |
| // Don't delegate events here - we'll do that in resolve() |
| this.setElement(rendered, false); |
| } else { |
| this.html(this.$el, rendered); |
| } |
| } |
| |
| // Resolve only after fetch and render have succeeded. |
| def.resolveWith(this, [this]); |
| }, |
| |
| // Creates a deferred and returns a function to call when finished. |
| // This gets passed to all _render methods. The `root` value here is passed |
| // from the `manage(this).render()` line in the `_render` function |
| _viewRender: function(manager) { |
| var url, contents, def; |
| var root = this; |
| |
| // Once the template is successfully fetched, use its contents to proceed. |
| // Context argument is first, since it is bound for partial application |
| // reasons. |
| function done(context, template) { |
| // Store the rendered template someplace so it can be re-assignable. |
| var rendered; |
| |
| // Trigger this once the render method has completed. |
| manager.callback = function(rendered) { |
| // Clean up asynchronous manager properties. |
| delete manager.isAsync; |
| delete manager.callback; |
| |
| root._applyTemplate(rendered, manager, def); |
| }; |
| |
| // Ensure the cache is up-to-date. |
| LayoutManager.cache(url, template); |
| |
| // Render the View into the el property. |
| if (template) { |
| rendered = root.renderTemplate.call(root, template, context); |
| } |
| |
| // If the function was synchronous, continue execution. |
| if (!manager.isAsync) { |
| root._applyTemplate(rendered, manager, def); |
| } |
| } |
| |
| return { |
| // This `render` function is what gets called inside of the View render, |
| // when `manage(this).render` is called. Returns a promise that can be |
| // used to know when the element has been rendered into its parent. |
| render: function() { |
| var context = root.serialize; |
| var template = root.template; |
| |
| // Create a deferred specifically for fetching. |
| def = root.deferred(); |
| |
| // If data is a function, immediately call it. |
| if (_.isFunction(context)) { |
| context = context.call(root); |
| } |
| |
| // Set the internal callback to trigger once the asynchronous or |
| // synchronous behavior has completed. |
| manager.callback = function(contents) { |
| // Clean up asynchronous manager properties. |
| delete manager.isAsync; |
| delete manager.callback; |
| |
| done(context, contents); |
| }; |
| |
| // Set the url to the prefix + the view's template property. |
| if (typeof template === "string") { |
| url = root.prefix + template; |
| } |
| |
| // Check if contents are already cached and if they are, simply process |
| // the template with the correct data. |
| if (contents = LayoutManager.cache(url)) { |
| done(context, contents, url); |
| |
| return def; |
| } |
| |
| // Fetch layout and template contents. |
| if (typeof template === "string") { |
| contents = root.fetchTemplate.call(root, root.prefix + |
| template); |
| // If the template is already a function, simply call it. |
| } else if (typeof template === "function") { |
| contents = template; |
| // If its not a string and not undefined, pass the value to `fetch`. |
| } else if (template != null) { |
| contents = root.fetchTemplate.call(root, template); |
| } |
| |
| // If the function was synchronous, continue execution. |
| if (!manager.isAsync) { |
| done(context, contents); |
| } |
| |
| return def; |
| } |
| }; |
| }, |
| |
| // This named function allows for significantly easier debugging. |
| constructor: function Layout(options) { |
| // Grant this View superpowers. |
| this.manage = true; |
| |
| // Give this View access to all passed options as instance properties. |
| _.extend(this, options); |
| |
| // Have Backbone set up the rest of this View. |
| Backbone.View.apply(this, arguments); |
| }, |
| |
| // This method is used within specific methods to indicate that they should |
| // be treated as asynchronous. This method should only be used within the |
| // render chain, otherwise unexpected behavior may occur. |
| async: function() { |
| var manager = this.__manager__; |
| |
| // Set this View's action to be asynchronous. |
| manager.isAsync = true; |
| |
| // Return the callback. |
| return manager.callback; |
| }, |
| |
| promise: function() { |
| return this.__manager__.renderDeferred.promise(); |
| }, |
| |
| // Sometimes it's desirable to only render the child views under the parent. |
| // This is typical for a layout that does not change. This method will |
| // iterate over the child Views and aggregate all child render promises and |
| // return the parent View. The internal `promise()` method will return the |
| // aggregate promise that resolves once all children have completed their |
| // render. |
| renderViews: function() { |
| var root = this; |
| var manager = root.__manager__; |
| var newDeferred = root.deferred(); |
| |
| // Collect all promises from rendering the child views and wait till they |
| // all complete. |
| var promises = root.getViews().map(function(view) { |
| return view.render().__manager__.renderDeferred; |
| }).value(); |
| |
| // Simulate a parent render to remain consistent. |
| manager.renderDeferred = newDeferred.promise(); |
| |
| // Once all child views have completed rendering, resolve parent deferred |
| // with the correct context. |
| root.when(promises).then(function() { |
| newDeferred.resolveWith(root, [root]); |
| }); |
| |
| // Allow this method to be chained. |
| return root; |
| }, |
| |
| // Shorthand to `setView` function with the `insert` flag set. |
| insertView: function(selector, view) { |
| // If the `view` argument exists, then a selector was passed in. This code |
| // path will forward the selector on to `setView`. |
| if (view) { |
| return this.setView(selector, view, true); |
| } |
| |
| // If no `view` argument is defined, then assume the first argument is the |
| // View, somewhat now confusingly named `selector`. |
| return this.setView(selector, true); |
| }, |
| |
| // Iterate over an object and ensure every value is wrapped in an array to |
| // ensure they will be inserted, then pass that object to `setViews`. |
| insertViews: function(views) { |
| // If an array of views was passed it should be inserted into the |
| // root view. Much like calling insertView without a selector. |
| if (_.isArray(views)) { |
| return this.setViews({ "": views }); |
| } |
| |
| _.each(views, function(view, selector) { |
| views[selector] = _.isArray(view) ? view : [view]; |
| }); |
| |
| return this.setViews(views); |
| }, |
| |
| // Returns the View that matches the `getViews` filter function. |
| getView: function(fn) { |
| // If `getView` is invoked with undefined as the first argument, then the |
| // second argument will be used instead. This is to allow |
| // `getViews(undefined, fn)` to work as `getViews(fn)`. Useful for when |
| // you are allowing an optional selector. |
| if (fn == null) { |
| fn = arguments[1]; |
| } |
| |
| return this.getViews(fn).first().value(); |
| }, |
| |
| // Provide a filter function to get a flattened array of all the subviews. |
| // If the filter function is omitted it will return all subviews. If a |
| // String is passed instead, it will return the Views for that selector. |
| getViews: function(fn) { |
| var views; |
| |
| // If the filter argument is a String, then return a chained Version of the |
| // elements. The value at the specified filter may be undefined, a single |
| // view, or an array of views; in all cases, chain on a flat array. |
| if (typeof fn === "string") { |
| fn = this.sections[fn] || fn; |
| views = this.views[fn] || []; |
| |
| // If Views is undefined you are concatenating an `undefined` to an array |
| // resulting in a value being returned. Defaulting to an array prevents |
| // this. |
| //return _.chain([].concat(views || [])); |
| return _.chain([].concat(views)); |
| } |
| |
| // Generate an array of all top level (no deeply nested) Views flattened. |
| views = _.chain(this.views).map(function(view) { |
| return _.isArray(view) ? view : [view]; |
| }, this).flatten(); |
| |
| // If the argument passed is an Object, then pass it to `_.where`. |
| if (typeof fn === "object") { |
| return views.where(fn); |
| } |
| |
| // If a filter function is provided, run it on all Views and return a |
| // wrapped chain. Otherwise, simply return a wrapped chain of all Views. |
| return typeof fn === "function" ? views.filter(fn) : views; |
| }, |
| |
| // Use this to remove Views, internally uses `getViews` so you can pass the |
| // same argument here as you would to that method. |
| removeView: function(fn) { |
| // Allow an optional selector or function to find the right model and |
| // remove nested Views based off the results of the selector or filter. |
| return this.getViews(fn).each(function(nestedView) { |
| nestedView.remove(); |
| }); |
| }, |
| |
| // This takes in a partial name and view instance and assigns them to |
| // the internal collection of views. If a view is not a LayoutManager |
| // instance, then mix in the LayoutManager prototype. This ensures |
| // all Views can be used successfully. |
| // |
| // Must definitely wrap any render method passed in or defaults to a |
| // typical render function `return layout(this).render()`. |
| setView: function(name, view, insert) { |
| var manager, selector; |
| // Parent view, the one you are setting a View on. |
| var root = this; |
| |
| // If no name was passed, use an empty string and shift all arguments. |
| if (typeof name !== "string") { |
| insert = view; |
| view = name; |
| name = ""; |
| } |
| |
| // Shorthand the `__manager__` property. |
| manager = view.__manager__; |
| |
| // If the View has not been properly set up, throw an Error message |
| // indicating that the View needs `manage: true` set. |
| if (!manager) { |
| throw new Error("The argument associated with selector '" + name + |
| "' is defined and a View. Set `manage` property to true for " + |
| "Backbone.View instances."); |
| } |
| |
| // Add reference to the parentView. |
| manager.parent = root; |
| |
| // Add reference to the placement selector used. |
| selector = manager.selector = root.sections[name] || name; |
| |
| // Code path is less complex for Views that are not being inserted. Simply |
| // remove existing Views and bail out with the assignment. |
| if (!insert) { |
| // Ensure remove is called only when swapping in a new view (when the |
| // view is the same, it does not need to be removed or cleaned up). |
| if (root.getView(name) !== view) { |
| root.removeView(name); |
| } |
| |
| // Assign to main views object and return for chainability. |
| return root.views[selector] = view; |
| } |
| |
| // Ensure this.views[selector] is an array and push this View to |
| // the end. |
| root.views[selector] = aConcat.call([], root.views[name] || [], view); |
| |
| // Put the parent view into `insert` mode. |
| root.__manager__.insert = true; |
| |
| return view; |
| }, |
| |
| // Allows the setting of multiple views instead of a single view. |
| setViews: function(views) { |
| // Iterate over all the views and use the View's view method to assign. |
| _.each(views, function(view, name) { |
| // If the view is an array put all views into insert mode. |
| if (_.isArray(view)) { |
| return _.each(view, function(view) { |
| this.insertView(name, view); |
| }, this); |
| } |
| |
| // Assign each view using the view function. |
| this.setView(name, view); |
| }, this); |
| |
| // Allow for chaining |
| return this; |
| }, |
| |
| // By default this should find all nested views and render them into |
| // the this.el and call done once all of them have successfully been |
| // resolved. |
| // |
| // This function returns a promise that can be chained to determine |
| // once all subviews and main view have been rendered into the view.el. |
| render: function() { |
| var root = this; |
| var manager = root.__manager__; |
| var parent = manager.parent; |
| var rentManager = parent && parent.__manager__; |
| var def = root.deferred(); |
| |
| // Triggered once the render has succeeded. |
| function resolve() { |
| var next; |
| |
| // Insert all subViews into the parent at once. |
| _.each(root.views, function(views, selector) { |
| // Fragments aren't used on arrays of subviews. |
| if (_.isArray(views)) { |
| root.htmlBatch(root, views, selector); |
| } |
| }); |
| |
| // If there is a parent and we weren't attached to it via the previous |
| // method (single view), attach. |
| if (parent && !manager.insertedViaFragment) { |
| if (!root.contains(parent.el, root.el)) { |
| // Apply the partial using parent's html() method. |
| parent.partial(parent.$el, root.$el, rentManager, manager); |
| } |
| } |
| |
| // Ensure events are always correctly bound after rendering. |
| root.delegateEvents(); |
| |
| // Set this View as successfully rendered. |
| root.hasRendered = true; |
| |
| // Only process the queue if it exists. |
| if (next = manager.queue.shift()) { |
| // Ensure that the next render is only called after all other |
| // `done` handlers have completed. This will prevent `render` |
| // callbacks from firing out of order. |
| next(); |
| } else { |
| // Once the queue is depleted, remove it, the render process has |
| // completed. |
| delete manager.queue; |
| } |
| |
| // Reusable function for triggering the afterRender callback and event |
| // and setting the hasRendered flag. |
| function completeRender() { |
| var console = window.console; |
| var afterRender = root.afterRender; |
| |
| if (afterRender) { |
| afterRender.call(root, root); |
| } |
| |
| // Always emit an afterRender event. |
| root.trigger("afterRender", root); |
| |
| // If there are multiple top level elements and `el: false` is used, |
| // display a warning message and a stack trace. |
| if (manager.noel && root.$el.length > 1) { |
| // Do not display a warning while testing or if warning suppression |
| // is enabled. |
| if (_.isFunction(console.warn) && !root.suppressWarnings) { |
| console.warn("`el: false` with multiple top level elements is " + |
| "not supported."); |
| |
| // Provide a stack trace if available to aid with debugging. |
| if (_.isFunction(console.trace)) { |
| console.trace(); |
| } |
| } |
| } |
| } |
| |
| // If the parent is currently rendering, wait until it has completed |
| // until calling the nested View's `afterRender`. |
| if (rentManager && rentManager.queue) { |
| // Wait until the parent View has finished rendering, which could be |
| // asynchronous, and trigger afterRender on this View once it has |
| // compeleted. |
| parent.once("afterRender", completeRender); |
| } else { |
| // This View and its parent have both rendered. |
| completeRender(); |
| } |
| |
| return def.resolveWith(root, [root]); |
| } |
| |
| // Actually facilitate a render. |
| function actuallyRender() { |
| |
| // The `_viewRender` method is broken out to abstract away from having |
| // too much code in `actuallyRender`. |
| root._render().done(function() { |
| // If there are no children to worry about, complete the render |
| // instantly. |
| if (!_.keys(root.views).length) { |
| return resolve(); |
| } |
| |
| // Create a list of promises to wait on until rendering is done. |
| // Since this method will run on all children as well, its sufficient |
| // for a full hierarchical. |
| var promises = _.map(root.views, function(view) { |
| var insert = _.isArray(view); |
| |
| // If items are being inserted, they will be in a non-zero length |
| // Array. |
| if (insert && view.length) { |
| // Mark each subview's manager so they don't attempt to attach by |
| // themselves. Return a single promise representing the entire |
| // render. |
| return root.when(_.map(view, function(subView) { |
| subView.__manager__.insertedViaFragment = true; |
| return subView.render().__manager__.renderDeferred; |
| })); |
| } |
| |
| // Only return the fetch deferred, resolve the main deferred after |
| // the element has been attached to it's parent. |
| return !insert ? view.render().__manager__.renderDeferred : view; |
| }); |
| |
| // Once all nested Views have been rendered, resolve this View's |
| // deferred. |
| root.when(promises).done(resolve); |
| }); |
| } |
| |
| // Another render is currently happening if there is an existing queue, so |
| // push a closure to render later into the queue. |
| if (manager.queue) { |
| aPush.call(manager.queue, actuallyRender); |
| } else { |
| manager.queue = []; |
| |
| // This the first `render`, preceeding the `queue` so render |
| // immediately. |
| actuallyRender(root, def); |
| } |
| |
| // Put the deferred inside of the `__manager__` object, since we don't want |
| // end users accessing this directly anymore in favor of the `afterRender` |
| // event. So instead of doing `render().then(...` do |
| // `render().once("afterRender", ...`. |
| root.__manager__.renderDeferred = def; |
| |
| // Return the actual View for chainability purposes. |
| return root; |
| }, |
| |
| // Ensure the cleanup function is called whenever remove is called. |
| remove: function() { |
| // Force remove itself from its parent. |
| LayoutManager._removeView(this, true); |
| |
| // Call the original remove function. |
| return this._remove.apply(this, arguments); |
| } |
| }, |
| |
| // Static Properties |
| { |
| // Clearable cache. |
| _cache: {}, |
| |
| // Remove all nested Views. |
| _removeViews: function(root, force) { |
| // Shift arguments around. |
| if (typeof root === "boolean") { |
| force = root; |
| root = this; |
| } |
| |
| // Allow removeView to be called on instances. |
| root = root || this; |
| |
| // Iterate over all of the nested View's and remove. |
| root.getViews().each(function(view) { |
| // Force doesn't care about if a View has rendered or not. |
| if (view.hasRendered || force) { |
| LayoutManager._removeView(view, force); |
| } |
| }); |
| }, |
| |
| // Remove a single nested View. |
| _removeView: function(view, force) { |
| var parentViews; |
| // Shorthand the managers for easier access. |
| var manager = view.__manager__; |
| var rentManager = manager.parent && manager.parent.__manager__; |
| // Test for keep. |
| var keep = typeof view.keep === "boolean" ? view.keep : view.options.keep; |
| |
| // In insert mode, remove views that do not have `keep` attribute set, |
| // unless the force flag is set. |
| if ((!keep && rentManager && rentManager.insert === true) || force) { |
| // Clean out the events. |
| LayoutManager.cleanViews(view); |
| |
| // Since we are removing this view, force subviews to remove |
| view._removeViews(true); |
| |
| // Remove the View completely. |
| view.$el.remove(); |
| |
| // Bail out early if no parent exists. |
| if (!manager.parent) { return; } |
| |
| // Assign (if they exist) the sibling Views to a property. |
| parentViews = manager.parent.views[manager.selector]; |
| |
| // If this is an array of items remove items that are not marked to |
| // keep. |
| if (_.isArray(parentViews)) { |
| // Remove duplicate Views. |
| return _.each(_.clone(parentViews), function(view, i) { |
| // If the managers match, splice off this View. |
| if (view && view.__manager__ === manager) { |
| aSplice.call(parentViews, i, 1); |
| } |
| }); |
| } |
| |
| // Otherwise delete the parent selector. |
| delete manager.parent.views[manager.selector]; |
| } |
| }, |
| |
| // Cache templates into LayoutManager._cache. |
| cache: function(path, contents) { |
| // If template path is found in the cache, return the contents. |
| if (path in this._cache && contents == null) { |
| return this._cache[path]; |
| // Ensure path and contents aren't undefined. |
| } else if (path != null && contents != null) { |
| return this._cache[path] = contents; |
| } |
| |
| // If the template is not in the cache, return undefined. |
| }, |
| |
| // Accept either a single view or an array of views to clean of all DOM |
| // events internal model and collection references and all Backbone.Events. |
| cleanViews: function(views) { |
| // Clear out all existing views. |
| _.each(aConcat.call([], views), function(view) { |
| // Remove all custom events attached to this View. |
| view.unbind(); |
| |
| // Automatically unbind `model`. |
| if (view.model instanceof Backbone.Model) { |
| view.model.off(null, null, view); |
| } |
| |
| // Automatically unbind `collection`. |
| if (view.collection instanceof Backbone.Collection) { |
| view.collection.off(null, null, view); |
| } |
| |
| // Automatically unbind events bound to this View. |
| view.stopListening(); |
| |
| // If a custom cleanup method was provided on the view, call it after |
| // the initial cleanup is done |
| if (_.isFunction(view.cleanup)) { |
| view.cleanup(); |
| } |
| }); |
| }, |
| |
| // This static method allows for global configuration of LayoutManager. |
| configure: function(options) { |
| _.extend(LayoutManager.prototype, options); |
| |
| // Allow LayoutManager to manage Backbone.View.prototype. |
| if (options.manage) { |
| Backbone.View.prototype.manage = true; |
| } |
| |
| // Disable the element globally. |
| if (options.el === false) { |
| Backbone.View.prototype.el = false; |
| } |
| |
| // Allow global configuration of `suppressWarnings`. |
| if (options.suppressWarnings === true) { |
| Backbone.View.prototype.suppressWarnings = true; |
| } |
| }, |
| |
| // Configure a View to work with the LayoutManager plugin. |
| setupView: function(views, options) { |
| // Ensure that options is always an object, and clone it so that |
| // changes to the original object don't screw up this view. |
| options = _.extend({}, options); |
| |
| // Set up all Views passed. |
| _.each(aConcat.call([], views), function(view) { |
| // If the View has already been setup, no need to do it again. |
| if (view.__manager__) { |
| return; |
| } |
| |
| var views, declaredViews; |
| var proto = LayoutManager.prototype; |
| |
| // Ensure necessary properties are set. |
| _.defaults(view, { |
| // Ensure a view always has a views object. |
| views: {}, |
| |
| // Ensure a view always has a sections object. |
| sections: {}, |
| |
| // Internal state object used to store whether or not a View has been |
| // taken over by layout manager and if it has been rendered into the |
| // DOM. |
| __manager__: {}, |
| |
| // Add the ability to remove all Views. |
| _removeViews: LayoutManager._removeViews, |
| |
| // Add the ability to remove itself. |
| _removeView: LayoutManager._removeView |
| |
| // Mix in all LayoutManager prototype properties as well. |
| }, LayoutManager.prototype); |
| |
| // Assign passed options. |
| view.options = options; |
| |
| // Merge the View options into the View. |
| _.extend(view, options); |
| |
| // By default the original Remove function is the Backbone.View one. |
| view._remove = Backbone.View.prototype.remove; |
| |
| // Ensure the render is always set correctly. |
| view.render = LayoutManager.prototype.render; |
| |
| // If the user provided their own remove override, use that instead of |
| // the default. |
| if (view.remove !== proto.remove) { |
| view._remove = view.remove; |
| view.remove = proto.remove; |
| } |
| |
| // Normalize views to exist on either instance or options, default to |
| // options. |
| views = options.views || view.views; |
| |
| // Set the internal views, only if selectors have been provided. |
| if (_.keys(views).length) { |
| // Keep original object declared containing Views. |
| declaredViews = views; |
| |
| // Reset the property to avoid duplication or overwritting. |
| view.views = {}; |
| |
| // If any declared view is wrapped in a function, invoke it. |
| _.each(declaredViews, function(declaredView, key) { |
| if (typeof declaredView === "function") { |
| declaredViews[key] = declaredView.call(view, view); |
| } |
| }); |
| |
| // Set the declared Views. |
| view.setViews(declaredViews); |
| } |
| }); |
| } |
| }); |
| |
| LayoutManager.VERSION = "0.9.5"; |
| |
| // Expose through Backbone object. |
| Backbone.Layout = LayoutManager; |
| |
| // Override _configure to provide extra functionality that is necessary in |
| // order for the render function reference to be bound during initialize. |
| Backbone.View = function(options) { |
| var noel; |
| |
| // Ensure options is always an object. |
| options = options || {}; |
| |
| // Remove the container element provided by Backbone. |
| if ("el" in options ? options.el === false : this.el === false) { |
| noel = true; |
| } |
| |
| // If manage is set, do it! |
| if (options.manage || this.manage) { |
| // Set up this View. |
| LayoutManager.setupView(this, options); |
| } |
| |
| // Assign the `noel` property once we're sure the View we're working with is |
| // managed by LayoutManager. |
| if (this.__manager__) { |
| this.__manager__.noel = noel; |
| this.__manager__.suppressWarnings = options.suppressWarnings; |
| } |
| |
| // Act like nothing happened. |
| ViewConstructor.apply(this, arguments); |
| }; |
| |
| // Copy over the extend method. |
| Backbone.View.extend = ViewConstructor.extend; |
| |
| // Copy over the prototype as well. |
| Backbone.View.prototype = ViewConstructor.prototype; |
| |
| // Default configuration options; designed to be overriden. |
| var defaultOptions = { |
| // Prefix template/layout paths. |
| prefix: "", |
| |
| // Can be used to supply a different deferred implementation. |
| deferred: function() { |
| return $.Deferred(); |
| }, |
| |
| // Fetch is passed a path and is expected to return template contents as a |
| // function or string. |
| fetchTemplate: function(path) { |
| return _.template($(path).html()); |
| }, |
| |
| // By default, render using underscore's templating and trim output. |
| renderTemplate: function(template, context) { |
| return trim(template.call(this, context)); |
| }, |
| |
| // By default, pass model attributes to the templates |
| serialize: function() { |
| return this.model ? _.clone(this.model.attributes) : {}; |
| }, |
| |
| // This is the most common way you will want to partially apply a view into |
| // a layout. |
| partial: function($root, $el, rentManager, manager) { |
| var $filtered; |
| |
| // If selector is specified, attempt to find it. |
| if (manager.selector) { |
| if (rentManager.noel) { |
| $filtered = $root.filter(manager.selector); |
| $root = $filtered.length ? $filtered : $root.find(manager.selector); |
| } else { |
| $root = $root.find(manager.selector); |
| } |
| } |
| |
| // Use the insert method if the parent's `insert` argument is true. |
| if (rentManager.insert) { |
| this.insert($root, $el); |
| } else { |
| this.html($root, $el); |
| } |
| }, |
| |
| // Override this with a custom HTML method, passed a root element and content |
| // (a jQuery collection or a string) to replace the innerHTML with. |
| html: function($root, content) { |
| $root.html(content); |
| }, |
| |
| // Used for inserting subViews in a single batch. This gives a small |
| // performance boost as we write to a disconnected fragment instead of to the |
| // DOM directly. Smarter browsers like Chrome will batch writes internally |
| // and layout as seldom as possible, but even in that case this provides a |
| // decent boost. jQuery will use a DocumentFragment for the batch update, |
| // but Cheerio in Node will not. |
| htmlBatch: function(rootView, subViews, selector) { |
| // Shorthand the parent manager object. |
| var rentManager = rootView.__manager__; |
| // Create a simplified manager object that tells partial() where |
| // place the elements. |
| var manager = { selector: selector }; |
| |
| // Get the elements to be inserted into the root view. |
| var els = _.reduce(subViews, function(memo, sub) { |
| // Check if keep is present - do boolean check in case the user |
| // has created a `keep` function. |
| var keep = typeof sub.keep === "boolean" ? sub.keep : sub.options.keep; |
| // If a subView is present, don't push it. This can only happen if |
| // `keep: true`. We do the keep check for speed as $.contains is not |
| // cheap. |
| var exists = keep && $.contains(rootView.el, sub.el); |
| |
| // If there is an element and it doesn't already exist in our structure |
| // attach it. |
| if (sub.el && !exists) { |
| memo.push(sub.el); |
| } |
| |
| return memo; |
| }, []); |
| |
| // Use partial to apply the elements. Wrap els in jQ obj for cheerio. |
| return this.partial(rootView.$el, $(els), rentManager, manager); |
| }, |
| |
| // Very similar to HTML except this one will appendChild by default. |
| insert: function($root, $el) { |
| $root.append($el); |
| }, |
| |
| // Return a deferred for when all promises resolve/reject. |
| when: function(promises) { |
| return $.when.apply(null, promises); |
| }, |
| |
| // A method to determine if a View contains another. |
| contains: function(parent, child) { |
| return $.contains(parent, child); |
| } |
| }; |
| |
| // Extend LayoutManager with default options. |
| _.extend(LayoutManager.prototype, defaultOptions); |
| |
| // Assign `LayoutManager` object for AMD loaders. |
| return LayoutManager; |
| |
| })); |