UNPKG

bb-collection-view

Version:

Easily render backbone.js collections with support for automatic selection of models in response to clicks, reordering models via drag and drop, and more.

1,278 lines (900 loc) 41.5 kB
$(document).ready( function() { var Employee = Backbone.Model.extend( { } ); var Employees = Backbone.Collection.extend( { model : Employee } ); function commonSetup() { this.emp1 = new Employee( { id : 1, firstName : 'Sherlock', lastName : 'Holmes' } ); this.emp2 = new Employee( { id : 2, firstName : 'John', lastName : 'Watson' } ); this.emp3 = new Employee( { id : 3, firstName : 'Mycroft', lastName : 'Holmes' } ); this.employees = new Employees( [ this.emp1, this.emp2, this.emp3 ] ); this.EmployeeView = Backbone.View.extend( { template : _.template( $( "#employee-template" ).html() ), render : function() { var emp = this.model.toJSON(); var html = this.template(emp); this.$el.html(html); } } ); this.EmployeeViewWithLi = Backbone.View.extend( { tagName : "li", template : _.template( $( "#employee-template" ).html() ), render : function() { var emp = this.model.toJSON(); var html = this.template(emp); this.$el.html(html); } } ); this.EmployeeViewForTable = Backbone.View.extend( { tagName : 'tr', template : _.template( $( "#employee-template-for-table" ).html() ), render : function() { var emp = this.model.toJSON(); var html = this.template(emp); this.$el.html(html); } } ); this.EmployeeView2 = Backbone.View.extend( { template : _.template( $( "#employee-template-2" ).html() ), render : function() { var emp = this.model.toJSON(); var html = this.template(emp); this.$el.html(html); } } ); var $fixture = $( "#qunit-fixture" ); $fixture.append( "<ul id='myCollectionList'></ul>" ); $fixture.append( "<table id='myCollectionTable'></table>" ); $fixture.append( "<table id='myCollectionTableWithContents'><thead></thead><tbody></tbody></table>" ); this.$collectionViewEl = $( "#myCollectionList" ); this.$collectionViewForTableEl = $( "#myCollectionTable" ); this.$collectionViewForTableWithContentsEl = $( "#myCollectionTableWithContents" ); } module( "Table rendering", { setup: function() { commonSetup.call( this ); } } ); test( "Rendering with an empty table element", 3, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, collection : this.employees, modelView : this.EmployeeViewForTable } ); myCollectionView.render(); equal( myCollectionView.$el.find( "tbody" ).length, 1, "Tbody is created" ); equal( myCollectionView.$el.find( "tbody > tr" ).length, 3, "Model views are added inside the tbody" ); ok( myCollectionView.$el.html().indexOf( "Sherlock Holmes" ) !== -1, "Rendered table contains 'Sherlock Holmes'"); } ); test( "Rendering with a tbody and thead inside the table element", 3, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewForTableWithContentsEl, collection : this.employees, modelView : this.EmployeeViewForTable } ); myCollectionView.render(); equal( myCollectionView.$el.find( "thead" ).length, 1, "Thead remains after rendering" ); equal( myCollectionView.$el.find( "tbody > tr" ).length, 3, "Model views are added inside the tbody" ); ok( myCollectionView.$el.html().indexOf( "Sherlock Holmes" ) !== -1, "Rendered table contains 'Sherlock Holmes'"); } ); test( "Rendering with an empty table element (detached rendering)", 3, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, collection : this.employees, modelView : this.EmployeeViewForTable, detachedRendering : true } ); myCollectionView.render(); equal( myCollectionView.$el.find( "tbody" ).length, 1, "Tbody is created" ); equal( myCollectionView.$el.find( "tbody > tr" ).length, 3, "Model views are added inside the tbody" ); ok( myCollectionView.$el.html().indexOf( "Sherlock Holmes" ) !== -1, "Rendered table contains 'Sherlock Holmes'"); } ); test( "Setting multiple models to empty collection renders new rows", 3, function() { var emptyCollection = new Backbone.Collection(); var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, collection : emptyCollection, modelView : this.EmployeeViewForTable } ); myCollectionView.render(); emptyCollection.set(this.employees.models); equal( myCollectionView.$el.find( "tbody" ).length, 1, "Tbody is created" ); equal( myCollectionView.$el.find( "tbody > tr" ).length, 3, "Model views are added inside the tbody" ); ok( myCollectionView.$el.html().indexOf( "Sherlock Holmes" ) !== -1, "Rendered table contains 'Sherlock Holmes'"); } ); module( "List rendering", { setup: function() { commonSetup.call( this ); } } ); test( "Rendering with a template that is an <li>", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeViewWithLi } ); myCollectionView.render(); ok( myCollectionView.viewManager.findByIndex(0).$el.children()[0].tagName.toLowerCase() !== "li", "<li> view template not wrapped with an extra <li>" ); } ); test( "Rendering with a template that is not an <li>", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); ok( myCollectionView.viewManager.findByIndex(0).$el.parent().prop( "tagName" ).toLowerCase() === "li", "non <li> view template is wrapped with an <li>" ); } ); module( "Item Selection", { setup: function() { commonSetup.call( this ); } } ); test( "getSelectedModel", 6, function() { var singleSelectionListView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView } ); singleSelectionListView.render(); singleSelectionListView.setSelectedModel( this.emp1 ); var selectedCid = singleSelectionListView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp1.cid, "Selected item is the correct cid" ); var selectedModel = singleSelectionListView.getSelectedModel(); equal( selectedModel, this.emp1, "Selected item is the correct model" ); var selectedId = singleSelectionListView.getSelectedModel( { by : "id" } ); equal( selectedId, this.emp1.id, "Selected item is the correct id" ); var selectedModelView = singleSelectionListView.getSelectedModel( { by : "view" } ); equal( selectedModelView.model, this.emp1, "Selected item is the correct view" ); singleSelectionListView.setSelectedModel( this.emp2 ); var selectedListItemOffset = singleSelectionListView.getSelectedModel( { by : "offset" } ); equal( selectedListItemOffset, 1, "Selected list item is the correct offset" ); var singleSelectionTableView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, selectable : true, collection : this.employees, modelView : this.EmployeeViewForTable } ); singleSelectionTableView.render(); singleSelectionTableView.setSelectedModel( this.emp2 ); var selectedTableRowOffset = singleSelectionTableView.getSelectedModel( { by : "offset" } ); equal( selectedTableRowOffset, 1, "Selected table row is the correct offset" ); }); test( "setSelectedModel", 6, function() { var singleSelectionListView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView } ); singleSelectionListView.render(); var selectedCid; singleSelectionListView.setSelectedModel( this.emp1.id, { by : "id" } ); selectedCid = singleSelectionListView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp1.cid, "Selected item correctly set using id" ); singleSelectionListView.setSelectedModel( this.emp2.cid, { by : "cid" } ); selectedCid = singleSelectionListView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp2.cid, "Selected item correctly set using cid" ); singleSelectionListView.setSelectedModel( this.emp3 ); selectedModel = singleSelectionListView.getSelectedModel(); equal( selectedModel, this.emp3, "Selected item correctly set using model" ); singleSelectionListView.setSelectedModel( singleSelectionListView.viewManager.findByModel( this.emp1 ), { by : "view" } ); selectedModel = singleSelectionListView.getSelectedModel(); equal( selectedModel, this.emp1, "Selected item correctly set using view" ); singleSelectionListView.setSelectedModel( 1, { by : "offset" } ); selectedModel = singleSelectionListView.getSelectedModel(); equal( selectedModel, this.emp2, "Selected list item correctly set using offset" ); var singleSelectionTableView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, selectable : true, collection : this.employees, modelView : this.EmployeeViewForTable } ); singleSelectionTableView.render(); singleSelectionTableView.setSelectedModel( 1, { by : "offset" } ); selectedModel = singleSelectionTableView.getSelectedModel(); equal( selectedModel, this.emp2, "Selected table row correctly set using offset" ); }); test( "getSelectedModels", 18, function() { var singleSelectionListView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, selectMultiple : true, collection : this.employees, modelView : this.EmployeeView } ); singleSelectionListView.render(); singleSelectionListView.setSelectedModels( [ this.emp1, this.emp2 ] ); var selectedCids = singleSelectionListView.getSelectedModels( { by : "cid" } ); equal( selectedCids.length, 2, "Correct number of selected items"); equal( selectedCids[0], this.emp1.cid, "One of selected items is the correct cid" ); equal( selectedCids[1], this.emp2.cid, "Other selected item is the correct cid" ); var selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected items"); equal( selectedModels[0], this.emp1, "One of selected items is the correct model" ); equal( selectedModels[1], this.emp2, "Other selected item is the correct model" ); var selectedIds = singleSelectionListView.getSelectedModels({ by : "id" }); equal( selectedIds.length, 2, "Correct number of selected items"); equal( selectedIds[0], this.emp1.id, "One of selected items is the correct id" ); equal( selectedIds[1], this.emp2.id, "Other selected item is the correct id" ); var selectedModelViews = singleSelectionListView.getSelectedModels( { by : "view" }); equal( selectedModelViews.length, 2, "Correct number of selected items"); equal( selectedModelViews[0].model, this.emp1, "One of the selected items is the correct view" ); equal( selectedModelViews[1].model, this.emp2, "Other selected item is the correct view" ); var selectedListItemOffsets = singleSelectionListView.getSelectedModels( { by : "offset" } ); equal( selectedListItemOffsets.length, 2, "Correct number of selected list items" ); equal( selectedListItemOffsets[0], 0, "One of the selected list item is the correct offset" ); equal( selectedListItemOffsets[1], 1, "Other selected list item is the correct offset" ); var singleSelectionTableView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, selectable : true, collection : this.employees, modelView : this.EmployeeViewForTable } ); singleSelectionTableView.render(); singleSelectionTableView.setSelectedModels( [ this.emp1, this.emp2 ] ); var selectedTableRowOffsets = singleSelectionTableView.getSelectedModels( { by : "offset" } ); equal( selectedTableRowOffsets.length, 2, "Correct number of selected table rows" ); equal( selectedTableRowOffsets[0], 0, "One of the selected table row is the correct offset" ); equal( selectedTableRowOffsets[1], 1, "Other selected table row is the correct offset" ); }); test( "setSelectedModels", 14, function() { var singleSelectionListView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView } ); singleSelectionListView.render(); var selectedCid; singleSelectionListView.setSelectedModels( [ this.emp1.id, this.emp2.id ], { by : "id" } ); selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected items"); equal( selectedModels[0], this.emp1, "One of selected items correctly set using 'id'" ); equal( selectedModels[1], this.emp2, "Other selected item correctly set using 'id'" ); equal( _.intersection( selectedModels, [this.emp1, this.emp2 ] ).length, 2, "Selected items correctly set using 'id'" ); singleSelectionListView.setSelectedModels([ this.emp2.cid, this.emp3.cid ], { by : "cid" } ); selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected items"); equal(_.intersection(selectedModels, [ this.emp2, this.emp3 ] ).length, 2, "Selected items correctly set using 'cid'" ); singleSelectionListView.setSelectedModels( [ this.emp1, this.emp2 ] ); selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected items"); equal( _.intersection(selectedModels, [ this.emp1, this.emp2 ] ).length, 2, "Selected items correctly set using 'model'" ); singleSelectionListView.setSelectedModels([ singleSelectionListView.viewManager.findByModel( this.emp2 ), singleSelectionListView.viewManager.findByModel( this.emp3 ) ], { by : "view" } ); selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected items"); equal( _.intersection(selectedModels, [ this.emp2, this.emp3 ] ).length, 2, "Selected items correctly set using 'view'" ); singleSelectionListView.setSelectedModels( [ 0, 1 ], { by : "offset" } ); selectedModels = singleSelectionListView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected list items"); equal( _.intersection(selectedModels, [ this.emp1, this.emp2 ] ).length, 2, "Selected list items correctly set using 'offset'" ); var singleSelectionTableView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, selectable : true, collection : this.employees, modelView : this.EmployeeViewForTable } ); singleSelectionTableView.render(); singleSelectionTableView.setSelectedModels( [ 0, 1 ], { by : "offset" } ); selectedModels = singleSelectionTableView.getSelectedModels(); equal( selectedModels.length, 2, "Correct number of selected table rows"); equal( _.intersection(selectedModels, [ this.emp1, this.emp2 ] ).length, 2, "Selected table rows correctly set using 'offset'" ); }); module( "Constructor Options", { setup: function() { commonSetup.call( this ); } } ); test( "selectableModelsFilter", 3, function() { var selectableFilterView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView, selectableModelsFilter : function(model) { return model.get( "lastName" ) === "Holmes"; } } ); selectableFilterView.render(); selectableFilterView.setSelectedModel( this.emp2 ); var selectedModel = selectableFilterView.getSelectedModel(); equal( selectedModel, null, "Unselectable item was not selected" ); selectableFilterView.setSelectedModel( this.emp1 ); var selectedModel2 = selectableFilterView.getSelectedModel(); equal( selectedModel2, this.emp1, "Selectable item was selected" ); var modelView1 = selectableFilterView.viewManager.findByModel( this.emp2 ); modelView1.$el.mousedown(); var selectedModel3 = selectableFilterView.getSelectedModel(); equal( selectedModel3, this.emp1, "Selection is unchanged by clicking on an unselectable item" ); } ); test( "visibleModelsFilter", 2, function() { var visibleFilterView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView, visibleModelsFilter : function( model ) { return model.get('lastName') === 'Holmes'; } } ); visibleFilterView.render(); var modelView1 = visibleFilterView.viewManager.findByModel( this.emp1 ); ok( modelView1.$el.is( ":visible" ), "Visible item (determined by visibleModelsFilter) is visible" ); var modelView2 = visibleFilterView.viewManager.findByModel( this.emp2 ); ok( !modelView2.$el.is( ":visible" ), "Hidden item (determined by visibleModelsFilter) is hidden" ); } ); test( "clickToSelect", 2, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); var modelView1 = myCollectionView.viewManager.findByModel( this.emp1 ); modelView1.$el.mousedown(); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp1, "Successfully selected an item by clicking on it" ); myCollectionView.remove(); myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, clickToSelect : false, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); modelView1 = myCollectionView.viewManager.findByModel( this.emp2 ); selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, null, "Were not able to select an item by clicking on it when clickToSelect is false" ); } ); test( "processKeyEvents", 6, function() { function keyEvent ( code ) { var keyEvent = $.Event( "keydown" ); keyEvent.which = code; return keyEvent; } var upArrow = 38; var downArrow = 40; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, processKeyEvents: true, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); var modelView1 = myCollectionView.viewManager.findByModel( this.emp1 ); modelView1.$el.mousedown(); $( myCollectionView.$el ).trigger( keyEvent( downArrow ) ); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp2, "Down arrow keypress changes the selection to the item below the current" ); $( myCollectionView.$el ).trigger( keyEvent( upArrow ) ); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp1, "Up arrow keypress changes the selection to the item above the current" ); myCollectionView.setSelectedModel( this.emp1 ); $( myCollectionView.$el ).trigger( keyEvent( upArrow ) ); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp1, "Up arrow keypress when the first item is selected doesn't change selection" ); myCollectionView.setSelectedModel( this.emp3 ); $( myCollectionView.$el ).trigger( keyEvent( downArrow ) ); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp3, "Down arrow keypress when the last item is selected doesn't change selection" ); myCollectionView.remove(); myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, processKeyEvents: false, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); var modelView1 = myCollectionView.viewManager.findByModel( this.emp1 ); modelView1.$el.mousedown(); $( myCollectionView.$el ).trigger( keyEvent( downArrow ) ); selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp1, "Were not able to change selection by pressing down arrow key when processKeyEvents is false" ); myCollectionView.setSelectedModel( this.emp2 ); $( myCollectionView.$el ).trigger( keyEvent( upArrow ) ); selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp2, "Were not able to change selection by pressing up arrow key when processKeyEvents is false" ); } ); test( "selectable", 2, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp1 ); var selectedModel = myCollectionView.getSelectedModel(); equal( selectedModel, this.emp1, "Item successfully selected" ); myCollectionView.remove(); myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, selectable : false, modelView : this.EmployeeView } ); myCollectionView.render(); selectedModel = myCollectionView.getSelectedModel(); equal(selectedModel, null, "Item not selected (because 'selectable' was false"); }); module( "setOption", { setup: function() { commonSetup.call( this ); } } ); test( "selectMultiple", 2, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectMultiple : false } ); myCollectionView.render(); myCollectionView.setSelectedModels( [ this.emp1, this.emp2 ] ); equal( myCollectionView.getSelectedModels().length, 2, "There are two selected items" ); myCollectionView.setOption( "selectMultiple", false ); equal( myCollectionView.getSelectedModels().length, 1, "Now there is only one selected item" ); } ); test( "selectable", 2, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModels( [ this.emp1, this.emp2 ] ); equal( myCollectionView.getSelectedModels().length, 2, "There are two selected items" ); myCollectionView.setOption( "selectable", false ); equal( myCollectionView.getSelectedModels().length, 0, "Now there is no selected items" ); } ); test( "selectableModelsFilter", 4, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModels( [ this.emp1, this.emp2 ] ); equal( myCollectionView.getSelectedModels().length, 2, "There are two selected items" ); myCollectionView.setOption( "selectableModelsFilter", function( model ) { return model.get( "lastName" ) === "Holmes"; } ); equal( myCollectionView.getSelectedModels().length, 1, "Now there is one selected item" ); equal( myCollectionView.getSelectedModels()[0], this.emp1, "The remaining selected model is correct" ); var emp1View = myCollectionView.viewManager.findByModel( this.emp2 ); console.log( emp1View.$el.parent().attr("class" ) ); equal(emp1View.$el.parent().hasClass( "not-selectable" ), true, "The non-selectable item has the 'not-selectable' class" ); } ); test( "sortableModelsFilter", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, sortable: true, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); myCollectionView.setOption( "sortableModelsFilter", function( model ) { return model.get( "lastName" ) === "Holmes"; } ); var emp1View = myCollectionView.viewManager.findByModel( this.emp2 ); equal(emp1View.$el.parent().attr("class"), "not-sortable", "The non-sortable item has the 'non-sortable' class" ); } ); test( "visibleModelsFilter", 1, function() { var visibleFilterView = new Backbone.CollectionView( { el : this.$collectionViewEl, selectable : true, collection : this.employees, modelView : this.EmployeeView, } ); visibleFilterView.render(); visibleFilterView.setOption( "visibleModelsFilter", function( model ) { return model.get( "lastName" ) === "Holmes"; } ); var modelView2 = visibleFilterView.viewManager.findByModel( this.emp2 ); ok( !modelView2.$el.is( ":visible" ), "Hidden item (determined by visibleModelsFilter) is hidden" ); } ); test( "modelView", 3, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); ok( myCollectionView.viewManager.findByModel( this.emp1 ).$el.is( ":contains(Full name)" ), "Item html contains string `Full name`" ); myCollectionView.setOption( "modelView", this.EmployeeView2 ); ok( ! myCollectionView.viewManager.findByModel( this.emp1 ).$el.is( ":contains(Full name)" ), "Item html does not contain string `Full name`" ); ok( myCollectionView.viewManager.findByModel( this.emp1 ).$el.is( ":contains(Full 2 name)" ), "Item html contains string `Full 2 name`" ); } ); test( "collection - render if already rendered", 1, function() { var collection1 = this.employees; var collection2 = new Employees( [ this.emp1, this.emp2, this.emp3 ] ); collection2.add( new Employee( { firstName : "New", lastName : "Addition" } ) ); var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setOption( "collection", collection2 ); ok( myCollectionView.$el.html().indexOf( "New Addition" ) !== -1, "New collection rendered." ); } ); test( "collection - don't render unless already rendered", 1, function() { var collection1 = this.employees; var collection2 = new Employees( [ this.emp1, this.emp2, this.emp3 ] ); var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.setOption( "collection", collection2 ); equal( myCollectionView.$el.html(), "", "Collection View not rendered after calling setOption(collection) because it was never rendered to begin with." ); } ); asyncTest( "collection - don't listen to old collection", 1, function() { var collection1 = this.employees; var collection2 = new Employees( [ this.emp1, this.emp2, this.emp3 ] ); var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.listenTo( collection1, "add", function() { start(); ok( false, "Collection View should not hear add event from old collection" ); stop(); } ); myCollectionView.setOption( "collection", collection2 ); collection1.add( new Employee( { firstName : "Extra", lastName : "1" } ) ); myCollectionView.listenTo( collection2, "add", function() { start(); ok( true, "Collection View heard add event from new collection" ); } ); collection2.add( new Employee( { firstName : "Extra", lastName : "1" } ) ); myCollectionView.render(); } ); module( "Events", { setup: function() { commonSetup.call( this ); } } ); asyncTest( "selectionChanged", 2, function() { var _this = this; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.setSelectedModel( this.emp1 ); var selectionChangedHandler1 = function( newSelectedModels, oldSelectedModels ) { start(); equal( oldSelectedModels[0], _this.emp1, "Old selected model is correct" ); equal( newSelectedModels[0], _this.emp2, "New selected model is correct" ); stop(); }; var selectionChangedHandler2 = function( newSelectedModels, oldSelectedModels ) { ok( false, "This selectionChanged handler should not have been called because silent = true. " ); }; myCollectionView.on( "selectionChanged", selectionChangedHandler1 ); myCollectionView.setSelectedModel( this.emp2 ); myCollectionView.off( "selectionChanged" ); myCollectionView.on( "selectionChanged", selectionChangedHandler2 ); setTimeout( function() { myCollectionView.setSelectedModel( _this.emp1, { silent : true } ); }, 50 ); setTimeout( function() { start(); }, 100 ); } ); asyncTest( "selectionChanged when re-rendering", 0, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp1 ); var selectionChangedHandler = function( newSelectedModels, oldSelectedModels ) { ok( false, "This selectionChanged handler should not have been called because the selection can be restored intact. " ); }; myCollectionView.on( "selectionChanged", selectionChangedHandler ); myCollectionView.render(); setTimeout( function() { start(); }, 100 ); } ); asyncTest( "selectionChanged when selected item is removed", 2, function() { var _this = this; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp1 ); var selectionChangedHandler = function( newSelectedModels, oldSelectedModels ) { start(); equal( oldSelectedModels[0], null, "Old selected model is null since it is no longer available" ); equal( newSelectedModels[0], _this.emp2, "New selected model is correct" ); stop(); }; myCollectionView.on( "selectionChanged", selectionChangedHandler ); this.employees.remove( this.emp1 ); setTimeout( function() { start(); }, 200 ); } ); asyncTest( "selectionChanged when selected item is removed with selectMultiple", 3, function() { var _this = this; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true, selectMultiple : true } ); myCollectionView.render(); myCollectionView.setSelectedModels( [this.emp1, this.emp2] ); var selectionChangedHandler = function( newSelectedModels, oldSelectedModels ) { start(); equal( oldSelectedModels[0], null, "Old selected model is null since it is no longer available" ); equal( newSelectedModels.length, 1, "New selected models contains one model" ); equal( newSelectedModels[0], _this.emp2, "New selected model is correct" ); stop(); }; myCollectionView.on( "selectionChanged", selectionChangedHandler ); this.employees.remove( this.emp1 ); setTimeout( function() { start(); }, 100 ); } ); asyncTest( "selectionChanged when selected item is removed and nothing to re-select", 2, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp1 ); var selectionChangedHandler = function( newSelectedModels, oldSelectedModels ) { start(); equal( oldSelectedModels[0], null, "Old selected model is null since it is no longer available" ); equal( newSelectedModels[0], null, "New selected model is null since there is nothing to select" ); stop(); }; myCollectionView.on( "selectionChanged", selectionChangedHandler ); this.employees.reset( [] ); setTimeout( function() { start(); }, 100 ); } ); asyncTest( "updateDependentControls", 1, function() { var _this = this; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.bind( "updateDependentControls", function( newSelectedModels ) { start(); equal( newSelectedModels[0], _this.emp1, "New selected model is correct" ); } ); myCollectionView.setSelectedModel( this.emp1 ); } ); asyncTest( "doubleClick", 1, function() { var _this = this; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.on( "doubleClick", function( clickedModel ) { start(); equal( clickedModel, _this.emp2, "Correct clicked model payload" ); } ); myCollectionView.render(); var emp2View = myCollectionView.viewManager.findByModel( _this.emp2 ); emp2View.$el.dblclick(); } ); test( "collection add preserves selection", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp2 ); this.emp4 = new Employee( { id : 4, firstName : 'Tracy', lastName : 'Johnson' } ); this.employees.add( this.emp4 ); var selectedCid = myCollectionView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp2.cid, "Selected item is the correct cid" ); } ); test( "collection remove preserves selection", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp2 ); this.employees.remove( this.emp3 ); var selectedCid = myCollectionView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp2.cid, "Selected item is the correct cid" ); } ); test( "collection remove when selected model is removed", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp2 ); this.employees.remove( this.emp2 ); var selectedCid = myCollectionView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp3.cid, "Selected item is the correct cid" ); } ); test( "collection reset preserves selection", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp2 ); this.employees.reset( [this.emp2, this.emp3] ); var selectedCid = myCollectionView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp2.cid, "Selected item is the correct cid" ); } ); test( "collection subviews should stopListening when removed", function () { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); var view = myCollectionView.viewManager.findByModel( this.emp2 ); var didGetEvent = false; view.listenTo( this.emp2, 'fnord', function(){ didGetEvent = true; } ); this.employees.remove( this.emp2 ); this.emp2.trigger( 'fnord' ); equal( didGetEvent, false ); }); test( "all subviews of collection should stop listening", function () { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView } ); myCollectionView.render(); var view = myCollectionView.viewManager.findByModel( this.emp2 ); var didGetEvent = false; view.listenTo( this.emp2, 'fnord', function(){ didGetEvent = true; } ); myCollectionView.remove(); this.emp2.trigger( 'fnord' ); equal( didGetEvent, false ); }); test( "should stop listening on already created subviews when changing modelView option", function () { var myCollectionView = new Backbone.CollectionView({ el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView }); myCollectionView.render(); var view = myCollectionView.viewManager.findByModel(this.emp2); var didGetEvent = false; view.listenTo(this.emp2, 'fnord', function(){ didGetEvent = true; }); myCollectionView.setOption( "modelView", this.EmployeeView2 ); this.emp2.trigger('fnord'); equal(didGetEvent, false); }); test( "collection reset when the selected model is removed", 1, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true } ); myCollectionView.render(); myCollectionView.setSelectedModel( this.emp2 ); this.employees.reset( [this.emp1, this.emp3] ); var selectedCid = myCollectionView.getSelectedModel( { by : "cid" } ); equal( selectedCid, this.emp3.cid, "Selected item is the correct cid" ); } ); module( "Empty List Caption", { setup: function() { commonSetup.call( this ); } } ); test( "Rendering as list", 3, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true, emptyListCaption : "This is an empty list" } ); this.employees.reset(); myCollectionView.render(); equal( $( "var.empty-list-caption" ).length, 1, "Empty list caption is present" ); equal( $( "var.empty-list-caption" ).parent().prop( "tagName" ), "LI", "Empty list caption is wrapped in an li" ); stop(); myCollectionView.collection.on( "add", function() { start(); equal( $( "var.empty-list-caption" ).length, 0, "Empty list caption is not present" ); } ); myCollectionView.collection.add( this.emp1 ); } ); test( "Rendering as table", 4, function() { var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewForTableEl, collection : this.employees, modelView : this.EmployeeViewForTable, selectable : true, emptyListCaption : "This is an empty list" } ); this.employees.reset(); myCollectionView.render(); equal( $( "var.empty-list-caption" ).length, 1, "Empty list caption is present" ); equal( $( "var.empty-list-caption" ).parent().prop( "tagName" ), "TD", "Empty list caption is wrapped in an td" ); equal( $( "var.empty-list-caption" ).parent().parent().prop( "tagName" ), "TR", "Empty list caption is wrapped in a tr around the td" ); stop(); myCollectionView.collection.on( "add", function() { start(); equal( $( "var.empty-list-caption" ).length, 0, "Empty list caption is not present" ); } ); myCollectionView.collection.add( this.emp1 ); } ); test( "String caption", 1, function() { var myEmptyListCaption = "This is an empty list"; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true, emptyListCaption : myEmptyListCaption } ); this.employees.reset(); myCollectionView.render(); ok( $( "var.empty-list-caption" ).is( ":contains(" + myEmptyListCaption + ")" ), "Empty list caption contains correct text" ); } ); test( "Function caption", 2, function() { var emptyCaption = "Empty caption 1"; var myEmptyCaptionFunction = function() { return emptyCaption; }; var myCollectionView = new Backbone.CollectionView( { el : this.$collectionViewEl, collection : this.employees, modelView : this.EmployeeView, selectable : true, emptyListCaption : myEmptyCaptionFunction } ); this.employees.reset(); myCollectionView.render(); ok( $( "var.empty-list-caption" ).is( ":contains(" + emptyCaption + ")" ), "Empty list caption contains the correct text" ); emptyCaption = "Empty caption 2"; myCollectionView.render(); ok( $( "var.empty-list-caption" ).is( ":contains(" + emptyCaption + ")" ), "Empty list caption contains the correct text" ); } ); });