diff options
author | Garren Smith <garren.smith@gmail.com> | 2014-01-09 16:02:58 +0200 |
---|---|---|
committer | Garren Smith <garren.smith@gmail.com> | 2014-01-09 16:02:58 +0200 |
commit | c8944911766816638682996eb6fd9efe890f3486 (patch) | |
tree | e4edd6b6d8ff43cc2ef14de02e1b0f43be67e7ac | |
parent | b058aefbaacff25f33175ce41133948249aef379 (diff) | |
download | couchdb-c8944911766816638682996eb6fd9efe890f3486.tar.gz |
Fauxton: Update backbone.layoutmanager to V0.9.4
-rw-r--r-- | src/fauxton/assets/js/plugins/backbone.layoutmanager.js | 418 |
1 files changed, 199 insertions, 219 deletions
diff --git a/src/fauxton/assets/js/plugins/backbone.layoutmanager.js b/src/fauxton/assets/js/plugins/backbone.layoutmanager.js index b8296b449..c5d4a80f1 100644 --- a/src/fauxton/assets/js/plugins/backbone.layoutmanager.js +++ b/src/fauxton/assets/js/plugins/backbone.layoutmanager.js @@ -1,5 +1,5 @@ /*! - * backbone.layoutmanager.js v0.9.1 + * backbone.layoutmanager.js v0.9.4 * Copyright 2013, Tim Branyen (@tbranyen) * backbone.layoutmanager.js may be freely distributed under the MIT license. */ @@ -24,30 +24,29 @@ // `window` object; in Node, it will be `global`. var window = this; -// Hoisted, referenced at the bottom of the source. This caches a list of all -// LayoutManager options at definition time. -var keys; - -// Maintain references to the two `Backbone.View` functions that are -// overwritten so that they can be proxied. -var _configure = Backbone.View.prototype._configure; +// 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(manage, options) { + _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 = options.beforeRender; + var beforeRender = view.beforeRender; // Create a deferred instead of going off - var def = options.deferred(); + var def = view.deferred(); // Ensure all nested Views are properly scrubbed if re-rendering. if (view.hasRendered) { @@ -64,7 +63,7 @@ var LayoutManager = Backbone.View.extend({ view.trigger("beforeRender", view); // Render! - manage(view, options).render().then(function() { + view._viewRender(manager).render().then(function() { // Complete this deferred once resolved. def.resolve(); }); @@ -82,18 +81,142 @@ var LayoutManager = Backbone.View.extend({ // 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) { - // Options may not always be passed to the constructor, this ensures it is - // always an object. - options = options || {}; - // Grant this View superpowers. - LayoutManager.setupView(this, options); + 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.call(this, options); + Backbone.View.apply(this, arguments); }, // This method is used within specific methods to indicate that they should @@ -122,8 +245,7 @@ var LayoutManager = Backbone.View.extend({ renderViews: function() { var root = this; var manager = root.__manager__; - var options = root.getAllOptions(); - var newDeferred = options.deferred(); + var newDeferred = root.deferred(); // Collect all promises from rendering the child views and wait till they // all complete. @@ -136,7 +258,7 @@ var LayoutManager = Backbone.View.extend({ // Once all child views have completed rendering, resolve parent deferred // with the correct context. - options.when(promises).then(function() { + root.when(promises).then(function() { newDeferred.resolveWith(root, [root]); }); @@ -239,7 +361,7 @@ var LayoutManager = Backbone.View.extend({ // 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, options, selector; + var manager, selector; // Parent view, the one you are setting a View on. var root = this; @@ -261,18 +383,12 @@ var LayoutManager = Backbone.View.extend({ "Backbone.View instances."); } - // Assign options. - options = view.getAllOptions(); - // Add reference to the parentView. manager.parent = root; // Add reference to the placement selector used. selector = manager.selector = root.sections[name] || name; - // Call the `setup` method, since we now have a relationship created. - _.result(view, "setup"); - // Code path is less complex for Views that are not being inserted. Simply // remove existing Views and bail out with the assignment. if (!insert) { @@ -280,7 +396,7 @@ var LayoutManager = Backbone.View.extend({ // into the parent. if (view.hasRendered) { // Apply the partial. - options.partial(root.$el, view.$el, root.__manager__, manager); + view.partial(root.$el, view.$el, root.__manager__, manager); } // Ensure remove is called when swapping View's. @@ -327,11 +443,10 @@ var LayoutManager = Backbone.View.extend({ // once all subviews and main view have been rendered into the view.el. render: function() { var root = this; - var options = root.getAllOptions(); var manager = root.__manager__; var parent = manager.parent; var rentManager = parent && parent.__manager__; - var def = options.deferred(); + var def = root.deferred(); // Triggered once the render has succeeded. function resolve() { @@ -341,16 +456,16 @@ var LayoutManager = Backbone.View.extend({ _.each(root.views, function(views, selector) { // Fragments aren't used on arrays of subviews. if (_.isArray(views)) { - options.htmlBatch(root, views, selector); + 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 (!options.contains(parent.el, root.el)) { + if (!root.contains(parent.el, root.el)) { // Apply the partial using parent's html() method. - parent.getAllOptions().partial(parent.$el, root.$el, rentManager, + parent.partial(parent.$el, root.$el, rentManager, manager); } } @@ -377,7 +492,7 @@ var LayoutManager = Backbone.View.extend({ // and setting the hasRendered flag. function completeRender() { var console = window.console; - var afterRender = options.afterRender; + var afterRender = root.afterRender; if (afterRender) { afterRender.call(root, root); @@ -391,7 +506,7 @@ var LayoutManager = Backbone.View.extend({ if (manager.noel && root.$el.length > 1) { // Do not display a warning while testing or if warning suppression // is enabled. - if (_.isFunction(console.warn) && !options.suppressWarnings) { + if (_.isFunction(console.warn) && !root.suppressWarnings) { console.warn("`el: false` with multiple top level elements is " + "not supported."); @@ -420,11 +535,10 @@ var LayoutManager = Backbone.View.extend({ // Actually facilitate a render. function actuallyRender() { - var options = root.getAllOptions(); // The `_viewRender` method is broken out to abstract away from having // too much code in `actuallyRender`. - root._render(LayoutManager._viewRender, options).done(function() { + root._render().done(function() { // If there are no children to worry about, complete the render // instantly. if (!_.keys(root.views).length) { @@ -443,7 +557,7 @@ var LayoutManager = Backbone.View.extend({ // Mark each subview's manager so they don't attempt to attach by // themselves. Return a single promise representing the entire // render. - return options.when(_.map(view, function(subView) { + return root.when(_.map(view, function(subView) { subView.__manager__.insertedViaFragment = true; return subView.render().__manager__.renderDeferred; })); @@ -456,7 +570,7 @@ var LayoutManager = Backbone.View.extend({ // Once all nested Views have been rendered, resolve this View's // deferred. - options.when(promises).done(resolve); + root.when(promises).done(resolve); }); } @@ -489,148 +603,14 @@ var LayoutManager = Backbone.View.extend({ // Call the original remove function. return this._remove.apply(this, arguments); - }, - - // Merge instance and global options. - getAllOptions: function() { - // Instance overrides take precedence, fallback to prototype options. - return _.extend({}, this, LayoutManager.prototype.options, this.options); } }, + +// Static Properties { // Clearable cache. _cache: {}, - // 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(root, options) { - var url, contents, def, renderedEl; - var manager = root.__manager__; - - // This function is responsible for pairing the rendered template into - // the DOM element. - function applyTemplate(rendered) { - // Actually put the rendered contents into the element. - if (_.isString(rendered)) { - // If no container is specified, we must replace the content. - if (manager.noel) { - // Trim off the whitespace, since the contents are passed into `$()`. - rendered = $.trim(rendered); - - // Hold a reference to created element as replaceWith doesn't return - // new el. - renderedEl = $(rendered); - - // Remove extra root elements. - root.$el.slice(1).remove(); - - // Swap out the View on the first top level element to avoid - // duplication. - root.$el.replaceWith(renderedEl); - - // Don't delegate events here - we'll do that in resolve() - root.setElement(renderedEl, false); - } else { - options.html(root.$el, rendered); - } - } - - // Resolve only after fetch and render have succeeded. - def.resolveWith(root, [root]); - } - - // 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, contents) { - // 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; - - applyTemplate(rendered); - }; - - // Ensure the cache is up-to-date. - LayoutManager.cache(url, contents); - - // Render the View into the el property. - if (contents) { - rendered = options.renderTemplate.call(root, contents, context); - } - - // If the function was synchronous, continue execution. - if (!manager.isAsync) { - applyTemplate(rendered); - } - } - - 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 || options.serialize; - var template = root.template || options.template; - - // Create a deferred specifically for fetching. - def = options.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 = options.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 = options.fetchTemplate.call(root, options.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 = options.fetchTemplate.call(root, template); - } - - // If the function was synchronous, continue execution. - if (!manager.isAsync) { - done(context, contents); - } - - return def; - } - }; - }, - // Remove all nested Views. _removeViews: function(root, force) { // Shift arguments around. @@ -713,8 +693,6 @@ var LayoutManager = Backbone.View.extend({ cleanViews: function(views) { // Clear out all existing views. _.each(aConcat.call([], views), function(view) { - var cleanup; - // Remove all custom events attached to this View. view.unbind(); @@ -733,16 +711,15 @@ var LayoutManager = Backbone.View.extend({ // If a custom cleanup method was provided on the view, call it after // the initial cleanup is done - cleanup = view.getAllOptions().cleanup; - if (_.isFunction(cleanup)) { - cleanup.call(view); + if (_.isFunction(view.cleanup)) { + view.cleanup(); } }); }, // This static method allows for global configuration of LayoutManager. configure: function(options) { - _.extend(LayoutManager.prototype.options, options); + _.extend(LayoutManager.prototype, options); // Allow LayoutManager to manage Backbone.View.prototype. if (options.manage) { @@ -762,6 +739,9 @@ var LayoutManager = Backbone.View.extend({ // Configure a View to work with the LayoutManager plugin. setupView: function(views, options) { + // Don't break the options object (passed into Backbone.View#initialize). + options = 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. @@ -769,9 +749,8 @@ var LayoutManager = Backbone.View.extend({ return; } - var views, declaredViews, viewOptions; + var views, declaredViews; var proto = LayoutManager.prototype; - var viewOverrides = _.pick(view, keys); // Ensure necessary properties are set. _.defaults(view, { @@ -795,20 +774,11 @@ var LayoutManager = Backbone.View.extend({ // Mix in all LayoutManager prototype properties as well. }, LayoutManager.prototype); - // Extend the options with the prototype and passed options. - options = view.options = _.defaults(options || {}, view.options, - proto.options); - - // Ensure view events are properly copied over. - viewOptions = _.pick(options, aConcat.call(["events", "sections"], - _.values(options.events))); + // Assign passed options. + view.options = options; // Merge the View options into the View. - _.extend(view, viewOptions); - - // Pick out the specific properties that can be dynamically added at - // runtime and ensure they are available on the view object. - _.extend(options, viewOverrides); + _.extend(view, options); // By default the original Remove function is the Backbone.View one. view._remove = Backbone.View.prototype.remove; @@ -835,43 +805,42 @@ var LayoutManager = Backbone.View.extend({ // 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); } - - // If a template is passed use that instead. - if (view.options.template) { - view.options.template = options.template; - // Ensure the template is mapped over. - } else if (view.template) { - options.template = view.template; - } }); } }); -// Tack on the version. -LayoutManager.VERSION = "0.9.1"; +LayoutManager.VERSION = "0.9.4"; +// 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.prototype._configure = function(options) { - var noel, retVal; +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; } - // Run the original _configure. - retVal = _configure.apply(this, arguments); - // If manage is set, do it! if (options.manage || this.manage) { // Set up this View. - LayoutManager.setupView(this); + LayoutManager.setupView(this, options); } // Assign the `noel` property once we're sure the View we're working with is @@ -882,11 +851,17 @@ Backbone.View.prototype._configure = function(options) { } // Act like nothing happened. - return retVal; + 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. -LayoutManager.prototype.options = { +var defaultOptions = { // Prefix template/layout paths. prefix: "", @@ -901,9 +876,14 @@ LayoutManager.prototype.options = { return _.template($(path).html()); }, - // By default, render using underscore's templating. + // By default, render using underscore's templating and trim output. renderTemplate: function(template, context) { - return template(context); + return trim(template(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 @@ -987,8 +967,8 @@ LayoutManager.prototype.options = { } }; -// Maintain a list of the keys at define time. -keys = _.keys(LayoutManager.prototype.options); +// Extend LayoutManager with default options. +_.extend(LayoutManager.prototype, defaultOptions); // Assign `LayoutManager` object for AMD loaders. return LayoutManager; |