diff --git a/backbone.layoutmanager.js b/backbone.layoutmanager.js index 5545afb0..a69191e0 100644 --- a/backbone.layoutmanager.js +++ b/backbone.layoutmanager.js @@ -273,16 +273,16 @@ var LayoutManager = Backbone.View.extend({ }, // Shorthand to `setView` function with the `insert` flag set. - insertView: function(selector, view) { + insertView: function(selector, view, options) { // 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 (view instanceof Backbone.View) { + return this.setView(selector, view, true, options); } // If no `view` argument is defined, then assume the first argument is the // View, somewhat now confusingly named `selector`. - return this.setView(selector, true); + return this.setView(selector, true, options); }, // Iterate over an object and ensure every value is wrapped in an array to @@ -366,8 +366,8 @@ 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; + setView: function(name, view, insert, insertOptions) { + var manager, options, selector, existing; // Parent view, the one you are setting a View on. var root = this; @@ -398,6 +398,9 @@ var LayoutManager = Backbone.View.extend({ // Add reference to the placement selector used. selector = manager.selector = root.sections[name] || name; + // Reference existing views at this selector. + existing = root.views[selector]; + // Code path is less complex for Views that are not being inserted. Simply // remove existing Views and bail out with the assignment. if (!insert) { @@ -419,6 +422,23 @@ var LayoutManager = Backbone.View.extend({ // the end. root.views[selector] = aConcat.call([], root.views[name] || [], view); + // If we're inserting, we need to insert into the views array. If one + // already exists, we may want to insert at a particular index. + if (insertOptions && _.isNumber(insertOptions.insertAt) && existing) { + // If an index is specified & existing views are presesnt, + // splice there. + existing.splice(insertOptions.insertAt, 0, view); + root.views[selector] = existing; + + // Mark where we inserted for later reference when inserting + // into the DOM. + root.__manager__.insertAt = insertOptions.insertAt; + } else { + // If no index is supplied, ensure this.views[selector] is an array + // and push this View to the end. + root.views[selector] = aConcat.call([], existing || [], view); + } + // Put the parent view into `insert` mode. root.__manager__.insert = true; @@ -925,7 +945,7 @@ LayoutManager.prototype.options = { // Use the insert method if the parent's `insert` argument is true. if (rentManager.insert) { - this.insert($root, $el); + this.insert($root, $el, rentManager.insertAt); } else { this.html($root, $el); } @@ -974,8 +994,35 @@ LayoutManager.prototype.options = { }, // Very similar to HTML except this one will appendChild by default. - insert: function($root, $el) { - $root.append($el); + insert: function($root, $el, insertAt) { + if(_.isNumber(insertAt)) { + // If an index is supplied, use it. + this.insertAtIndex($root, $el, insertAt); + } else { + $root.append($el); + } + }, + + // Called by `insert` if an `insertAt` is provided. Inserts a view + // at a particular position. + insertAtIndex: function($root, $el, insertAt) { + var $baseEl; + // If insertAt is < 0, it behaves like the index in Array#splice. + if(insertAt < 0) { + $baseEl = $root.children() + .eq(Math.max(0, $root.children().length + insertAt)); + } else { + $baseEl = $root.children().eq(insertAt); + } + + if($baseEl.length) { + // If a reference point is found for insertion, put this view behind it. + $baseEl.before($el); + } else { + // If no reference point is found (index is greater than the length of + // of the array of elements), append it. + this.insert($root, $el); + } }, // Return a deferred for when all promises resolve/reject. diff --git a/node/index.js b/node/index.js index 965e0438..f641d9d8 100644 --- a/node/index.js +++ b/node/index.js @@ -85,7 +85,7 @@ Backbone.Layout.configure({ // Use the insert method if the parent's `insert` argument is true. if (rentManager.insert) { - this.insert($root, $el); + this.insert($root, $el, rentManager.insertAt); } else { this.html($root, $el); } diff --git a/test/views.js b/test/views.js index bd7cfc88..b41897e6 100644 --- a/test/views.js +++ b/test/views.js @@ -2227,4 +2227,72 @@ test("A view's 'views' option should auto-invoke passed functions.", 3, function }); }); +test("`insertAt` parameter to View#insertView", 8, function() { + var Child = Backbone.Layout.extend({ + template: _.template("
"), + afterRender: function(){ + this.$(".child").addClass("index-" + this.options.index); + } + }); + var Parent = Backbone.Layout.extend({ + id: "wrapper", + template: _.template("
"), + views: { + ".parent" : [ + new Child({index: 1}), + new Child({index: 2}) + ] + } + }); + + var view = new Parent(); + view.render(); + + var expected1 = [ + "
", + "
", + "
", + "
" + ]; + equal(view.$el.html(), expected1.join(""), "expected HTML before an insert"); + + // Insert a few + + // beginning + view.insertView(".parent", new Child({index: 3}), {insertAt: 0}).render(); + // end + view.insertView(".parent", new Child({index: 4}), {insertAt: 3}).render(); + // high index + view.insertView(".parent", new Child({index: 5}), {insertAt: 10}).render(); + // neg index (one from end as in Array#splice) + view.insertView(".parent", new Child({index: 6}), {insertAt: -1}).render(); + + var expected2 = [ + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
" + ]; + equal(view.$el.html(), expected2.join(""), + "expected HTML after a few targeted inserts"); + ok(view.views[".parent"][0].$el.find(".index-3").length, + "element 3 is correctly inserted in parent.views array"); + ok(view.views[".parent"][1].$el.find(".index-1").length, + "element 1 is correctly inserted in parent.views array"); + ok(view.views[".parent"][2].$el.find(".index-2").length, + "element 2 is correctly inserted in parent.views array"); + ok(view.views[".parent"][3].$el.find(".index-4").length, + "element 4 is correctly inserted in parent.views array"); + ok(view.views[".parent"][4].$el.find(".index-6").length, + "element 6 is correctly inserted in parent.views array"); + ok(view.views[".parent"][5].$el.find(".index-5").length, + "element 5 is correctly inserted in parent.views array"); + +}); + + })();