├── demo ├── .bowerrc ├── demo.css ├── demo.js ├── amd │ ├── precompiled-templates │ │ ├── source │ │ │ ├── item-template.handlebars │ │ │ ├── list-template.handlebars │ │ │ └── stats-template.handlebars │ │ ├── compilation-info.md │ │ └── output │ │ │ └── precompiled.js │ ├── rjs │ │ ├── config │ │ │ ├── unified │ │ │ │ ├── plain-build-config.js │ │ │ │ ├── marionette-build-config.js │ │ │ │ ├── plain-precompiled-build-config.js │ │ │ │ └── marionette-precompiled-build-config.js │ │ │ └── jsbin-parts │ │ │ │ ├── backbone-app-config.js │ │ │ │ ├── marionette-app-config.js │ │ │ │ ├── backbone-precompiled-app-config.js │ │ │ │ ├── marionette-precompiled-app-config.js │ │ │ │ └── vendor-config.js │ │ ├── build-commands.md │ │ └── output │ │ │ └── parts │ │ │ ├── marionette-app.js │ │ │ ├── backbone-app.js │ │ │ ├── marionette-precompiled-app.js │ │ │ └── backbone-precompiled-app.js │ ├── marionette.js │ ├── plain.js │ ├── amd.css │ ├── plain-precompiled.js │ ├── marionette-precompiled.js │ ├── jsbin │ │ ├── map-config.js │ │ ├── marionette-precompiled.index.html │ │ ├── plain-precompiled.index.html │ │ ├── marionette.index.html │ │ └── plain.index.html │ ├── views-marionette.js │ ├── base.js │ ├── require-config.js │ ├── backbone-precompiled.html │ ├── marionette-precompiled.html │ ├── views-backbone.js │ ├── marionette.html │ └── index.html ├── about.txt ├── bower.json ├── index.html └── bower-check.js ├── .gitignore ├── .jshintrc ├── LICENSE ├── bower.json ├── web-mocha ├── test-framework.css ├── help.html └── _index.html ├── package.json ├── karma.legacy.conf.js ├── karma.conf.js ├── dist ├── backbone.declarative.views.min.js └── backbone.declarative.views.min.js.map ├── spec ├── helpers │ ├── data-provider.js │ ├── fixture-utils.js │ └── various-utils.js ├── custom.compiler.spec.js ├── custom.loader.spec.js ├── default.loader.spec.js └── marionette.cache.spec.js └── Gruntfile.js /demo/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_demo_components" 3 | } 4 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin: 0.25rem; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | ( function( Backbone, _ ) { 2 | "use strict"; 3 | 4 | }( Backbone, _ )); -------------------------------------------------------------------------------- /demo/amd/precompiled-templates/source/item-template.handlebars: -------------------------------------------------------------------------------- 1 | 2 | {{number}} 3 | -------------------------------------------------------------------------------- /demo/amd/rjs/config/unified/plain-build-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.plain", 5 | out: "../../output/unified/plain-build.js" 6 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/unified/marionette-build-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.marionette", 5 | out: "../../output/unified/marionette-build.js" 6 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/unified/plain-precompiled-build-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.plain-precompiled", 5 | out: "../../output/unified/plain-precompiled-build.js" 6 | }) -------------------------------------------------------------------------------- /demo/amd/precompiled-templates/source/list-template.handlebars: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /demo/amd/rjs/config/unified/marionette-precompiled-build-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.marionette-precompiled", 5 | out: "../../output/unified/marionette-precompiled-build.js" 6 | }) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.swo 4 | *.orig 5 | tmp/ 6 | ext/ 7 | node_modules/ 8 | bower_components/ 9 | _SpecRunner.html 10 | .idea/ 11 | web-mocha/index.html 12 | reports/ 13 | demo/bower_demo_components/ 14 | spec-helpers-library/ 15 | todo.md 16 | -------------------------------------------------------------------------------- /demo/amd/precompiled-templates/source/stats-template.handlebars: -------------------------------------------------------------------------------- 1 | 2 | Generating {{itemViewCount}} item views took {{duration}}ms. In total, it took {{renderDuration}}ms until they had all been appended to the DOM. Finally, when painting was over, {{totalDuration}}ms had passed. 3 | -------------------------------------------------------------------------------- /demo/about.txt: -------------------------------------------------------------------------------- 1 | If you'd like to build a demo or an interactive playground for this project, 2 | edit the files in this directory. 3 | 4 | With `grunt demo`, the index.html file is displayed in the browser, and the 5 | demo, src and spec directories are monitored for changes. If anything is altered 6 | there, the grunt task will live-reload the demo page. 7 | -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/backbone-app-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.plain", 5 | exclude: [ 6 | "usertiming", 7 | "jquery", 8 | "underscore", 9 | "backbone", 10 | "backbone.radio", 11 | "marionette", 12 | "handlebars", 13 | "marionette.handlebars", 14 | "backbone.declarative.views", 15 | "precompiled.declarative.handlebars.templates" 16 | ], 17 | out: "../../output/parts/backbone-app.js" 18 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/marionette-app-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.marionette", 5 | exclude: [ 6 | "usertiming", 7 | "jquery", 8 | "underscore", 9 | "backbone", 10 | "backbone.radio", 11 | "marionette", 12 | "handlebars", 13 | "marionette.handlebars", 14 | "backbone.declarative.views", 15 | "precompiled.declarative.handlebars.templates" 16 | ], 17 | out: "../../output/parts/marionette-app.js" 18 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/backbone-precompiled-app-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.plain-precompiled", 5 | exclude: [ 6 | "usertiming", 7 | "jquery", 8 | "underscore", 9 | "backbone", 10 | "backbone.radio", 11 | "marionette", 12 | "handlebars", 13 | "marionette.handlebars", 14 | "backbone.declarative.views", 15 | "precompiled.declarative.handlebars.templates" 16 | ], 17 | out: "../../output/parts/backbone-precompiled-app.js" 18 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/marionette-precompiled-app-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.marionette-precompiled", 5 | exclude: [ 6 | "usertiming", 7 | "jquery", 8 | "underscore", 9 | "backbone", 10 | "backbone.radio", 11 | "marionette", 12 | "handlebars", 13 | "marionette.handlebars", 14 | "backbone.declarative.views", 15 | "precompiled.declarative.handlebars.templates" 16 | ], 17 | out: "../../output/parts/marionette-precompiled-app.js" 18 | }) -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/vendor-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.plain", 5 | include: ["local.marionette", "local.plain-precompiled", "local.marionette-precompiled"], 6 | excludeShallow: [ 7 | "local.precompiled.templates", 8 | "local.base", 9 | "local.marionette", 10 | "local.plain", 11 | "local.plain-precompiled", 12 | "local.marionette-precompiled", 13 | "local.views-backbone", 14 | "local.views-marionette" 15 | ], 16 | out: "../../output/parts/vendor.js" 17 | }) -------------------------------------------------------------------------------- /demo/amd/marionette.js: -------------------------------------------------------------------------------- 1 | // marionette.js 2 | 3 | require( [ 4 | 5 | 'local.base', 6 | 'local.views-marionette' 7 | 8 | ], function ( base, marionetteViews ) { 9 | 10 | var count = 1000, 11 | 12 | ItemView = marionetteViews.ItemView.extend( { template: "#item-template" } ), 13 | 14 | listView = new marionetteViews.ListView( { 15 | template: "#list-template", 16 | childView: ItemView, 17 | parent: ".container", 18 | collection: base.Collection.create( count ) 19 | } ); 20 | 21 | new base.StatsView(); 22 | listView.render(); 23 | 24 | } ); 25 | -------------------------------------------------------------------------------- /demo/amd/plain.js: -------------------------------------------------------------------------------- 1 | // plain.js 2 | 3 | require( [ 4 | 5 | 'local.base', 6 | 'local.views-backbone' 7 | 8 | ], function ( base, backboneViews ) { 9 | 10 | var count = 1000, 11 | 12 | ItemView = backboneViews.ItemView.extend( { 13 | template: "#item-template" 14 | } ), 15 | 16 | listView = new backboneViews.ListView( { 17 | template: "#list-template", 18 | parent: ".container", 19 | ItemView: ItemView, 20 | collection: base.Collection.create( count ) 21 | } ); 22 | 23 | new base.StatsView(); 24 | listView.render(); 25 | 26 | } ); 27 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "es3": true, 6 | "freeze": true, 7 | "immed": true, 8 | "latedef": "nofunc", 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "nonbsp": true, 13 | "undef": true, 14 | 15 | "eqnull": true, 16 | "expr": true, 17 | "sub": true, 18 | 19 | "browser": true, 20 | "jquery": true, 21 | 22 | "strict": true, 23 | 24 | "globals": { 25 | "Backbone": true, 26 | "_": true, 27 | "$": true, 28 | 29 | "JSON": false, 30 | "require": false, 31 | "define": false, 32 | "module": false, 33 | "exports": true 34 | } 35 | } -------------------------------------------------------------------------------- /demo/amd/precompiled-templates/compilation-info.md: -------------------------------------------------------------------------------- 1 | # Precompiling Handlebars templates 2 | 3 | All templates in the /source directory are precompiled and concatenated into a single file, to be loaded with AMD, with the following command: 4 | 5 | ``` 6 | handlebars --amd source/ > output/precompiled.js 7 | ``` 8 | 9 | The ID of a given template, when pulled from the store of precompiled templates, is the same as the filename of the source template, minus the `.handlebars` extension. 10 | 11 | For more on the numerous gotchas when precompiling Handlebars templates, see the Marionette.Handlebars project. The full lowdown can be found there, in `demo/amd/require-config.js`. -------------------------------------------------------------------------------- /demo/amd/amd.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin: 0.25rem; 5 | } 6 | 7 | #header { 8 | margin-bottom: 2rem; 9 | } 10 | 11 | .list { 12 | list-style: none; 13 | margin: 0; 14 | } 15 | 16 | li.item { 17 | display: block; 18 | box-sizing: border-box; 19 | border: 1px dotted red; 20 | padding: 0; 21 | text-align: center; 22 | line-height: 3rem; 23 | height: 3rem; 24 | vertical-align: middle; 25 | } 26 | 27 | h1 { 28 | font-size: 2.25rem; 29 | } 30 | 31 | h2 { 32 | font-size: 1.75rem; 33 | } 34 | 35 | .stats { 36 | padding: 1rem; 37 | border: 1px solid #ccc; 38 | margin-bottom: 2rem; 39 | 40 | box-sizing: border-box; 41 | min-height: 8rem; 42 | } 43 | -------------------------------------------------------------------------------- /demo/amd/plain-precompiled.js: -------------------------------------------------------------------------------- 1 | // plain-precompiled.js 2 | 3 | require( [ 4 | 5 | 'local.base', 6 | 'local.views-backbone', 7 | 'precompiled.declarative.handlebars.templates', 8 | 'local.precompiled.templates' 9 | 10 | ], function ( base, backboneViews ) { 11 | 12 | var count = 1000, 13 | 14 | ItemView = backboneViews.ItemView.extend( { 15 | template: "item-template" // without leading "#": is ID of precompiled template, not selector 16 | } ), 17 | 18 | listView = new backboneViews.ListView( { 19 | template: "list-template", // without leading "#" 20 | parent: ".container", 21 | ItemView: ItemView, 22 | collection: base.Collection.create( count ) 23 | } ); 24 | 25 | new base.StatsView( { template: "stats-template" } ); // without leading "#" 26 | listView.render(); 27 | 28 | } ); 29 | -------------------------------------------------------------------------------- /demo/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "main": "index.html", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/hashchange/backbone.declarative.views", 6 | "authors": [ 7 | "hashchange " 8 | ], 9 | "description": "Backbone.Declarative.Views demo and playground", 10 | "keywords": [ 11 | "demo" 12 | ], 13 | "license": "MIT", 14 | "private": true, 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "bower_demo_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "foundation": "~5.5.3", 25 | "modernizr": "^2 || ~3.3.1", 26 | "requirejs": "~2.2.0", 27 | "usertiming": "~0.1.8", 28 | "handlebars": "~4.0.5", 29 | "marionette.handlebars": "^2.0.0", 30 | "precompiled.declarative.handlebars.templates": "https://gist.github.com/f99dca479f91b55a61d7f4994ea5d438.git" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/amd/marionette-precompiled.js: -------------------------------------------------------------------------------- 1 | // marionette-precompiled.js 2 | 3 | require( [ 4 | 5 | 'local.base', 6 | 'local.views-marionette', 7 | 'marionette.handlebars', 8 | 'precompiled.declarative.handlebars.templates', 9 | 'local.precompiled.templates' 10 | 11 | ], function ( base, marionetteViews ) { 12 | 13 | var count = 1000, 14 | 15 | ItemView = marionetteViews.ItemView.extend( { 16 | template: "item-template" // without leading "#": is ID of precompiled template, not selector 17 | } ), 18 | 19 | listView = new marionetteViews.ListView( { 20 | template: "list-template", // without leading "#" 21 | childView: ItemView, 22 | parent: ".container", 23 | collection: base.Collection.create( count ) 24 | } ); 25 | 26 | new base.StatsView( { template: "stats-template" } ); // without leading "#" 27 | listView.render(); 28 | 29 | } ); 30 | -------------------------------------------------------------------------------- /demo/amd/jsbin/map-config.js: -------------------------------------------------------------------------------- 1 | requirejs.config( { 2 | 3 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | // 5 | // Keep this in sync with the map config in amd/require-config.js 6 | // 7 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 8 | 9 | map: { 10 | '*': { 11 | // Using a different jQuery here than elsewhere (1.x, instead of 3.x in node_modules and bower_components). 12 | // Makes the demo work in oldIE, too. Likewise for Marionette: using Marionette 2.x, rather than 3.x, for 13 | // legacy browsers. 14 | 'jquery': 'jquery-legacy-v1', 15 | 'marionette': 'marionette-legacy', 16 | 17 | // Templates precompiled with the --amd switch require 'handlebars.runtime' rather than 'handlebars'. As we 18 | // don't use the runtime here, we need to map 'handlebars' to a 'handlebars.runtime' alias. 19 | 'handlebars.runtime': 'handlebars' 20 | } 21 | } 22 | 23 | } ); 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Michael Heim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.declarative.views", 3 | "version": "4.1.2", 4 | "homepage": "https://github.com/hashchange/backbone.declarative.views", 5 | "authors": [ 6 | "Michael Heim " 7 | ], 8 | "description": "Defining the DOM element of a Backbone view right in the template. Compatible with absolutely everything.", 9 | "main": "dist/backbone.declarative.views.js", 10 | "keywords": [ 11 | "backbone", 12 | "views", 13 | "backbone.view", 14 | "el", 15 | "html", 16 | "template", 17 | "tag", 18 | "tagName", 19 | "className", 20 | "attributes", 21 | "marionette" 22 | ], 23 | "license": "MIT", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests", 30 | "public", 31 | "reports", 32 | "demo", 33 | "lib-other", 34 | "web-mocha", 35 | "spec", 36 | "src", 37 | "Gruntfile.js", 38 | "karma.conf.js", 39 | "package.json" 40 | ], 41 | "devDependencies": { 42 | "marionette": "^3.0.0 <3.3.0", 43 | "marionette-legacy": "marionette#^1.1.0 <1.9.0 || ^2.0.0", 44 | "jquery-legacy-v1": "jquery#^1", 45 | "jquery-legacy-v2": "jquery#^2" 46 | }, 47 | "dependencies": { 48 | "backbone": "^1.0.0 <1.4.0", 49 | "jquery": "^1.5.0 || ^2.0.0 || ^3.0.0 <3.3.0", 50 | "underscore": "^1.5.0 <1.9.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /web-mocha/test-framework.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .aux { 4 | margin-left: 65px; 5 | position: absolute; 6 | top: 15px; 7 | } 8 | 9 | #test-framework-help-link { 10 | width: 12em; 11 | /*margin-left: 450px;*/ 12 | } 13 | 14 | #alternate-setup { 15 | top: 15px; 16 | } 17 | .aux, 18 | .test-framework-help nav { 19 | margin-top: 1em; 20 | margin-bottom: 1em; 21 | font: 12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 22 | color: #888; 23 | } 24 | .aux a, 25 | .test-framework-help a { 26 | text-decoration: none; 27 | color: inherit; 28 | } 29 | 30 | .aux a:hover, 31 | .aux a:active, 32 | .test-framework-help nav a:hover, 33 | .test-framework-help nav a:active 34 | { 35 | text-decoration: underline; 36 | } 37 | 38 | .test-framework-help section a { 39 | color: #555; 40 | border-bottom: 1px dotted black; 41 | } 42 | 43 | .test-framework-help section a:hover, 44 | .test-framework-help section a:active { 45 | border-bottom-style: solid; 46 | border-bottom-color: #888; 47 | } 48 | 49 | .test-framework-help { 50 | margin: 0; 51 | padding: 15px 65px; 52 | font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif 53 | } 54 | 55 | .test-framework-help h1 { 56 | font-size: 20px; 57 | font-weight: 200; 58 | } 59 | 60 | .test-framework-help ul { 61 | padding-left: 0; 62 | } 63 | 64 | .test-framework-help li { 65 | margin-bottom: 0.5em; 66 | } 67 | -------------------------------------------------------------------------------- /web-mocha/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Framework Help 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Test Framework: Help and Reference

14 |

The test framework is based on Mocha and Chai. It includes these components:

15 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Demo and Playground 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

Demos of AMD setups

23 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo/amd/views-marionette.js: -------------------------------------------------------------------------------- 1 | // views-marionette.js 2 | 3 | define( [ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'marionette', 8 | 'usertiming', 9 | 'local.base', 10 | 'backbone.declarative.views' 11 | 12 | ], function ( _, Backbone, Marionette, performance, base ) { 13 | 14 | // Integrate with Marionette 15 | Backbone.DeclarativeViews.joinMarionette(); 16 | 17 | var MarionetteBaseView = Marionette.ItemView || Marionette.View, // Base view type, depending on Marionette version 18 | 19 | ItemView = MarionetteBaseView.extend( { 20 | 21 | appendTo: function ( $parent ) { 22 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 23 | $parent.append( this.$el ); 24 | return this; 25 | } 26 | 27 | } ), 28 | 29 | ListView = Marionette.CollectionView.extend( { 30 | 31 | initialize: function ( options ) { 32 | options || ( options = {} ); // jshint ignore:line 33 | 34 | if ( options.parent ) this.parent = options.parent; 35 | this.$parent = Backbone.$( this.parent ); 36 | }, 37 | 38 | onBeforeRender: function () { 39 | // Start timer 40 | performance.clearMarks(); 41 | performance.clearMeasures(); 42 | performance.mark( "create-itemViews-start" ); 43 | }, 44 | 45 | onRender: function () { 46 | var duration; 47 | 48 | // Measure the time it took to create the itemViews 49 | performance.measure( "create-itemViews", "create-itemViews-start" ); 50 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 51 | 52 | if ( ! this.$el.parent().length ) this.$el.appendTo( this.$parent ); 53 | 54 | base.eventBus.trigger( "createStats", { itemViewCount : this.children.length, duration: Math.round( duration ) } ); 55 | } 56 | 57 | } ); 58 | 59 | return { 60 | ItemView: ItemView, 61 | ListView: ListView 62 | } 63 | 64 | } ); 65 | -------------------------------------------------------------------------------- /demo/amd/jsbin/marionette-precompiled.index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone.Declarative.Views 4.1.1, Marionette demo with precompiled templates (AMD) 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 |
33 |

Performance statistics

34 |
35 | 36 |
37 |

Content

38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demo/amd/jsbin/plain-precompiled.index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone.Declarative.Views 4.1.1, plain Backbone demo with precompiled templates (AMD) 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 |
33 |

Performance statistics

34 |
35 | 36 |
37 |

Content

38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demo/amd/precompiled-templates/output/precompiled.js: -------------------------------------------------------------------------------- 1 | define(['handlebars.runtime'], function(Handlebars) { 2 | Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; 3 | templates['item-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 4 | var helper; 5 | 6 | return "\n" 7 | + container.escapeExpression(((helper = (helper = helpers.number || (depth0 != null ? depth0.number : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"number","hash":{},"data":data}) : helper))) 8 | + "\n"; 9 | },"useData":true}); 10 | templates['list-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 11 | return "\n"; 12 | },"useData":true}); 13 | templates['stats-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 14 | var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 15 | 16 | return "\nGenerating " 17 | + alias4(((helper = (helper = helpers.itemViewCount || (depth0 != null ? depth0.itemViewCount : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"itemViewCount","hash":{},"data":data}) : helper))) 18 | + " item views took " 19 | + alias4(((helper = (helper = helpers.duration || (depth0 != null ? depth0.duration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"duration","hash":{},"data":data}) : helper))) 20 | + "ms. In total, it took " 21 | + alias4(((helper = (helper = helpers.renderDuration || (depth0 != null ? depth0.renderDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"renderDuration","hash":{},"data":data}) : helper))) 22 | + "ms until they had all been appended to the DOM. Finally, when painting was over, " 23 | + alias4(((helper = (helper = helpers.totalDuration || (depth0 != null ? depth0.totalDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"totalDuration","hash":{},"data":data}) : helper))) 24 | + "ms had passed.\n"; 25 | },"useData":true}); 26 | return templates; 27 | }); 28 | -------------------------------------------------------------------------------- /demo/amd/base.js: -------------------------------------------------------------------------------- 1 | // base.js 2 | 3 | define( [ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'backbone.declarative.views' 9 | 10 | ], function ( _, Backbone, performance ) { 11 | 12 | var eventBus = _.extend( {}, Backbone.Events ), 13 | 14 | Model = Backbone.Model.extend(), 15 | 16 | Collection = Backbone.Collection.extend( 17 | { model: Model }, 18 | { 19 | create: function ( modelCount ) { 20 | var i, 21 | collection = new Collection(), 22 | models = []; 23 | 24 | for ( i = 0; i < modelCount; i++ ) models.push( new collection.model( { number: i + 1 } ) ); 25 | collection.reset( models ); 26 | 27 | return collection; 28 | } 29 | } 30 | ), 31 | 32 | StatsView = Backbone.View.extend( { 33 | 34 | template: "#stats-template", 35 | 36 | parent: ".stats", 37 | 38 | initialize: function ( options ) { 39 | var compiledTemplate = this.declarativeViews.getCachedTemplate().compiled; 40 | this.template = compiledTemplate || _.template( this.declarativeViews.getCachedTemplate(). html ); 41 | 42 | options || ( options = {} ); 43 | 44 | if ( options.parent ) this.parent = options.parent; 45 | this.$parent = Backbone.$( this.parent ); 46 | this.$el.appendTo( this.$parent ); 47 | 48 | this.listenTo( eventBus, "createStats", this.render ); 49 | }, 50 | 51 | render: function ( stats ) { 52 | stats.totalDuration = Math.round( this.getTotalDuration() ); 53 | this.$el.html( this.template( stats ) ); 54 | }, 55 | 56 | getTotalDuration: function () { 57 | // Query the document height. This is to assess the true total render time, including the painting. 58 | // The calculation of the document height should be blocked by the browser until all item elements have 59 | // been painted. 60 | var docHeight = $( document ).height(); 61 | 62 | performance.measure( "paint", "create-itemViews-start" ); 63 | return performance.getEntriesByName( "paint" )[0].duration; 64 | } 65 | 66 | } ); 67 | 68 | Backbone.DeclarativeViews.custom.compiler = _.template; 69 | 70 | return { 71 | Model: Model, 72 | Collection: Collection, 73 | StatsView: StatsView, 74 | eventBus: eventBus 75 | } 76 | 77 | } ); 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.declarative.views", 3 | "version": "4.1.2", 4 | "homepage": "https://github.com/hashchange/backbone.declarative.views", 5 | "bugs": "https://github.com/hashchange/backbone.declarative.views/issues", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/hashchange/backbone.declarative.views.git" 9 | }, 10 | "author": "Michael Heim (http://www.zeilenwechsel.de/)", 11 | "description": "Defining the DOM element of a Backbone view right in the template. Compatible with absolutely everything.", 12 | "main": "dist/backbone.declarative.views.js", 13 | "keywords": [ 14 | "backbone", 15 | "views", 16 | "backbone.view", 17 | "el", 18 | "html", 19 | "template", 20 | "tag", 21 | "tagName", 22 | "className", 23 | "attributes", 24 | "marionette" 25 | ], 26 | "license": "MIT", 27 | "scripts": { 28 | "cleanup": "del-cli bower_components demo/bower_demo_components node_modules -f", 29 | "setup": "npm install && bower install && cd demo && bower install && cd ..", 30 | "reinstall": "npm run cleanup -s && npm run setup || npm run setup" 31 | }, 32 | "dependencies": { 33 | "backbone": "^1.0.0 <1.4.0", 34 | "jquery": "^1.5.0 || ^2.0.0 || ^3.0.0 <3.3.0", 35 | "underscore": "^1.5.0 <1.9.0" 36 | }, 37 | "devDependencies": { 38 | "bower": "^1.8.0", 39 | "chai-subset": "~1.5.0", 40 | "connect-livereload": "~0.6.0", 41 | "del-cli": "~0.2.1", 42 | "grunt": "^1.0.1", 43 | "grunt-cli": "^1.2.0", 44 | "grunt-contrib-concat": "~1.0.1", 45 | "grunt-contrib-connect": "~1.0.2", 46 | "grunt-contrib-jshint": "~1.1.0", 47 | "grunt-contrib-requirejs": "^1.0.0", 48 | "grunt-contrib-uglify": "~2.2.0", 49 | "grunt-contrib-watch": "~1.0.0", 50 | "grunt-focus": "~1.0.0", 51 | "grunt-karma": "~2.0.0", 52 | "grunt-mocha": "~1.0.4", 53 | "grunt-preprocess": "~5.1.0", 54 | "grunt-sails-linker": "~1.0.4", 55 | "grunt-text-replace": "~0.4.0", 56 | "karma": "~1.5.0", 57 | "karma-chai-plugins": "~0.8.0", 58 | "karma-chrome-launcher": "~2.0.0", 59 | "karma-firefox-launcher": "~1.0.1", 60 | "karma-html2js-preprocessor": "~1.1.0", 61 | "karma-ie-launcher": "~1.0.0", 62 | "karma-mocha": "~1.3.0", 63 | "karma-mocha-reporter": "~2.2.3", 64 | "karma-opera-launcher": "~1.0.0", 65 | "karma-phantomjs-launcher": "~1.0.4", 66 | "karma-requirejs": "~1.1.0", 67 | "karma-safari-launcher": "1.0.0", 68 | "karma-script-launcher": "~1.0.0", 69 | "karma-slimerjs-launcher": "~1.1.0", 70 | "mocha": "~3.2.0", 71 | "phantomjs-prebuilt": "^2.1.14", 72 | "require-from-string": "^1.2.1", 73 | "requirejs": "^2.3.3" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /demo/amd/jsbin/marionette.index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone.Declarative.Views 4.1.1, Marionette demo (AMD) 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 43 | 44 |
45 |

Performance statistics

46 |
47 | 48 |
49 |

Content

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /demo/amd/jsbin/plain.index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Backbone.Declarative.Views 4.1.1, plain Backbone demo (AMD) 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 43 | 44 |
45 |

Performance statistics

46 |
47 | 48 |
49 |

Content

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /demo/amd/require-config.js: -------------------------------------------------------------------------------- 1 | requirejs.config( { 2 | 3 | // Base URL: project root 4 | baseUrl: '../../', 5 | 6 | paths: { 7 | 'usertiming': 'demo/bower_demo_components/usertiming/src/usertiming', 8 | 9 | 'jquery-legacy-v1': 'bower_components/jquery-legacy-v1/dist/jquery', 10 | 'jquery-legacy-v2': 'bower_components/jquery-legacy-v2/dist/jquery', 11 | 'jquery-modern': 'bower_components/jquery/dist/jquery', 12 | 13 | 'underscore': 'bower_components/underscore/underscore', 14 | 'backbone': 'bower_components/backbone/backbone', 15 | 'backbone.radio': 'bower_components/backbone.radio/build/backbone.radio', 16 | 'marionette-modern': 'bower_components/marionette/lib/backbone.marionette', 17 | 'marionette-legacy': 'bower_components/marionette-legacy/lib/backbone.marionette', 18 | 19 | 'handlebars': 'demo/bower_demo_components/handlebars/handlebars', 20 | 'marionette.handlebars': 'demo/bower_demo_components/marionette.handlebars/dist/marionette.handlebars', 21 | 'backbone.declarative.views': 'dist/backbone.declarative.views', 22 | 'precompiled.declarative.handlebars.templates': 'demo/bower_demo_components/precompiled.declarative.handlebars.templates/precompiled.declarative.handlebars.templates', 23 | 24 | 'local.precompiled.templates': 'demo/amd/precompiled-templates/output/precompiled', 25 | 26 | 'local.base': 'demo/amd/base', 27 | 'local.marionette': 'demo/amd/marionette', 28 | 'local.plain': 'demo/amd/plain', 29 | 'local.plain-precompiled': 'demo/amd/plain-precompiled', 30 | 'local.marionette-precompiled': 'demo/amd/marionette-precompiled', 31 | 'local.views-backbone': 'demo/amd/views-backbone', 32 | 'local.views-marionette': 'demo/amd/views-marionette' 33 | }, 34 | 35 | map: { 36 | '*': { 37 | // Using a different jQuery here than elsewhere (1.x, instead of 3.x in node_modules and bower_components). 38 | // Makes the demo work in oldIE, too. Likewise for Marionette: using Marionette 2.x, rather than 3.x, for 39 | // legacy browsers. 40 | 'jquery': 'jquery-legacy-v1', 41 | 'marionette': 'marionette-legacy', 42 | 43 | // Templates precompiled with the --amd switch require 'handlebars.runtime' rather than 'handlebars'. As we 44 | // don't use the runtime here, we need to map 'handlebars' to a 'handlebars.runtime' alias. 45 | 'handlebars.runtime': 'handlebars' 46 | } 47 | }, 48 | 49 | shim: { 50 | 'jquery-legacy-v1': { 51 | exports: "jQuery" 52 | }, 53 | 'jquery-legacy-v2': { 54 | exports: "jQuery" 55 | }, 56 | 'jquery-modern': { 57 | exports: "jQuery" 58 | }, 59 | 60 | // Required for the Marionette demo 61 | 'marionette': ['backbone.declarative.views'] 62 | } 63 | 64 | } ); 65 | -------------------------------------------------------------------------------- /web-mocha/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mocha Spec Runner 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /demo/amd/backbone-precompiled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AMD setup: Plain Backbone demo with precompiled templates 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 |
36 |

Performance statistics

37 |
38 | 39 |
40 |

Content

41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | 57 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /demo/amd/marionette-precompiled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AMD setup: Marionette demo with precompiled templates 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 |
36 |

Performance statistics

37 |
38 | 39 |
40 |

Content

41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | 57 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /demo/amd/views-backbone.js: -------------------------------------------------------------------------------- 1 | // views-backbone.js 2 | 3 | define( [ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'local.base', 9 | 'backbone.declarative.views' 10 | 11 | ], function ( _, Backbone, performance, base ) { 12 | 13 | var ItemView = Backbone.View.extend( { 14 | 15 | initialize: function ( options ) { 16 | this.template = this.declarativeViews.getCachedTemplate().compiled; 17 | }, 18 | 19 | render: function () { 20 | this.$el.html( this.template( this.model.attributes ) ); 21 | return this; 22 | }, 23 | 24 | appendTo: function ( $parent ) { 25 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 26 | $parent.append( this.$el ); 27 | return this; 28 | } 29 | 30 | } ), 31 | 32 | ListView = Backbone.View.extend( { 33 | 34 | initialize: function ( options ) { 35 | options || ( options = {} ); // jshint ignore:line 36 | 37 | if ( options.ItemView ) this.ItemView = options.ItemView; 38 | if ( options.parent ) this.parent = options.parent; 39 | this.$parent = Backbone.$( this.parent ); 40 | }, 41 | 42 | render: function () { 43 | var duration, renderDuration, 44 | els = []; 45 | 46 | this.itemViews = []; 47 | 48 | // Start timer 49 | performance.clearMarks(); 50 | performance.clearMeasures(); 51 | performance.mark( "create-itemViews-start" ); 52 | 53 | this.collection.each( function ( model ) { 54 | //Backbone.DeclarativeViews.clearCache(); 55 | var itemView = new this.ItemView( { model: model } ); 56 | itemView.render(); 57 | 58 | this.itemViews.push( itemView ); 59 | els.push( itemView.el ); 60 | }, this ); 61 | 62 | // Measure itemView creation time 63 | performance.measure( "create-itemViews", "create-itemViews-start" ); 64 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 65 | 66 | this.$el.append( els ); 67 | this.$el.appendTo( this.$parent ); 68 | 69 | // Measure render duration time (total from beginning of itemView creation) 70 | performance.measure( "render", "create-itemViews-start" ); 71 | renderDuration = performance.getEntriesByName( "render" )[0].duration; 72 | 73 | base.eventBus.trigger( "createStats", { 74 | itemViewCount : this.itemViews.length, 75 | duration: Math.round( duration ), 76 | renderDuration: Math.round( renderDuration ) 77 | } ); 78 | }, 79 | 80 | destroy: function () { 81 | _.each( this.itemViews, function ( itemView ) { 82 | itemView.remove(); 83 | } ); 84 | 85 | this.remove(); 86 | } 87 | 88 | } ); 89 | 90 | return { 91 | ItemView: ItemView, 92 | ListView: ListView 93 | } 94 | 95 | } ); 96 | -------------------------------------------------------------------------------- /demo/amd/rjs/build-commands.md: -------------------------------------------------------------------------------- 1 | # Generating r.js builds 2 | 3 | ## Using a Grunt task 4 | 5 | Instead of individual r.js calls, the following command will create all builds: 6 | 7 | ``` 8 | grunt requirejs 9 | ``` 10 | 11 | The grunt task simply reads the build profiles described below, and feeds them to r.js. 12 | 13 | 14 | ## Split builds with two build files, for JS Bin demos 15 | 16 | The demo HTML files for JS Bin reference two concatenated build files (per page): 17 | 18 | - `vendor.js` for the third-party dependencies. It includes Backbone.Declarative.Views. 19 | - `backbone-app.js`, `marionette-app.js`, `backbone-precompiled-app.js` and `marionette-precompiled-app.js` for the demo code, consisting of local modules. 20 | 21 | The code is not rolled up into a single file because that file would be massive, making it unnecessarily difficult to examine the demo code. The purpose of the demo is to see how Backbone.Declarative.Views is used, so it makes sense to keep the client code separate. 22 | 23 | ### Adjustments 24 | 25 | Care must be taken to avoid duplication. A module pulled into `vendor.js` must not be part of `*-app.js`, and vice versa. Update the module exclusions in **all** build config files when new modules are added to a demo. 26 | 27 | ### r.js calls 28 | 29 | Open a command prompt in the **project root** directory. 30 | 31 | ``` 32 | # For vendor.js: 33 | 34 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/vendor-config.js 35 | 36 | # For backbone-app.js: 37 | 38 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/backbone-app-config.js 39 | 40 | # For marionette-app.js: 41 | 42 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/marionette-app-config.js 43 | 44 | # For backbone-precompiled-app.js: 45 | 46 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/backbone-precompiled-app-config.js 47 | 48 | # For marionette-precompiled-app.js: 49 | 50 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/marionette-precompiled-app-config.js 51 | ``` 52 | 53 | ### Output files 54 | 55 | The output is written to the directory `demo/amd/rjs/output/parts`. 56 | 57 | 58 | ## Single-file builds, for local demos 59 | 60 | Builds for local demos are created to test that the setup continues to work after optimization with r.js. All modules of a demo end up in a single file. For easier examination, the file is not minified. 61 | 62 | For more info, see the comments in `index.html`, `marionette.html`, `backbone-precompiled.html` and `marionette-precompiled.html`. 63 | 64 | ### r.js calls 65 | 66 | For building the output file, open a command prompt in the **project root** directory, and run these commands: 67 | 68 | ``` 69 | # For the vanilla JS demo: 70 | 71 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/unified/plain-build-config.js 72 | 73 | # For the Marionette demo: 74 | 75 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/unified/marionette-build-config.js 76 | 77 | # For the vanilla JS demo with precompiled templates: 78 | 79 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/unified/plain-precompiled-build-config.js 80 | 81 | # For the Marionette demo with precompiled templates: 82 | 83 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/unified/marionette-precompiled-build-config.js 84 | ``` 85 | 86 | ### Output files 87 | 88 | The output is written to the directory `demo/amd/rjs/output/unified`. -------------------------------------------------------------------------------- /karma.legacy.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Dec 30 2013 16:14:03 GMT+0100 (CET) 3 | 4 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | // + For automated testing with Grunt, some settings in this config file + 6 | // + are overridden in Gruntfile.js. Check both locations. + 7 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | module.exports = function(config) { 10 | config.set({ 11 | 12 | // base path, that will be used to resolve files and exclude 13 | basePath: '', 14 | 15 | 16 | // frameworks to use 17 | // 18 | // Available for chai (installed with karma-chai-plugins): 19 | // sinon-chai, chai-as-promised, chai-jquery. Enable as needed. 20 | // 21 | // NB sinon-chai includes Sinon; chai-jquery does _not_ include jQuery 22 | frameworks: ['mocha', 'chai', 'sinon-chai'], 23 | 24 | 25 | // list of files / patterns to load in the browser 26 | files: [ 27 | // Component dependencies 28 | 29 | // Using jQuery 1.x here by default. Switch to jQuery 2.x or 2.x as needed. 30 | 31 | 'bower_components/jquery-legacy-v1/dist/jquery.js', 32 | // 'bower_components/jquery-legacy-v2/dist/jquery.js', 33 | // 'bower_components/jquery/dist/jquery.js', 34 | 35 | 'bower_components/underscore/underscore.js', 36 | 'bower_components/backbone/backbone.js', 37 | 38 | // Component under test (main) 39 | 'src/backbone.declarative.views.js', 40 | 41 | // Marionette (must be loaded after Backbone.Declarative.Views) 42 | 'bower_components/marionette-legacy/lib/backbone.marionette.js', 43 | // 'bower_components/backbone.radio/build/backbone.radio.js', 44 | // 'bower_components/marionette/lib/backbone.marionette.js', 45 | 46 | // Test helpers 47 | 'node_modules/chai-subset/lib/chai-subset.js', 48 | 'spec/chai-helpers/**/*.js', 49 | 'spec/helpers/**/*.js', 50 | 51 | // Tests 52 | 'spec/**/*.+(spec|test|tests).js' 53 | ], 54 | 55 | 56 | // list of files to exclude 57 | exclude: [ 58 | 59 | ], 60 | 61 | 62 | // test results reporter to use 63 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 'mocha' 64 | reporters: ['progress'], 65 | 66 | 67 | // web server port 68 | port: 9876, 69 | 70 | 71 | // enable / disable colors in the output (reporters and logs) 72 | colors: true, 73 | 74 | 75 | // level of logging 76 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 77 | logLevel: config.LOG_INFO, 78 | 79 | 80 | // enable / disable watching file and executing tests whenever any file changes 81 | autoWatch: false, 82 | 83 | 84 | // Start these browsers, currently available: 85 | // - Chrome 86 | // - ChromeCanary 87 | // - Firefox 88 | // - Opera 89 | // - Safari 90 | // - PhantomJS 91 | // - SlimerJS 92 | // - IE (Windows only) 93 | // 94 | // ATTN Interactive debugging in PhpStorm/WebStorm doesn't work with PhantomJS. Use Firefox or Chrome instead. 95 | browsers: ['PhantomJS'], 96 | 97 | 98 | // If browser does not capture in given timeout [ms], kill it 99 | captureTimeout: 60000, 100 | 101 | 102 | // Continuous Integration mode 103 | // if true, it capture browsers, run tests and exit 104 | singleRun: false 105 | }); 106 | }; 107 | -------------------------------------------------------------------------------- /demo/amd/marionette.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AMD setup: Marionette 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 |
48 |

Performance statistics

49 |
50 | 51 |
52 |

Content

53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | 69 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /demo/amd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AMD setup: Plain Backbone Demo 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 |
48 |

Performance statistics

49 |
50 | 51 |
52 |

Content

53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | 69 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Dec 30 2013 16:14:03 GMT+0100 (CET) 3 | 4 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | // + For automated testing with Grunt, some settings in this config file + 6 | // + are overridden in Gruntfile.js. Check both locations. + 7 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | module.exports = function(config) { 10 | config.set({ 11 | 12 | // base path, that will be used to resolve files and exclude 13 | basePath: '', 14 | 15 | 16 | // frameworks to use 17 | // 18 | // Available for chai (installed with karma-chai-plugins): 19 | // sinon-chai, chai-as-promised, chai-jquery. Enable as needed. 20 | // 21 | // NB sinon-chai includes Sinon; chai-jquery does _not_ include jQuery 22 | frameworks: ['mocha', 'chai', 'sinon-chai'], 23 | 24 | 25 | // list of files / patterns to load in the browser 26 | files: [ 27 | // Component dependencies 28 | 29 | // Using the latest jQuery by default. Switch to jQuery 1.x or 2.x as needed. 30 | // 31 | // NB Tests run through the interactive web interface use jQuery 1.x. Use `grunt interactive` or `grunt webtest` 32 | // for them. 33 | 34 | 'bower_components/jquery/dist/jquery.js', 35 | // 'bower_components/jquery-legacy-v1/dist/jquery.js', 36 | // 'bower_components/jquery-legacy-v2/dist/jquery.js', 37 | 38 | 'bower_components/underscore/underscore.js', 39 | 'bower_components/backbone/backbone.js', 40 | 41 | // Component under test (main) 42 | 'src/backbone.declarative.views.js', 43 | 44 | // Marionette (must be loaded after Backbone.Declarative.Views) 45 | // 46 | // Using the latest Marionette by default. Switch to Marionette 2.x as needed, or run Karma with the config for 47 | // legacy Marionette. 48 | // 49 | // NB Tests run through the interactive web interface use Marionette 2.x. Use `grunt interactive` or `grunt webtest` 50 | // for them. 51 | 52 | // 'bower_components/marionette-legacy/lib/backbone.marionette.js', 53 | 'bower_components/backbone.radio/build/backbone.radio.js', 54 | 'bower_components/marionette/lib/backbone.marionette.js', 55 | 56 | // Test helpers 57 | 'node_modules/chai-subset/lib/chai-subset.js', 58 | 'spec/chai-helpers/**/*.js', 59 | 'spec/helpers/**/*.js', 60 | 61 | // Tests 62 | 'spec/**/*.+(spec|test|tests).js' 63 | ], 64 | 65 | 66 | // list of files to exclude 67 | exclude: [ 68 | 69 | ], 70 | 71 | 72 | // test results reporter to use 73 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 'mocha' 74 | reporters: ['progress'], 75 | 76 | 77 | // web server port 78 | port: 9876, 79 | 80 | 81 | // enable / disable colors in the output (reporters and logs) 82 | colors: true, 83 | 84 | 85 | // level of logging 86 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 87 | logLevel: config.LOG_INFO, 88 | 89 | 90 | // enable / disable watching file and executing tests whenever any file changes 91 | autoWatch: false, 92 | 93 | 94 | // Start these browsers, currently available: 95 | // - Chrome 96 | // - ChromeCanary 97 | // - Firefox 98 | // - Opera 99 | // - Safari 100 | // - PhantomJS 101 | // - SlimerJS 102 | // - IE (Windows only) 103 | // 104 | // ATTN Interactive debugging in PhpStorm/WebStorm doesn't work with PhantomJS. Use Firefox or Chrome instead. 105 | browsers: ['PhantomJS'], 106 | 107 | 108 | // If browser does not capture in given timeout [ms], kill it 109 | captureTimeout: 60000, 110 | 111 | 112 | // Continuous Integration mode 113 | // if true, it capture browsers, run tests and exit 114 | singleRun: false 115 | }); 116 | }; 117 | -------------------------------------------------------------------------------- /demo/bower-check.js: -------------------------------------------------------------------------------- 1 | // Checks that 'bower install' has been run in the demo dir if demo/bower.json isn't empty. 2 | 3 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 | // + + 5 | // + This is a blocking script, triggering _synchronous_ http subrequests. Use locally only. + 6 | // + + 7 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | ( function ( window ) { 10 | "use strict"; 11 | 12 | var BOWER_DEMO_COMPONENTS_DIR = "/demo/bower_demo_components", 13 | 14 | msg = "
" + 15 | "

Bower components for the demo seem to be missing. Install them first:

" + 16 | "
    " + 17 | "
  • Open a command prompt in the demo directory of the project.
  • " + 18 | "
  • Run bower install
  • " + 19 | "
" + 20 | "

If this is a false positive and the packages are in fact in place, " + 21 | "disable the check by removing bower-check.js at the top of the <body>.

" + 22 | "
"; 23 | 24 | getJSON( "/demo/bower.json", false, function ( data ) { 25 | 26 | var i, j, depNames, 27 | exists = false, 28 | files = [ 29 | 'bower.json', 'package.json', 30 | 'readme.md', 'Readme.md', 'README.md', 'README', 31 | 'license.txt', 'LICENSE.txt', 'LICENSE', 32 | 'Gruntfile.js', 33 | 'composer.json', 'component.json' 34 | ]; 35 | 36 | if ( data && data.dependencies ) { 37 | 38 | depNames = Object.keys( data.dependencies ); 39 | 40 | // Bower packages don't necessarily have a bower.json. file. The only file guaranteed to be there after 41 | // install is `.bower.json` (note the leading dot), but it is hidden and won't be served over http. 42 | // 43 | // So instead, we are looking for a bunch of files which are very likely to be there. If none of them is, 44 | // for none of the projects, the dependencies are most likely not installed. 45 | for ( i = 0; i < depNames.length; i++ ) { 46 | for ( j = 0; j < files.length; j++ ) { 47 | 48 | get( 49 | BOWER_DEMO_COMPONENTS_DIR + "/" + depNames[i] + "/" + files[j], false, 50 | function ( data ) { 51 | exists = !!data; 52 | } 53 | ); 54 | 55 | if ( exists ) return; 56 | 57 | } 58 | } 59 | 60 | window.document.write( msg ); 61 | } 62 | 63 | } ); 64 | 65 | 66 | // Helper functions in the absence of a library like jQuery (not loaded at this point) 67 | function get ( url, async, cb, cbError ) { 68 | var data, 69 | request = new XMLHttpRequest; 70 | 71 | request.open( 'GET', url, async ); 72 | 73 | request.onload = function () { 74 | if ( this.status >= 200 && this.status < 400 ) { 75 | // Success! 76 | data = this.response; 77 | } else { 78 | // We reached our target server, but it returned an error. Most likely, the bower.json file is missing. 79 | // We just return undefined here. 80 | data = undefined; 81 | } 82 | cb( data ); 83 | }; 84 | 85 | request.onerror = function ( err ) { 86 | // There was a connection error of some sort. 87 | if ( cbError ) cbError( err ); 88 | }; 89 | 90 | request.send(); 91 | } 92 | 93 | function getJSON ( url, async, cb, cbError ) { 94 | get( url, async, function ( data ) { 95 | 96 | if ( data ) data = JSON.parse( data ); 97 | cb( data ); 98 | 99 | }, cbError ); 100 | } 101 | 102 | }( window )); -------------------------------------------------------------------------------- /demo/amd/rjs/output/parts/marionette-app.js: -------------------------------------------------------------------------------- 1 | // base.js 2 | 3 | define( 'local.base',[ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'backbone.declarative.views' 9 | 10 | ], function ( _, Backbone, performance ) { 11 | 12 | var eventBus = _.extend( {}, Backbone.Events ), 13 | 14 | Model = Backbone.Model.extend(), 15 | 16 | Collection = Backbone.Collection.extend( 17 | { model: Model }, 18 | { 19 | create: function ( modelCount ) { 20 | var i, 21 | collection = new Collection(), 22 | models = []; 23 | 24 | for ( i = 0; i < modelCount; i++ ) models.push( new collection.model( { number: i + 1 } ) ); 25 | collection.reset( models ); 26 | 27 | return collection; 28 | } 29 | } 30 | ), 31 | 32 | StatsView = Backbone.View.extend( { 33 | 34 | template: "#stats-template", 35 | 36 | parent: ".stats", 37 | 38 | initialize: function ( options ) { 39 | var compiledTemplate = this.declarativeViews.getCachedTemplate().compiled; 40 | this.template = compiledTemplate || _.template( this.declarativeViews.getCachedTemplate(). html ); 41 | 42 | options || ( options = {} ); 43 | 44 | if ( options.parent ) this.parent = options.parent; 45 | this.$parent = Backbone.$( this.parent ); 46 | this.$el.appendTo( this.$parent ); 47 | 48 | this.listenTo( eventBus, "createStats", this.render ); 49 | }, 50 | 51 | render: function ( stats ) { 52 | stats.totalDuration = Math.round( this.getTotalDuration() ); 53 | this.$el.html( this.template( stats ) ); 54 | }, 55 | 56 | getTotalDuration: function () { 57 | // Query the document height. This is to assess the true total render time, including the painting. 58 | // The calculation of the document height should be blocked by the browser until all item elements have 59 | // been painted. 60 | var docHeight = $( document ).height(); 61 | 62 | performance.measure( "paint", "create-itemViews-start" ); 63 | return performance.getEntriesByName( "paint" )[0].duration; 64 | } 65 | 66 | } ); 67 | 68 | Backbone.DeclarativeViews.custom.compiler = _.template; 69 | 70 | return { 71 | Model: Model, 72 | Collection: Collection, 73 | StatsView: StatsView, 74 | eventBus: eventBus 75 | } 76 | 77 | } ); 78 | 79 | // views-marionette.js 80 | 81 | define( 'local.views-marionette',[ 82 | 83 | 'underscore', 84 | 'backbone', 85 | 'marionette', 86 | 'usertiming', 87 | 'local.base', 88 | 'backbone.declarative.views' 89 | 90 | ], function ( _, Backbone, Marionette, performance, base ) { 91 | 92 | // Integrate with Marionette 93 | Backbone.DeclarativeViews.joinMarionette(); 94 | 95 | var MarionetteBaseView = Marionette.ItemView || Marionette.View, // Base view type, depending on Marionette version 96 | 97 | ItemView = MarionetteBaseView.extend( { 98 | 99 | appendTo: function ( $parent ) { 100 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 101 | $parent.append( this.$el ); 102 | return this; 103 | } 104 | 105 | } ), 106 | 107 | ListView = Marionette.CollectionView.extend( { 108 | 109 | initialize: function ( options ) { 110 | options || ( options = {} ); // jshint ignore:line 111 | 112 | if ( options.parent ) this.parent = options.parent; 113 | this.$parent = Backbone.$( this.parent ); 114 | }, 115 | 116 | onBeforeRender: function () { 117 | // Start timer 118 | performance.clearMarks(); 119 | performance.clearMeasures(); 120 | performance.mark( "create-itemViews-start" ); 121 | }, 122 | 123 | onRender: function () { 124 | var duration; 125 | 126 | // Measure the time it took to create the itemViews 127 | performance.measure( "create-itemViews", "create-itemViews-start" ); 128 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 129 | 130 | if ( ! this.$el.parent().length ) this.$el.appendTo( this.$parent ); 131 | 132 | base.eventBus.trigger( "createStats", { itemViewCount : this.children.length, duration: Math.round( duration ) } ); 133 | } 134 | 135 | } ); 136 | 137 | return { 138 | ItemView: ItemView, 139 | ListView: ListView 140 | } 141 | 142 | } ); 143 | 144 | // marionette.js 145 | 146 | require( [ 147 | 148 | 'local.base', 149 | 'local.views-marionette' 150 | 151 | ], function ( base, marionetteViews ) { 152 | 153 | var count = 1000, 154 | 155 | ItemView = marionetteViews.ItemView.extend( { template: "#item-template" } ), 156 | 157 | listView = new marionetteViews.ListView( { 158 | template: "#list-template", 159 | childView: ItemView, 160 | parent: ".container", 161 | collection: base.Collection.create( count ) 162 | } ); 163 | 164 | new base.StatsView(); 165 | listView.render(); 166 | 167 | } ); 168 | 169 | define("local.marionette", function(){}); 170 | 171 | -------------------------------------------------------------------------------- /demo/amd/rjs/output/parts/backbone-app.js: -------------------------------------------------------------------------------- 1 | // base.js 2 | 3 | define( 'local.base',[ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'backbone.declarative.views' 9 | 10 | ], function ( _, Backbone, performance ) { 11 | 12 | var eventBus = _.extend( {}, Backbone.Events ), 13 | 14 | Model = Backbone.Model.extend(), 15 | 16 | Collection = Backbone.Collection.extend( 17 | { model: Model }, 18 | { 19 | create: function ( modelCount ) { 20 | var i, 21 | collection = new Collection(), 22 | models = []; 23 | 24 | for ( i = 0; i < modelCount; i++ ) models.push( new collection.model( { number: i + 1 } ) ); 25 | collection.reset( models ); 26 | 27 | return collection; 28 | } 29 | } 30 | ), 31 | 32 | StatsView = Backbone.View.extend( { 33 | 34 | template: "#stats-template", 35 | 36 | parent: ".stats", 37 | 38 | initialize: function ( options ) { 39 | var compiledTemplate = this.declarativeViews.getCachedTemplate().compiled; 40 | this.template = compiledTemplate || _.template( this.declarativeViews.getCachedTemplate(). html ); 41 | 42 | options || ( options = {} ); 43 | 44 | if ( options.parent ) this.parent = options.parent; 45 | this.$parent = Backbone.$( this.parent ); 46 | this.$el.appendTo( this.$parent ); 47 | 48 | this.listenTo( eventBus, "createStats", this.render ); 49 | }, 50 | 51 | render: function ( stats ) { 52 | stats.totalDuration = Math.round( this.getTotalDuration() ); 53 | this.$el.html( this.template( stats ) ); 54 | }, 55 | 56 | getTotalDuration: function () { 57 | // Query the document height. This is to assess the true total render time, including the painting. 58 | // The calculation of the document height should be blocked by the browser until all item elements have 59 | // been painted. 60 | var docHeight = $( document ).height(); 61 | 62 | performance.measure( "paint", "create-itemViews-start" ); 63 | return performance.getEntriesByName( "paint" )[0].duration; 64 | } 65 | 66 | } ); 67 | 68 | Backbone.DeclarativeViews.custom.compiler = _.template; 69 | 70 | return { 71 | Model: Model, 72 | Collection: Collection, 73 | StatsView: StatsView, 74 | eventBus: eventBus 75 | } 76 | 77 | } ); 78 | 79 | // views-backbone.js 80 | 81 | define( 'local.views-backbone',[ 82 | 83 | 'underscore', 84 | 'backbone', 85 | 'usertiming', 86 | 'local.base', 87 | 'backbone.declarative.views' 88 | 89 | ], function ( _, Backbone, performance, base ) { 90 | 91 | var ItemView = Backbone.View.extend( { 92 | 93 | initialize: function ( options ) { 94 | this.template = this.declarativeViews.getCachedTemplate().compiled; 95 | }, 96 | 97 | render: function () { 98 | this.$el.html( this.template( this.model.attributes ) ); 99 | return this; 100 | }, 101 | 102 | appendTo: function ( $parent ) { 103 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 104 | $parent.append( this.$el ); 105 | return this; 106 | } 107 | 108 | } ), 109 | 110 | ListView = Backbone.View.extend( { 111 | 112 | initialize: function ( options ) { 113 | options || ( options = {} ); // jshint ignore:line 114 | 115 | if ( options.ItemView ) this.ItemView = options.ItemView; 116 | if ( options.parent ) this.parent = options.parent; 117 | this.$parent = Backbone.$( this.parent ); 118 | }, 119 | 120 | render: function () { 121 | var duration, renderDuration, 122 | els = []; 123 | 124 | this.itemViews = []; 125 | 126 | // Start timer 127 | performance.clearMarks(); 128 | performance.clearMeasures(); 129 | performance.mark( "create-itemViews-start" ); 130 | 131 | this.collection.each( function ( model ) { 132 | //Backbone.DeclarativeViews.clearCache(); 133 | var itemView = new this.ItemView( { model: model } ); 134 | itemView.render(); 135 | 136 | this.itemViews.push( itemView ); 137 | els.push( itemView.el ); 138 | }, this ); 139 | 140 | // Measure itemView creation time 141 | performance.measure( "create-itemViews", "create-itemViews-start" ); 142 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 143 | 144 | this.$el.append( els ); 145 | this.$el.appendTo( this.$parent ); 146 | 147 | // Measure render duration time (total from beginning of itemView creation) 148 | performance.measure( "render", "create-itemViews-start" ); 149 | renderDuration = performance.getEntriesByName( "render" )[0].duration; 150 | 151 | base.eventBus.trigger( "createStats", { 152 | itemViewCount : this.itemViews.length, 153 | duration: Math.round( duration ), 154 | renderDuration: Math.round( renderDuration ) 155 | } ); 156 | }, 157 | 158 | destroy: function () { 159 | _.each( this.itemViews, function ( itemView ) { 160 | itemView.remove(); 161 | } ); 162 | 163 | this.remove(); 164 | } 165 | 166 | } ); 167 | 168 | return { 169 | ItemView: ItemView, 170 | ListView: ListView 171 | } 172 | 173 | } ); 174 | 175 | // plain.js 176 | 177 | require( [ 178 | 179 | 'local.base', 180 | 'local.views-backbone' 181 | 182 | ], function ( base, backboneViews ) { 183 | 184 | var count = 1000, 185 | 186 | ItemView = backboneViews.ItemView.extend( { 187 | template: "#item-template" 188 | } ), 189 | 190 | listView = new backboneViews.ListView( { 191 | template: "#list-template", 192 | parent: ".container", 193 | ItemView: ItemView, 194 | collection: base.Collection.create( count ) 195 | } ); 196 | 197 | new base.StatsView(); 198 | listView.render(); 199 | 200 | } ); 201 | 202 | define("local.plain", function(){}); 203 | 204 | -------------------------------------------------------------------------------- /dist/backbone.declarative.views.min.js: -------------------------------------------------------------------------------- 1 | // Backbone.Declarative.Views, v4.1.2 2 | // Copyright (c) 2014-2017 Michael Heim, Zeilenwechsel.de 3 | // Distributed under MIT license 4 | // http://github.com/hashchange/backbone.declarative.views 5 | 6 | 7 | !function(a,b){"use strict";var c="object"==typeof exports&&exports&&!exports.nodeType&&"object"==typeof module&&module&&!module.nodeType;"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(["exports","underscore","backbone"],b):c?b(exports,require("underscore"),require("backbone")):b({},_,Backbone)}(0,function(a,_,Backbone){"use strict";function b(a,b,c){var d;return a&&_.isString(a)&&(d=B[a],d||(d=k(a,b,c)),d.invalid&&(d=void 0),d&&(d=j(d))),d}function c(a,c){var d,e=a.declarativeViews.meta;return e.processed?d=e.inGlobalCache?b(e.originalTemplateProp,a,c):void 0:a.template&&_.isString(a.template)?(e.originalTemplateProp=a.template,d=b(a.template,a,c),e.processed=!0,e.inGlobalCache=!0,d&&M.trigger("cacheEntry:view:process",j(d),e.originalTemplateProp,a,c)):(d=void 0,e.processed=!0,e.inGlobalCache=!1),d&&M.trigger("cacheEntry:view:fetch",d,e.originalTemplateProp,a,c),d}function d(a){B={},!a&&Backbone.Marionette&&Backbone.Marionette.TemplateCache&&Backbone.Marionette.TemplateCache.clear()}function e(a){var b=!1,c=_.toArray(arguments),d=_.last(c);if(c.length&&_.isBoolean(d)&&(b=c.pop()),c.length>1)_.each(c,function(a){e(a,b)});else if(_.isArray(a)||_.isArguments(a))_.each(a,function(a){e(a,b)});else{if(!a)throw new H("Missing argument: string identifying the template. The string should be a template selector or the raw HTML of a template, as provided to the template property of a view when the cache entry was created");if(_.isString(a)&&(m(a),!b&&Backbone.Marionette&&Backbone.Marionette.TemplateCache))try{Backbone.Marionette.TemplateCache.clear(a)}catch(a){}}}function f(a){var b=a.declarativeViews.meta;b.processed?b.inGlobalCache&&m(b.originalTemplateProp):a.template&&_.isString(a.template)&&m(a.template)}function g(a){var b;try{if(b=N(a),!N.contains(document.documentElement,b[0]))throw new Error}catch(c){if(b=h(a),b.html()!==a)throw new Error("Failed to wrap template string in script tag without altering it")}return b}function h(a){var b=N("'; 25 | innerTemplateHtml = '
  • Inner list item
  • '; 26 | 27 | $templateNode = $( baseTemplateHtml ) 28 | .html( innerTemplateHtml ) 29 | .attr( dataAttributes ) 30 | .appendTo( "body" ); 31 | 32 | outerTemplateHtml = $templateNode.prop( "outerHTML" ) ; 33 | 34 | Backbone.DeclarativeViews.custom.compiler = function ( html, $template ) { 35 | return _.template( html ); 36 | }; 37 | 38 | View = Backbone.View.extend(); 39 | } ); 40 | 41 | afterEach( function () { 42 | cleanup(); 43 | $templateNode.remove(); 44 | Backbone.DeclarativeViews.custom.compiler = undefined; 45 | } ); 46 | 47 | describe( 'The custom compiler method receives', function () { 48 | 49 | var compilerSpy; 50 | 51 | beforeEach( function () { 52 | compilerSpy = sinon.spy( Backbone.DeclarativeViews.custom, "compiler" ); 53 | new View( { template: "#template" } ); 54 | } ); 55 | 56 | afterEach( function () { 57 | Backbone.DeclarativeViews.custom.compiler.restore(); 58 | } ); 59 | 60 | it( 'exactly two arguments', function () { 61 | expect( Backbone.DeclarativeViews.custom.compiler.getCall( 0 ).args ).to.be.of.length( 2 ); 62 | } ); 63 | 64 | it( 'the inner HTML of the template as the first argument', function () { 65 | expect( Backbone.DeclarativeViews.custom.compiler ).to.have.been.calledWith( innerTemplateHtml ); 66 | } ); 67 | 68 | it( 'the $template node, wrapped in a jQuery object, as the second argument', function () { 69 | var firstArg = Backbone.DeclarativeViews.custom.compiler.getCall( 0 ).args[1]; 70 | expect( firstArg ).to.be.instanceOf( jQuery ); 71 | expect( firstArg ).to.be.of.length( 1 ); 72 | expect( firstArg.prop( "outerHTML" ) ).to.equal( outerTemplateHtml ); 73 | } ); 74 | 75 | } ); 76 | 77 | describe( 'The return value of the custom compiler', function () { 78 | 79 | it( 'is allowed to be anything without throwing an error (checking with string return value)', function () { 80 | // It _should_ be a function, but it is not enforced. 81 | Backbone.DeclarativeViews.custom.compiler = function () { return "A random string!" }; 82 | expect( function () { new View( { template: "#template" } ); } ).not.to.throw(); 83 | } ); 84 | 85 | it( 'is allowed to be anything without throwing an error (checking with undefined return value)', function () { 86 | // It _should_ be a function, but it is not enforced. 87 | Backbone.DeclarativeViews.custom.compiler = function () { return undefined; }; 88 | expect( function () { new View( { template: "#template" } ); } ).not.to.throw(); 89 | } ); 90 | 91 | it( 'appears as the "compiled" property in the cache entry', function () { 92 | var view = new View( { template: "#template" } ), 93 | cached = view.declarativeViews.getCachedTemplate(); 94 | 95 | expect( cached ).to.haveOwnProperty( "compiled" ); 96 | expect( cached.compiled ).to.be.a( "function" ); 97 | expect( cached.compiled() ).to.equal( ( _.template( innerTemplateHtml ) )() ); 98 | } ); 99 | 100 | } ); 101 | 102 | describe( 'The presence of a custom compiler does not affect', function () { 103 | 104 | var view; 105 | 106 | beforeEach( function () { 107 | view = new View( { template: "#template" } ); 108 | } ); 109 | 110 | it( 'the other cached values', function () { 111 | expect( view.declarativeViews.getCachedTemplate() ).to.returnCacheValueFor( dataAttributes, outerTemplateHtml, _.template( innerTemplateHtml ) ); 112 | } ); 113 | 114 | it( 'the el of the view', function () { 115 | expect( view ).to.have.exactElProperties( dataAttributesToProperties( dataAttributes ) ); 116 | } ); 117 | 118 | } ); 119 | 120 | describe( 'If defined, the compiler must be a function. An error is thrown', function () { 121 | 122 | it( 'if it is an object', function () { 123 | Backbone.DeclarativeViews.custom.compiler = {}; 124 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid custom template compiler set in Backbone.DeclarativeViews.custom.compiler: compiler is not a function" ); 125 | } ); 126 | 127 | it( 'if it is a string', function () { 128 | Backbone.DeclarativeViews.custom.compiler = "a string"; 129 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid custom template compiler set in Backbone.DeclarativeViews.custom.compiler: compiler is not a function" ); 130 | } ); 131 | 132 | it( 'if it is an array', function () { 133 | Backbone.DeclarativeViews.custom.compiler = []; 134 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid custom template compiler set in Backbone.DeclarativeViews.custom.compiler: compiler is not a function" ); 135 | } ); 136 | 137 | } ); 138 | 139 | describe( 'If the compiler throws an error when called', function () { 140 | 141 | it( 'Backbone.Declarative.Views throws a (friendly) error', function () { 142 | Backbone.DeclarativeViews.custom.compiler = function () { throw new Error( "compiler error message" ); }; 143 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CompilerError, "An error occurred while compiling the template" ); 144 | } ); 145 | 146 | } ); 147 | 148 | describe( 'If no compiler is defined', function () { 149 | 150 | it( 'the "compiled" property in the cache entry is set to undefined', function () { 151 | Backbone.DeclarativeViews.custom.compiler = undefined; 152 | var view = new View( { template: "#template" } ), 153 | cached = view.declarativeViews.getCachedTemplate(); 154 | 155 | expect( cached.compiled ).to.be.undefined; 156 | } ); 157 | 158 | } ); 159 | 160 | } ); 161 | 162 | })(); -------------------------------------------------------------------------------- /spec/helpers/fixture-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a raw HTML string for a template. A comment containing the el configuration can be inserted into it. 3 | * 4 | * By default, the comment is inserted at the beginning of the HTML string. 5 | * 6 | * @param {Function} createTemplateFn the function returning the template HTML, except for the el comment. It must 7 | * be possible to pass the el comment as an insertion. Expected signature: 8 | * createTemplateFn( templateLanguage, insertion ) 9 | * @param {string} templateLanguage currently supported: "Handlebars", "EJS" (EJS), "ES6". It is up to the template 10 | * creation function what to make of it (e.g., can be ignored if there are no 11 | * template vars) 12 | * @param {Object} elCommentConfig 13 | * @param {Function} [elCommentConfig.createContent] a function generating the content of the comment which defines 14 | * the el, except for the outer comment tags (not wrapped in 15 | * ""). Called with the data attributes (as a hash). 16 | * Expected signature: createContent( dataAttributes ) 17 | * @param {boolean} [elCommentConfig.noComment=false] flag: comment should be omitted 18 | * @param {boolean} [elCommentConfig.trailing=false] flag: comment should be placed at the end of the HTML 19 | * @param {boolean} [elCommentConfig.among=false] flag: comment should be placed inside the HTML 20 | * @param {Object} [dataAttributes] hash containing the data attributes to be set in the comment 21 | * 22 | * @returns {string} 23 | */ 24 | function createRawHtml( createTemplateFn, templateLanguage, elCommentConfig, dataAttributes ) { 25 | // Construct the HTML string 26 | var comment = elCommentConfig.noComment ? "" : "", 27 | 28 | insertion = elCommentConfig.among ? comment : "", 29 | isLeading = ! elCommentConfig.trailing && !elCommentConfig.among, 30 | isTrailing = elCommentConfig.trailing, 31 | 32 | baseTemplate = createTemplateFn( templateLanguage, insertion ); 33 | 34 | return isLeading ? comment + baseTemplate : isTrailing ? baseTemplate + comment : baseTemplate; 35 | } 36 | 37 | /** 38 | * Creates the content of a complex template. It should contain as many pitfalls for correct processing as possible. 39 | * 40 | * Features: 41 | * 42 | * - has a top-level comment (single line) 43 | * - has a top-level comment (multi-line) 44 | * - has a comment containing a tag 45 | * - has numerous top-level tags 46 | * - has text at top level, not enclosed in another tag 47 | * - has tags nested in invalid ways, creating invalid HTML (

    inside

    ) 48 | * - has significant whitespace which must be preserved 49 | * - has template vars inside a tag itself (defining attributes) 50 | * - has a template var define the tag type 51 | * - has templating instructions (if/else) 52 | * - has a script tag inside the template, containing Javascript 53 | * - has a reference to a partial template 54 | * 55 | * NB For ES6 templates, only the start and end delimiters used for a variable are supported, other constructs (if, loop, 56 | * partial) are omitted here. 57 | * 58 | * @param {string} templateLanguage values are "Handlebars", "EJS" (EJS), "ES6" 59 | * @param {object} [options] 60 | * @param {string} [options.indentation=""] a string of whitespace, e.g. " " or "" (no indentation) 61 | * @param {string} [options.insertion=""] an additional string which is inserted somewhere in the middle of the 62 | * content (if left undefined, a blank line, plus insertion, appears instead) 63 | * @returns {string} 64 | */ 65 | function createComplexTemplate ( templateLanguage, options ) { 66 | var t = getTemplateLanguageConstructs( templateLanguage ), 67 | 68 | indent = options && options.indentation || "", 69 | insert = options && options.insertion || "", 70 | 71 | lines = [ 72 | '', 73 | '', 78 | 79 | t.if, 80 | '

    This is a %%paragraph', 81 | 'Some random %%text&& with different line breaks.


    ', 82 | t.else, 83 | '

    This is a %%header&&

    ', 84 | t.endIf, 85 | t.if, 86 | '

    ', 87 | t.endIf, 88 | 89 | insert, 90 | 91 | 'Some top-level %%text&&, not wrapped in a tag.


    ', 92 | '', 93 | "<" + "script>alert( 'foo' );", 94 | '

    ', 95 | ' some text

    ', 96 | '<%%tagName&& %%attrs&&>lorem ipsum', 97 | '

    Invalid nesting

    ', 98 | 99 | t.partial, 100 | 101 | '
    ', 102 | ' ' + t.loop, 103 | '
    %%dd_name&&
    ', 104 | '
    %%dd_content&&
    ', 105 | ' ' + t.endLoop, 106 | '
    ' 107 | ], 108 | 109 | innerContent = _.map( lines, function ( line ) { 110 | return indent + line; 111 | } ).join( "\n" ); 112 | 113 | return innerContent.replace( /%%/g, t.startDelimiter ).replace( /&&/g, t.endDelimiter ); 114 | } 115 | 116 | /** 117 | * Returns the language constructs for a given template language, for use in template creation. 118 | * 119 | * NB For ES6 templates, only the start and end delimiters used for a variable are supported, other constructs (if, loop, 120 | * partial) are omitted here. Empty strings are returned for the instead. 121 | * 122 | * @param {string} templateLanguage 123 | * @returns {Object} 124 | */ 125 | function getTemplateLanguageConstructs ( templateLanguage ) { 126 | var constructs; 127 | 128 | switch ( templateLanguage.toLowerCase() ) { 129 | 130 | case "handlebars": 131 | constructs = { 132 | startDelimiter: "{{", 133 | endDelimiter: "}}", 134 | if: "{{#if isActive}}", 135 | else: "{{else}}", 136 | endIf: "{{/if}}", 137 | loop: "{{#each looped as |value index|}}", 138 | endLoop: "{{/each}}", 139 | partial: '{{> userMessage tagName="h2" }}' 140 | }; 141 | 142 | break; 143 | case "ejs": 144 | constructs = { 145 | startDelimiter: "<%= ", 146 | endDelimiter: " %>", 147 | if: "<% if (isActive) { %>", 148 | else: "<% } else { %>", 149 | endIf: "<% } %>", 150 | loop: "<% looped.forEach(function(item) { %>", 151 | endLoop: "<% }); %>", 152 | partial: "<%- include('user/show', {user: user}); %>" 153 | }; 154 | break; 155 | case "es6": 156 | constructs = { 157 | startDelimiter: "${", 158 | endDelimiter: "}", 159 | if: "", 160 | else: "", 161 | endIf: "", 162 | loop: "", 163 | endLoop: "", 164 | partial: "" 165 | }; 166 | break; 167 | default: 168 | throw new Error( 'Unsupported template language "' + templateLanguage + '"' ); 169 | } 170 | 171 | return constructs; 172 | } 173 | -------------------------------------------------------------------------------- /spec/helpers/various-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a HTML5 data-* attributes hash to a hash of Javascript properties. 3 | * 4 | * - The key names are changed. The "data-" prefix is dropped, and hyphen notation is changed to camel case. 5 | * - Some values are changed. If a data attribute is used to store an object, it has been converted to a JSON string. 6 | * So JSON-string values are converted back into actual objects. 7 | * 8 | * Example: 9 | * { 10 | * "data-my-prop": "some value", 11 | * "data-hash": '{ "foo": "nested foo", "bar": "nested bar" }' 12 | * } 13 | * => 14 | * { 15 | * myProp: "some value", 16 | * hash: { foo: "nested foo", bar: "nested bar" } 17 | * } 18 | * 19 | * @param {Object} dataAttributesHash 20 | * @returns {Object} 21 | */ 22 | function dataAttributesToProperties ( dataAttributesHash ) { 23 | var transformed = {}; 24 | 25 | $.each( dataAttributesHash, function ( key, value ) { 26 | // Drop the "data-" prefix, then convert to camelCase 27 | key = toCamelCase( key.replace( /^data-/, "" ) ); 28 | 29 | try { 30 | value = $.parseJSON( value ); 31 | } catch ( err ) {} 32 | 33 | transformed[key] = value; 34 | } ); 35 | 36 | return transformed; 37 | } 38 | 39 | /** 40 | * Transforms a hash of Javascript properties into a HTML5 data-* attributes hash. Is the inverse function of 41 | * `dataAttributesToProperties()`. 42 | * 43 | * @param {Object} attributesHash 44 | * @returns {Object} 45 | */ 46 | function propertiesToDataAttributes ( attributesHash ) { 47 | var transformed = {}; 48 | 49 | $.each( attributesHash, function ( key, value ) { 50 | // Convert camelCase to dashed notation, then add the "data-" prefix 51 | key = "data-" + toDashed( key ); 52 | 53 | if ( $.isPlainObject( value ) ) value = JSON.stringify( value ); 54 | 55 | transformed[key] = value; 56 | } ); 57 | 58 | return transformed; 59 | } 60 | 61 | /** 62 | * Transforms a hash of HTML attributes - e.g., data attributes - into a string. 63 | * 64 | * The string has a trailing slash (or newline), but not a leading one. Double quotes are used by default around values, 65 | * except around JSON strings which have to be enclosed by single quotes. 66 | * 67 | * The string can be used to create the HTML of a tag with the given attributes. Data attributes containing a 68 | * stringified JSON structure are fully supported (simply by using single quotes around attribute values there). 69 | * 70 | * The following formatting option are available: 71 | * 72 | * - The order of the attributes can be reversed. 73 | * - Attributes can be separated by newlines instead of spaces. 74 | * - Redundant whitespace can be inserted around the "=" of each assignment. 75 | * - Values can be enclosed in single quotes, rather than double quotes, where possible (skipped for values containing a 76 | * double quote character). 77 | * 78 | * @param {Object} attributesHash 79 | * @param {Object} [options] 80 | * @param {boolean} [options.reverse=false] 81 | * @param {boolean} [options.multiline=false] 82 | * @param {string} [options.extraSpace=""] 83 | * @param {boolean} [options.preferSingleQuotes=false] 84 | * @returns {string} 85 | */ 86 | function attributesHashToString ( attributesHash, options ) { 87 | var reduce = options && options.reverse ? _.reduceRight : _.reduce, 88 | separator = options && options.multiline ? "\n" : " ", 89 | spacing = options && options.extrSpace || "", 90 | defaultQuote = options && options.preferSingleQuotes ? "'" : '"'; 91 | 92 | return reduce( attributesHash, function ( attrString, value, key ) { 93 | var quote = value.indexOf( '"' ) !== -1 ? "'" : value.indexOf( "'" ) !== -1 ? '"' : defaultQuote; 94 | return attrString + key + spacing + "=" + spacing + quote + value + quote + separator; 95 | }, "" ); 96 | } 97 | 98 | /** 99 | * Returns a transformed hash in which all camelCased property names have been replaced by dashed property names. The 100 | * input hash remains untouched. 101 | * 102 | * Property values are not modified. 103 | * 104 | * Simple implementation, but good enough for the attribute names we deal with here. 105 | * 106 | * Example: { fooBar: "whatEver" } => { "foo-bar": "whatEver" } 107 | * 108 | * @param {Object} hash 109 | * @returns {Object} 110 | */ 111 | function toDashedProperties ( hash ) { 112 | var transformed = {}; 113 | 114 | _.each( hash, function ( value, key ) { 115 | transformed[toDashed( key )] = value; 116 | } ); 117 | 118 | return transformed; 119 | } 120 | 121 | /** 122 | * Returns a transformed hash in which all dashed property names have been replaced by camelCased property names. The 123 | * input hash remains untouched. 124 | * 125 | * Property values are not modified. 126 | * 127 | * Simple implementation, but good enough for the attribute names we deal with here. 128 | * 129 | * Example: { "foo-bar": "what-ever" } => { fooBar: "what-ever" } 130 | * 131 | * @param {Object} hash 132 | * @returns {Object} 133 | */ 134 | function toCamelCasedProperties ( hash ) { 135 | var transformed = {}; 136 | 137 | _.each( hash, function ( value, key ) { 138 | transformed[toCamelCase( key )] = value; 139 | } ); 140 | 141 | return transformed; 142 | } 143 | 144 | /** 145 | * Converts a camelCased label into a dashed label. 146 | * 147 | * Simple implementation, but good enough for the attribute names we deal with here. 148 | * 149 | * @param {string} label 150 | * @returns {string} 151 | */ 152 | function toDashed ( label ) { 153 | return label.replace( /([a-z])([A-Z])/g, function ( $0, $1, $2 ) { 154 | return $1 + "-" + $2.toLowerCase(); 155 | } ); 156 | } 157 | 158 | /** 159 | * Converts a dashed label into a camelCased label. 160 | * 161 | * Simple implementation, but good enough for the attribute names we deal with here. 162 | * 163 | * @param {string} label 164 | * @returns {string} 165 | */ 166 | function toCamelCase ( label ) { 167 | return label.replace( /-([a-z])/gi, function ( $0, $1 ) { 168 | return $1.toUpperCase(); 169 | } ); 170 | } 171 | 172 | /** 173 | * Returns an array of dashed key names, which are the alternative names for all camel-cased key names in a hash. 174 | * 175 | * Simple key names (not camel-cased) are ignored and don't show up in the array. 176 | * 177 | * E.g., dashedKeyAlternatives( { foo: "whatever", barBaz: "whatever" } ) returns [ "bar-baz" ]. 178 | * 179 | * @param {Object} hash 180 | * @returns {string[]} 181 | */ 182 | function dashedKeyAlternatives ( hash ) { 183 | var keys = _.keys( toDashedProperties( hash ) ); 184 | return _.filter( keys, function ( key ) { 185 | return key.search(/[^-]-[a-z]/) !== -1; 186 | } ); 187 | } 188 | 189 | /** 190 | * Combines various hashes with a shallow _.extend(). Doesn't modify the input hashes. 191 | * 192 | * Syntactic sugar only, as a simple way of saying _.extend( {}, hashA, hashB, hashN ); 193 | * 194 | * @param {...Object} hashes 195 | * @returns {Object} 196 | */ 197 | function combine ( hashA, hashB, hashN ) { 198 | var hashes = _.toArray( arguments ); 199 | 200 | return _.extend.apply( undefined, [ {} ].concat( hashes ) ); 201 | } 202 | 203 | /** 204 | * Checks the Marionette version and returns true if it is less than 3. 205 | * 206 | * NB Marionette 1 and 2 don't expose the version, so we look for the version string of Marionette 3 (or above) and 207 | * negate the result. 208 | * 209 | * @returns {boolean} 210 | */ 211 | function isMarionetteLt3 () { 212 | return !( Backbone.Marionette.VERSION && Backbone.Marionette.VERSION[0] >= 3 ); 213 | } 214 | 215 | /** 216 | * Returns the basic Marionette view type: Marionette.ItemView for Marionette 1 and 2, Marionette.View for Marionette 3. 217 | * 218 | * @returns {Backbone.Marionette.ItemView|Backbone.Marionette.View} 219 | */ 220 | function getMarionetteView () { 221 | return Backbone.Marionette.ItemView || Backbone.Marionette.View; 222 | } 223 | -------------------------------------------------------------------------------- /demo/amd/rjs/output/parts/marionette-precompiled-app.js: -------------------------------------------------------------------------------- 1 | // base.js 2 | 3 | define( 'local.base',[ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'backbone.declarative.views' 9 | 10 | ], function ( _, Backbone, performance ) { 11 | 12 | var eventBus = _.extend( {}, Backbone.Events ), 13 | 14 | Model = Backbone.Model.extend(), 15 | 16 | Collection = Backbone.Collection.extend( 17 | { model: Model }, 18 | { 19 | create: function ( modelCount ) { 20 | var i, 21 | collection = new Collection(), 22 | models = []; 23 | 24 | for ( i = 0; i < modelCount; i++ ) models.push( new collection.model( { number: i + 1 } ) ); 25 | collection.reset( models ); 26 | 27 | return collection; 28 | } 29 | } 30 | ), 31 | 32 | StatsView = Backbone.View.extend( { 33 | 34 | template: "#stats-template", 35 | 36 | parent: ".stats", 37 | 38 | initialize: function ( options ) { 39 | var compiledTemplate = this.declarativeViews.getCachedTemplate().compiled; 40 | this.template = compiledTemplate || _.template( this.declarativeViews.getCachedTemplate(). html ); 41 | 42 | options || ( options = {} ); 43 | 44 | if ( options.parent ) this.parent = options.parent; 45 | this.$parent = Backbone.$( this.parent ); 46 | this.$el.appendTo( this.$parent ); 47 | 48 | this.listenTo( eventBus, "createStats", this.render ); 49 | }, 50 | 51 | render: function ( stats ) { 52 | stats.totalDuration = Math.round( this.getTotalDuration() ); 53 | this.$el.html( this.template( stats ) ); 54 | }, 55 | 56 | getTotalDuration: function () { 57 | // Query the document height. This is to assess the true total render time, including the painting. 58 | // The calculation of the document height should be blocked by the browser until all item elements have 59 | // been painted. 60 | var docHeight = $( document ).height(); 61 | 62 | performance.measure( "paint", "create-itemViews-start" ); 63 | return performance.getEntriesByName( "paint" )[0].duration; 64 | } 65 | 66 | } ); 67 | 68 | Backbone.DeclarativeViews.custom.compiler = _.template; 69 | 70 | return { 71 | Model: Model, 72 | Collection: Collection, 73 | StatsView: StatsView, 74 | eventBus: eventBus 75 | } 76 | 77 | } ); 78 | 79 | // views-marionette.js 80 | 81 | define( 'local.views-marionette',[ 82 | 83 | 'underscore', 84 | 'backbone', 85 | 'marionette', 86 | 'usertiming', 87 | 'local.base', 88 | 'backbone.declarative.views' 89 | 90 | ], function ( _, Backbone, Marionette, performance, base ) { 91 | 92 | // Integrate with Marionette 93 | Backbone.DeclarativeViews.joinMarionette(); 94 | 95 | var MarionetteBaseView = Marionette.ItemView || Marionette.View, // Base view type, depending on Marionette version 96 | 97 | ItemView = MarionetteBaseView.extend( { 98 | 99 | appendTo: function ( $parent ) { 100 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 101 | $parent.append( this.$el ); 102 | return this; 103 | } 104 | 105 | } ), 106 | 107 | ListView = Marionette.CollectionView.extend( { 108 | 109 | initialize: function ( options ) { 110 | options || ( options = {} ); // jshint ignore:line 111 | 112 | if ( options.parent ) this.parent = options.parent; 113 | this.$parent = Backbone.$( this.parent ); 114 | }, 115 | 116 | onBeforeRender: function () { 117 | // Start timer 118 | performance.clearMarks(); 119 | performance.clearMeasures(); 120 | performance.mark( "create-itemViews-start" ); 121 | }, 122 | 123 | onRender: function () { 124 | var duration; 125 | 126 | // Measure the time it took to create the itemViews 127 | performance.measure( "create-itemViews", "create-itemViews-start" ); 128 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 129 | 130 | if ( ! this.$el.parent().length ) this.$el.appendTo( this.$parent ); 131 | 132 | base.eventBus.trigger( "createStats", { itemViewCount : this.children.length, duration: Math.round( duration ) } ); 133 | } 134 | 135 | } ); 136 | 137 | return { 138 | ItemView: ItemView, 139 | ListView: ListView 140 | } 141 | 142 | } ); 143 | 144 | define('local.precompiled.templates',['handlebars.runtime'], function(Handlebars) { 145 | Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; 146 | templates['item-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 147 | var helper; 148 | 149 | return "\n" 150 | + container.escapeExpression(((helper = (helper = helpers.number || (depth0 != null ? depth0.number : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"number","hash":{},"data":data}) : helper))) 151 | + "\n"; 152 | },"useData":true}); 153 | templates['list-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 154 | return "\n"; 155 | },"useData":true}); 156 | templates['stats-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 157 | var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 158 | 159 | return "\nGenerating " 160 | + alias4(((helper = (helper = helpers.itemViewCount || (depth0 != null ? depth0.itemViewCount : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"itemViewCount","hash":{},"data":data}) : helper))) 161 | + " item views took " 162 | + alias4(((helper = (helper = helpers.duration || (depth0 != null ? depth0.duration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"duration","hash":{},"data":data}) : helper))) 163 | + "ms. In total, it took " 164 | + alias4(((helper = (helper = helpers.renderDuration || (depth0 != null ? depth0.renderDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"renderDuration","hash":{},"data":data}) : helper))) 165 | + "ms until they had all been appended to the DOM. Finally, when painting was over, " 166 | + alias4(((helper = (helper = helpers.totalDuration || (depth0 != null ? depth0.totalDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"totalDuration","hash":{},"data":data}) : helper))) 167 | + "ms had passed.\n"; 168 | },"useData":true}); 169 | return templates; 170 | }); 171 | 172 | // marionette-precompiled.js 173 | 174 | require( [ 175 | 176 | 'local.base', 177 | 'local.views-marionette', 178 | 'marionette.handlebars', 179 | 'precompiled.declarative.handlebars.templates', 180 | 'local.precompiled.templates' 181 | 182 | ], function ( base, marionetteViews ) { 183 | 184 | var count = 1000, 185 | 186 | ItemView = marionetteViews.ItemView.extend( { 187 | template: "item-template" // without leading "#": is ID of precompiled template, not selector 188 | } ), 189 | 190 | listView = new marionetteViews.ListView( { 191 | template: "list-template", // without leading "#" 192 | childView: ItemView, 193 | parent: ".container", 194 | collection: base.Collection.create( count ) 195 | } ); 196 | 197 | new base.StatsView( { template: "stats-template" } ); // without leading "#" 198 | listView.render(); 199 | 200 | } ); 201 | 202 | define("local.marionette-precompiled", function(){}); 203 | 204 | -------------------------------------------------------------------------------- /dist/backbone.declarative.views.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["backbone.declarative.views.js"],"names":["root","factory","supportsExports","exports","nodeType","module","define","amd","require","_","Backbone","this","getTemplateData","templateProp","view","viewOptions","data","isString","templateCache","_createTemplateCache","invalid","undefined","_copyCacheEntry","getViewTemplateData","meta","declarativeViews","processed","inGlobalCache","originalTemplateProp","template","events","trigger","clearCache","fromMarionette","Marionette","TemplateCache","clear","clearCachedTemplate","args","toArray","arguments","lastArg","last","length","isBoolean","pop","each","singleProp","isArray","isArguments","GenericError","_clearCachedTemplate","err","clearViewTemplateCache","loadTemplate","templateProperty","$template","$","contains","document","documentElement","Error","_wrapRawTemplate","html","templateString","$wrapper","attr","text","elDataAttributes","_getEmbeddedElAttributes","elDefinitionMatch","rxElDefinitionComment","exec","elDefinitionComment","rxRegisteredDataAttributes","rxAttributeMatcher","attributeName","match","attributeValue","size","cacheEntry","copy","clone","isObject","attributes","customLoader","DeclarativeViews","custom","defaultLoader","defaults","modifiedDefaultLoader","cacheId","_isDeclarativeViewsErrorType","CustomizationError","_getDataAttributes","compiled","_tryCompileTemplate","tagName","className","id","_pluginData","customCompiler","compiler","isFunction","CompilerError","message","_registerDataAttribute","name","options","existingNames","_getRegisteredDataAttributeNames","fullName","type","isJSON","names","registeredDataAttributes","indexOf","push","uniq","RegExp","_createElDefinitionCommentRx","primitives","concat","json","join","$elem","_updateJQueryDataCache","add","remove","hasData","toCamelCase","parseJSON","removeData","_registerCacheAlias","namespaceObject","instanceCachePropertyName","getCachedTemplate","instanceCacheAliases","unique","_enforceTemplateLoading","enforceTemplateLoading","error","TemplateError","ConfigurationError","joinMarionette","isMarionetteInitialized","originalClearCache","apply","dashed","replace","$0","$1","$2","toUpperCase","createCustomErrorType","CustomError","captureStackTrace","constructor","stack","prototype","originalConstructor","View","Events","extend","viewId","uniqueId","partial","alias","plugins","registerDataAttribute","getDataAttributes","updateJqueryDataCache","registerCacheAlias","tryCompileTemplate","version","info"],"mappings":";;;;;;CAKG,SAAWA,EAAMC,GAChB,YAQA,IAAIC,GAAqC,gBAAZC,UAAwBA,UAAYA,QAAQC,UAA8B,gBAAXC,SAAuBA,SAAWA,OAAOD,QAO9G,mBAAXE,SAA+C,gBAAfA,QAAOC,KAAoBD,OAAOC,IAG1ED,QAAU,UAAW,aAAc,YAAcL,GAEzCC,EAGRD,EAASE,QAASK,QAAS,cAAgBA,QAAS,aAKpDP,KAAaQ,EAAGC,WAIrBC,EAAM,SAAWR,EAASM,EAAGC,UAC5B,YAiJA,SAASE,GAAkBC,EAAcC,EAAMC,GAC3C,GAAIC,EAUJ,OARKH,IAAgBJ,EAAEQ,SAAUJ,KAC7BG,EAAOE,EAAeL,GACfG,IAAOA,EAAOG,EAAsBN,EAAcC,EAAMC,IAE1DC,EAAKI,UAAUJ,EAAOK,QACtBL,IAAOA,EAAOM,EAAiBN,KAGjCA,EA2DX,QAASO,GAAsBT,EAAMC,GACjC,GAAIC,GACAQ,EAAOV,EAAKW,iBAAiBD,IA8BjC,OA5BOA,GAAKE,UAuBRV,EAAOQ,EAAKG,cAAgBf,EAAiBY,EAAKI,qBAAsBd,EAAMC,GAAgBM,OArBzFP,EAAKe,UAAYpB,EAAEQ,SAAUH,EAAKe,WAEnCL,EAAKI,qBAAuBd,EAAKe,SAEjCb,EAAOJ,EAAiBE,EAAKe,SAAUf,EAAMC,GAE7CS,EAAKE,WAAY,EACjBF,EAAKG,eAAgB,EAEhBX,GAAOc,EAAOC,QAAS,0BAA2BT,EAAiBN,GAAQQ,EAAKI,qBAAsBd,EAAMC,KAIjHC,EAAOK,OAEPG,EAAKE,WAAY,EACjBF,EAAKG,eAAgB,GAQxBX,GAAOc,EAAOC,QAAS,wBAAyBf,EAAMQ,EAAKI,qBAAsBd,EAAMC,GAErFC,EAUX,QAASgB,GAAaC,GAClBf,MACOe,GAAkBvB,SAASwB,YAAcxB,SAASwB,WAAWC,eAAgBzB,SAASwB,WAAWC,cAAcC,QA+B1H,QAASC,GAAsBxB,GAE3B,GAAIoB,IAAiB,EACjBK,EAAO7B,EAAE8B,QAASC,WAClBC,EAAUhC,EAAEiC,KAAMJ,EAQtB,IAJKA,EAAKK,QAAUlC,EAAEmC,UAAWH,KAAYR,EAAiBK,EAAKO,OAI9DP,EAAKK,OAAS,EACflC,EAAEqC,KAAMR,EAAM,SAAWS,GAAeV,EAAqBU,EAAYd,SACtE,IAAKxB,EAAEuC,QAASnC,IAAkBJ,EAAEwC,YAAapC,GACpDJ,EAAEqC,KAAMjC,EAAc,SAAWkC,GAAeV,EAAqBU,EAAYd,SAC9E,CAEH,IAAOpB,EAAe,KAAM,IAAIqC,GAAc,6MAM9C,IAAKzC,EAAEQ,SAAUJ,KAEbsC,EAAsBtC,IAEfoB,GAAkBvB,SAASwB,YAAcxB,SAASwB,WAAWC,eAChE,IACIzB,SAASwB,WAAWC,cAAcC,MAAOvB,GAC3C,MAAQuC,MAc1B,QAASC,GAAyBvC,GAC9B,GAAIU,GAAOV,EAAKW,iBAAiBD,IAE5BA,GAAKE,UACDF,EAAKG,eAAgBwB,EAAsB3B,EAAKI,sBAC7Cd,EAAKe,UAAYpB,EAAEQ,SAAUH,EAAKe,WAC1CsB,EAAsBrC,EAAKe,UAoBnC,QAASyB,GAAeC,GACpB,GAAIC,EAEJ,KAMI,GALAA,EAAYC,EAAGF,IAKTE,EAAEC,SAAUC,SAASC,gBAAiBJ,EAAU,IAAO,KAAM,IAAIK,OACzE,MAAQT,GAKN,GAJAI,EAAYM,EAAkBP,GAIzBC,EAAUO,SAAWR,EAAmB,KAAM,IAAIM,OAAO,oEAGlE,MAAOL,GAWX,QAASM,GAAkBE,GACvB,GAAIC,GAAWR,EAAG,cACTS,KAAM,OAAQ,mBACdC,KAAMH,GAEXI,EAAmBC,EAA0BL,EAIjD,OAFKI,IAAmBH,EAASC,KAAME,GAEhCH,EAYX,QAASI,GAA2BL,GAChC,GAAII,MAEAE,EAAoBC,EAAsBC,KAAMR,GAChDS,EAAsBH,GAAqBA,EAAkB,EAWjE,OATKG,IACDhE,EAAEqC,KAAM4B,EAA4B,SAAWC,EAAoBC,GAC/D,GAAIC,GAAQF,EAAmBH,KAAMC,GACjCK,EAAiBD,GAASA,EAAM,EAE/BC,KAAiBV,EAAiBQ,GAAiBE,KAIzDrE,EAAEsE,KAAMX,GAAqBA,EAAmB/C,OAY3D,QAASC,GAAkB0D,GACvB,GAAIC,GAAOxE,EAAEyE,MAAOF,EAEpB,OADKvE,GAAE0E,SAAUF,EAAKG,cAAeH,EAAKG,WAAa3E,EAAEyE,MAAOD,EAAKG,aAC9DH,EAoCX,QAAS9D,GAAsBN,EAAcC,EAAMC,GAC/C,GAAIyC,GAAWxC,EAAM+C,EAEjBsB,EAAe3E,SAAS4E,iBAAiBC,OAAOjC,aAChDkC,EAAgB9E,SAAS4E,iBAAiBG,SAASnC,aACnDoC,EAAwBF,IAAkBlC,EAE1CqC,EAAU9E,CAGd,KACI2C,EAAY6B,EAAeA,EAAcxE,EAAcC,EAAMC,GAAgByE,EAAe3E,EAAcC,EAAMC,GAClH,MAAQqC,GAEN,GAAIwC,EAA8BxC,GAAQ,KAAMA,EAEhDI,GAAY,GAGhB,IAAO6B,GAAgBK,IAAyC,KAAdlC,KAAwBA,YAAqB9C,UAAS+C,GACpG,KAAM,IAAIoC,GAAoB,8BAAiCR,EAAe,SAAW,WAAc,sEA+B3G,OA3BK7B,GAAUb,QAGX3B,EAAO8E,EAAoBtC,GAE3BO,EAAOP,EAAUO,OAEjB7C,EAAcyE,IACV5B,KAAMA,EACNgC,SAAUC,EAAqBjC,EAAMP,GAErCyC,QAASjF,EAAKiF,QACdC,UAAWlF,EAAKkF,UAChBC,GAAInF,EAAKmF,GACTf,WAAYpE,EAAKoE,WAIjBgB,gBAGJtE,EAAOC,QAAS,oBAAqBb,EAAcyE,GAAU9E,EAAcC,EAAMC,IAGjFG,EAAcyE,IAAavE,SAAS,GAGjCF,EAAcyE,GAsBzB,QAASK,GAAsBjC,EAAMP,GACjC,GAAIuC,GACAM,EAAiB3F,SAAS4E,iBAAiBC,OAAOe,QAEtD,IAAKD,EAAiB,CAElB,GAAKA,IAAoB5F,EAAE8F,WAAYF,GAAmB,KAAM,IAAIR,GAAoB,gHAExF,KACIE,EAAWM,EAAgBtC,EAAMP,GACnC,MAAQJ,GACN,KAAM,IAAIoD,GACN,iGAAmGzC,GAC/FP,EACA,oHACA,2BACA,6CAA+CJ,EAAIqD,UAKnE,MAAOV,GAQX,QAAS5C,GAAuBtC,GACvBK,EAAeL,UAAwBK,GAAeL,GAoC/D,QAAS6F,GAAyBC,EAAMC,GACpC,GAAIC,GAAgBC,IAChBC,EAAW,QAAUJ,EACrBK,EAAOJ,GAAWA,EAAQK,OAAS,OAAS,aAC5CC,EAAQC,EAAyBH,EAErC,IAAiC,IAA5BL,EAAKS,QAAS,SAAkB,KAAM,IAAIvB,GAAoB,oDAAsDc,EAAO,+CAChI,IAAc,SAATA,GAA4B,aAATA,EAAsB,KAAM,IAAId,GAAoB,4DAA8Dc,EAAO,2BACjJ,IAAKlG,EAAEiD,SAAUmD,EAAeF,GAAS,KAAM,IAAId,GAAoB,4DAA8Dc,EAAO,2CAG5IO,GAAMG,KAAMV,GACZQ,EAAyBH,GAAQvG,EAAE6G,KAAMJ,GAIzCxC,EAA2BqC,GAAY,GAAIQ,QAAQR,EAAW,mCAI9DxC,EAAwBiD,IAQ5B,QAASV,KACL,MAAOK,GAAyBM,WAAWC,OAAQP,EAAyBQ,MAYhF,QAAUH,KACN,MAAO,IAAID,QAAQ,oCAAsCT,IAAmCc,KAAM,KAAQ,+CAiB9G,QAAS9B,GAAqB+B,GAE1B,MADAC,GAAwBD,GACjBA,EAAM7G,OA+BjB,QAAS8G,GAAyBD,GAC9B,GAAIE,MACAC,IAECvE,GAAEwE,QAASJ,EAAM,MAMlBpH,EAAEqC,KAAMqE,EAAyBM,WAAY,SAAW7C,GACpD,GAAIE,GAAiB+C,EAAM3D,KAAM,QAAUU,EAEnBvD,UAAnByD,EACDkD,EAAOX,KAAMzC,GAEbmD,EAAIG,EAAatD,IAAmBE,IAK5CrE,EAAEqC,KAAMqE,EAAyBQ,KAAM,SAAW/C,GAC9C,GAAIE,GAAiB+C,EAAM3D,KAAM,QAAUU,EAE3C,IAAwBvD,SAAnByD,EACDkD,EAAOX,KAAMzC,OAGb,KACImD,EAAIG,EAAatD,IAAmBnB,EAAE0E,UAAWrD,GACnD,MAAQ1B,GACN4E,EAAOX,KAAMzC,MAMpBoD,EAAOrF,QAASkF,EAAMO,WAAYJ,GAClCvH,EAAEsE,KAAMgD,IAAQF,EAAM7G,KAAM+G,IAmBzC,QAASM,GAAqBC,EAAiBC,GAC3CD,EAAgBE,kBAAoB9H,SAAS4E,iBAAiBkD,kBAC9DF,EAAgBjG,oBAAsB3B,SAAS4E,iBAAiBjD,oBAChEiG,EAAgBtG,WAAatB,SAAS4E,iBAAiBtD,WACvDsG,EAAgB/C,OAAS7E,SAAS4E,iBAAiBC,OAE9CgD,IACDE,EAAqBpB,KAAMkB,GAC3BE,EAAuBhI,EAAEiI,OAAQD,IAkBzC,QAASE,KACLC,GAAyB,EAW7B,QAAShD,GAA+BiD,GACpC,MAAOA,aAAiB3F,IACjB2F,YAAiBC,IACjBD,YAAiBrC,IACjBqC,YAAiBhD,IACjBgD,YAAiBE,GAO5B,QAASC,KAEAtI,SAASwB,YAAcxB,SAASwB,WAAWC,gBAAkB8G,IAE9DC,EAAqBxI,SAASwB,WAAWC,cAAcC,MAOvD1B,SAASwB,WAAWC,cAAcC,MAAQ,WACjCI,UAAUG,OACXjC,SAAS4E,iBAAiBjD,oBAAqBG,WAAW,GAE1D9B,SAAS4E,iBAAiBtD,YAAY,GAG1CkH,EAAmBC,MAAOxI,KAAM6B,YAGpCyG,GAA0B,GAoClC,QAASf,GAAckB,GACnB,MAAOA,GAAOC,QAAS,kBAAmB,SAAWC,EAAIC,EAAIC,GACzD,MAAOD,GAAKC,EAAGC,gBAYvB,QAASC,GAAwB/C,GAE7B,QAASgD,GAAclD,GACnB9F,KAAK8F,QAAUA,EAEV5C,MAAM+F,kBACP/F,MAAM+F,kBAAmBjJ,KAAMA,KAAKkJ,aAEpClJ,KAAKmJ,OAAQ,GAAMjG,QAAUiG,MAQrC,MAJAH,GAAYI,UAAY,GAAIlG,OAC5B8F,EAAYI,UAAUpD,KAAOA,EAC7BgD,EAAYI,UAAUF,YAAcF,EAE7BA,EAp4BX,GAAIT,GAaA3E,EAZAyF,EAAsBtJ,SAASuJ,KAC/B/I,KACAuH,KAEAG,GAAyB,EACzBK,GAA0B,EAE1B9B,GACIM,cACAE,SAIJjD,KAEAxB,EAAewG,EAAuB,mCACtCZ,EAAgBY,EAAuB,2CACvClD,EAAiBkD,EAAuB,2CACxC7D,EAAqB6D,EAAuB,gDAC5CX,EAAqBW,EAAuB,gDAE5C5H,EAASrB,EAAEyE,MAAOxE,SAASwJ,QAE3BzG,EAAI/C,SAAS+C,CAMjBhD,GAAE0J,OAAQzJ,SAASuJ,KAAKF,WAEpB9D,QAAS,WAEL,OADW1E,EAAqBZ,WACpBsF,SAAW,OAG3BC,UAAW,WAEP,OADW3E,EAAqBZ,WACpBuF,WAAa7E,QAG7B8E,GAAI,WAEA,OADW5E,EAAqBZ,WACpBwF,IAAM9E,QAGtB+D,WAAY,WAER,OADW7D,EAAqBZ,WACpByE,YAAc/D,UAKlCX,SAASuJ,KAAOvJ,SAASuJ,KAAKE,QAE1BN,YAAa,SAAWjD,GACfA,GAAgCvF,SAArBuF,EAAQ/E,WAAyBlB,KAAKkB,SAAW+E,EAAQ/E,UAEzElB,KAAKc,kBACDD,MACG4I,OAAQ3J,EAAE4J,SAAU,UAEvB7B,kBAAmB/H,EAAE6J,QAAS/I,EAAqBZ,MACnD0B,oBAAqB5B,EAAE6J,QAASjH,EAAwB1C,OAG5DF,EAAEqC,KAAM2F,EAAsB,SAAW8B,GACrC5J,KAAK4J,GAAS5J,KAAKc,kBACpBd,MAEEiI,GAAyBrH,EAAqBZ,KAAMiG,GAEzDoD,EAAoBb,MAAOxI,KAAM6B,cAKzC9B,SAAS4E,kBACLkD,kBAAmB5H,EACnByB,oBAAqBA,EACrBL,WAAYA,EAEZgH,eAAgBA,EAEhBnF,MAAOX,EACP4F,cAAeA,EACftC,cAAeA,EACfX,mBAAoBA,EACpBkD,mBAAoBA,EAEpByB,SACIC,sBAAuB/D,EACvBgE,kBAAmB5E,EACnB6E,sBAAuB7C,EACvB8C,mBAAoBvC,EACpBO,uBAAwBD,EACxBkC,mBAAoB7E,EACpBlE,OAAQA,GAGZ2D,UACInC,aAAcA,GAGlBiC,QAEIjC,aAAcjC,OAEdiF,SAAUjF,QAGdyJ,QAAS,SAMbpE,EAAwB,YACxBA,EAAwB,cACxBA,EAAwB,MACxBA,EAAwB,cAAgBO,QAAQ,IAoxBhD9G,EAAQ4K,KAAO","file":"backbone.declarative.views.min.js"} -------------------------------------------------------------------------------- /demo/amd/rjs/output/parts/backbone-precompiled-app.js: -------------------------------------------------------------------------------- 1 | // base.js 2 | 3 | define( 'local.base',[ 4 | 5 | 'underscore', 6 | 'backbone', 7 | 'usertiming', 8 | 'backbone.declarative.views' 9 | 10 | ], function ( _, Backbone, performance ) { 11 | 12 | var eventBus = _.extend( {}, Backbone.Events ), 13 | 14 | Model = Backbone.Model.extend(), 15 | 16 | Collection = Backbone.Collection.extend( 17 | { model: Model }, 18 | { 19 | create: function ( modelCount ) { 20 | var i, 21 | collection = new Collection(), 22 | models = []; 23 | 24 | for ( i = 0; i < modelCount; i++ ) models.push( new collection.model( { number: i + 1 } ) ); 25 | collection.reset( models ); 26 | 27 | return collection; 28 | } 29 | } 30 | ), 31 | 32 | StatsView = Backbone.View.extend( { 33 | 34 | template: "#stats-template", 35 | 36 | parent: ".stats", 37 | 38 | initialize: function ( options ) { 39 | var compiledTemplate = this.declarativeViews.getCachedTemplate().compiled; 40 | this.template = compiledTemplate || _.template( this.declarativeViews.getCachedTemplate(). html ); 41 | 42 | options || ( options = {} ); 43 | 44 | if ( options.parent ) this.parent = options.parent; 45 | this.$parent = Backbone.$( this.parent ); 46 | this.$el.appendTo( this.$parent ); 47 | 48 | this.listenTo( eventBus, "createStats", this.render ); 49 | }, 50 | 51 | render: function ( stats ) { 52 | stats.totalDuration = Math.round( this.getTotalDuration() ); 53 | this.$el.html( this.template( stats ) ); 54 | }, 55 | 56 | getTotalDuration: function () { 57 | // Query the document height. This is to assess the true total render time, including the painting. 58 | // The calculation of the document height should be blocked by the browser until all item elements have 59 | // been painted. 60 | var docHeight = $( document ).height(); 61 | 62 | performance.measure( "paint", "create-itemViews-start" ); 63 | return performance.getEntriesByName( "paint" )[0].duration; 64 | } 65 | 66 | } ); 67 | 68 | Backbone.DeclarativeViews.custom.compiler = _.template; 69 | 70 | return { 71 | Model: Model, 72 | Collection: Collection, 73 | StatsView: StatsView, 74 | eventBus: eventBus 75 | } 76 | 77 | } ); 78 | 79 | // views-backbone.js 80 | 81 | define( 'local.views-backbone',[ 82 | 83 | 'underscore', 84 | 'backbone', 85 | 'usertiming', 86 | 'local.base', 87 | 'backbone.declarative.views' 88 | 89 | ], function ( _, Backbone, performance, base ) { 90 | 91 | var ItemView = Backbone.View.extend( { 92 | 93 | initialize: function ( options ) { 94 | this.template = this.declarativeViews.getCachedTemplate().compiled; 95 | }, 96 | 97 | render: function () { 98 | this.$el.html( this.template( this.model.attributes ) ); 99 | return this; 100 | }, 101 | 102 | appendTo: function ( $parent ) { 103 | if ( !( $parent instanceof Backbone.$ ) ) $parent = Backbone.$( $parent ); 104 | $parent.append( this.$el ); 105 | return this; 106 | } 107 | 108 | } ), 109 | 110 | ListView = Backbone.View.extend( { 111 | 112 | initialize: function ( options ) { 113 | options || ( options = {} ); // jshint ignore:line 114 | 115 | if ( options.ItemView ) this.ItemView = options.ItemView; 116 | if ( options.parent ) this.parent = options.parent; 117 | this.$parent = Backbone.$( this.parent ); 118 | }, 119 | 120 | render: function () { 121 | var duration, renderDuration, 122 | els = []; 123 | 124 | this.itemViews = []; 125 | 126 | // Start timer 127 | performance.clearMarks(); 128 | performance.clearMeasures(); 129 | performance.mark( "create-itemViews-start" ); 130 | 131 | this.collection.each( function ( model ) { 132 | //Backbone.DeclarativeViews.clearCache(); 133 | var itemView = new this.ItemView( { model: model } ); 134 | itemView.render(); 135 | 136 | this.itemViews.push( itemView ); 137 | els.push( itemView.el ); 138 | }, this ); 139 | 140 | // Measure itemView creation time 141 | performance.measure( "create-itemViews", "create-itemViews-start" ); 142 | duration = performance.getEntriesByName( "create-itemViews" )[0].duration; 143 | 144 | this.$el.append( els ); 145 | this.$el.appendTo( this.$parent ); 146 | 147 | // Measure render duration time (total from beginning of itemView creation) 148 | performance.measure( "render", "create-itemViews-start" ); 149 | renderDuration = performance.getEntriesByName( "render" )[0].duration; 150 | 151 | base.eventBus.trigger( "createStats", { 152 | itemViewCount : this.itemViews.length, 153 | duration: Math.round( duration ), 154 | renderDuration: Math.round( renderDuration ) 155 | } ); 156 | }, 157 | 158 | destroy: function () { 159 | _.each( this.itemViews, function ( itemView ) { 160 | itemView.remove(); 161 | } ); 162 | 163 | this.remove(); 164 | } 165 | 166 | } ); 167 | 168 | return { 169 | ItemView: ItemView, 170 | ListView: ListView 171 | } 172 | 173 | } ); 174 | 175 | define('local.precompiled.templates',['handlebars.runtime'], function(Handlebars) { 176 | Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; 177 | templates['item-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 178 | var helper; 179 | 180 | return "\n" 181 | + container.escapeExpression(((helper = (helper = helpers.number || (depth0 != null ? depth0.number : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"number","hash":{},"data":data}) : helper))) 182 | + "\n"; 183 | },"useData":true}); 184 | templates['list-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 185 | return "\n"; 186 | },"useData":true}); 187 | templates['stats-template'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 188 | var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 189 | 190 | return "\nGenerating " 191 | + alias4(((helper = (helper = helpers.itemViewCount || (depth0 != null ? depth0.itemViewCount : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"itemViewCount","hash":{},"data":data}) : helper))) 192 | + " item views took " 193 | + alias4(((helper = (helper = helpers.duration || (depth0 != null ? depth0.duration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"duration","hash":{},"data":data}) : helper))) 194 | + "ms. In total, it took " 195 | + alias4(((helper = (helper = helpers.renderDuration || (depth0 != null ? depth0.renderDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"renderDuration","hash":{},"data":data}) : helper))) 196 | + "ms until they had all been appended to the DOM. Finally, when painting was over, " 197 | + alias4(((helper = (helper = helpers.totalDuration || (depth0 != null ? depth0.totalDuration : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"totalDuration","hash":{},"data":data}) : helper))) 198 | + "ms had passed.\n"; 199 | },"useData":true}); 200 | return templates; 201 | }); 202 | 203 | // plain-precompiled.js 204 | 205 | require( [ 206 | 207 | 'local.base', 208 | 'local.views-backbone', 209 | 'precompiled.declarative.handlebars.templates', 210 | 'local.precompiled.templates' 211 | 212 | ], function ( base, backboneViews ) { 213 | 214 | var count = 1000, 215 | 216 | ItemView = backboneViews.ItemView.extend( { 217 | template: "item-template" // without leading "#": is ID of precompiled template, not selector 218 | } ), 219 | 220 | listView = new backboneViews.ListView( { 221 | template: "list-template", // without leading "#" 222 | parent: ".container", 223 | ItemView: ItemView, 224 | collection: base.Collection.create( count ) 225 | } ); 226 | 227 | new base.StatsView( { template: "stats-template" } ); // without leading "#" 228 | listView.render(); 229 | 230 | } ); 231 | 232 | define("local.plain-precompiled", function(){}); 233 | 234 | -------------------------------------------------------------------------------- /spec/custom.loader.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it */ 2 | (function () { 3 | "use strict"; 4 | 5 | describe( 'Custom loader', function () { 6 | 7 | var dataAttributes, outerTemplateHtml, innerTemplateHtml, View, 8 | cleanup = function () { 9 | Backbone.DeclarativeViews.clearCache(); 10 | Backbone.Marionette.TemplateCache.clear(); 11 | }; 12 | 13 | beforeEach( function () { 14 | 15 | cleanup(); 16 | 17 | dataAttributes = { 18 | "data-tag-name": "ul", 19 | "data-class-name": "listClass", 20 | "data-id": "listId", 21 | "data-attributes": '{ "lang": "en", "title": "title from data attributes" }' 22 | }; 23 | 24 | // Construct the HTML strings 25 | // NB Normalize the template HTML by piping it through jQuery 26 | innerTemplateHtml = '
  • Inner list item
  • '; 27 | outerTemplateHtml = $( '
    ' ) 28 | .html( innerTemplateHtml ) 29 | .attr( dataAttributes ) 30 | .prop( 'outerHTML' ); 31 | 32 | Backbone.DeclarativeViews.custom.loadTemplate = function () { 33 | return $( outerTemplateHtml ); 34 | }; 35 | 36 | View = Backbone.View.extend(); 37 | } ); 38 | 39 | afterEach( function () { 40 | cleanup(); 41 | Backbone.DeclarativeViews.custom.loadTemplate = undefined; 42 | } ); 43 | 44 | describe( 'Arguments provided to the custom loadTemplate method', function () { 45 | 46 | var view; 47 | 48 | beforeEach( function () { 49 | sinon.spy( Backbone.DeclarativeViews.custom, "loadTemplate" ); 50 | } ); 51 | 52 | afterEach( function () { 53 | Backbone.DeclarativeViews.custom.loadTemplate.restore(); 54 | } ); 55 | 56 | describe( 'When the custom loader is called in the context of a view', function () { 57 | 58 | describe( 'with the template being set as a property on the view class', function () { 59 | 60 | beforeEach( function () { 61 | View = Backbone.View.extend( { template: "#template" } ); 62 | view = new View(); 63 | } ); 64 | 65 | it( 'it receives the template property of the view as first argument', function () { 66 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template" ); 67 | } ); 68 | 69 | it( 'it receives the view as second argument', function () { 70 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template", view ); 71 | } ); 72 | 73 | } ); 74 | 75 | describe( 'with the template being passed in as an option during view instantiation', function () { 76 | 77 | beforeEach( function () { 78 | view = new View( { template: "#template" } ); 79 | } ); 80 | 81 | it( 'it receives the template option passed to the view as first argument', function () { 82 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template" ); 83 | } ); 84 | 85 | it( 'it receives the view as second argument', function () { 86 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template", view ); 87 | } ); 88 | 89 | } ); 90 | 91 | describe( 'with a template being set as a property on the view class, and a different template being passed in as an option', function () { 92 | 93 | beforeEach( function () { 94 | View = Backbone.View.extend( { template: "#template" } ); 95 | view = new View( { template: "#otherTemplate" } ); 96 | } ); 97 | 98 | it( 'it receives the template option passed to the view as first argument', function () { 99 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#otherTemplate" ); 100 | } ); 101 | 102 | it( 'it receives the view as second argument', function () { 103 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#otherTemplate", view ); 104 | } ); 105 | 106 | } ); 107 | 108 | } ); 109 | 110 | describe( 'When the custom loader is called from the global API', function () { 111 | 112 | describe( 'with the template selector provided as the only argument', function () { 113 | 114 | beforeEach( function () { 115 | Backbone.DeclarativeViews.getCachedTemplate( "#template" ); 116 | } ); 117 | 118 | it( 'the loader receives the template selector as first argument', function () { 119 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template" ); 120 | } ); 121 | 122 | it( 'the second argument provided to the loader is undefined', function () { 123 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template", undefined ); 124 | } ); 125 | 126 | } ); 127 | 128 | describe( 'with the template selector and a view provided as arguments', function () { 129 | 130 | beforeEach( function () { 131 | view = new View(); 132 | Backbone.DeclarativeViews.getCachedTemplate( "#template", view ); 133 | } ); 134 | 135 | it( 'the loader receives the template selector as first argument', function () { 136 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template" ); 137 | } ); 138 | 139 | it( 'the loader receives the view as second argument', function () { 140 | expect( Backbone.DeclarativeViews.custom.loadTemplate ).to.have.been.calledWith( "#template", view ); 141 | } ); 142 | 143 | } ); 144 | 145 | } ); 146 | 147 | } ); 148 | 149 | describe( 'When the jQuery object of the template is provided by a custom loadTemplate method', function () { 150 | 151 | var view; 152 | 153 | beforeEach( function () { 154 | view = new View( { template: "#template" } ); 155 | } ); 156 | 157 | it( 'its data attributes get applied to the el', function () { 158 | var attributes = JSON.parse( dataAttributes["data-attributes"] ); 159 | 160 | expect( view.el.tagName.toLowerCase() ).to.equal( dataAttributes["data-tag-name"] ); 161 | expect( view.$el.attr( "class" ) ).to.equal( dataAttributes["data-class-name"] ); 162 | expect( view.$el.attr( "id" ) ).to.equal( dataAttributes["data-id"] ); 163 | expect( view.$el.attr( "lang" ) ).to.equal( attributes.lang ); 164 | expect( view.$el.attr( "title" ) ).to.equal( attributes.title ); 165 | } ); 166 | 167 | it( 'it gets stored in the cache', function () { 168 | expect( view.declarativeViews.getCachedTemplate() ).to.returnCacheValueFor( dataAttributes, outerTemplateHtml ); 169 | } ); 170 | 171 | it( 'its inner HTML is returned as the template HTML by the cache', function () { 172 | expect( view.declarativeViews.getCachedTemplate().html ).to.eql( innerTemplateHtml ); 173 | } ); 174 | 175 | } ); 176 | 177 | describe( 'Backbone default behaviour remains unchanged', function () { 178 | 179 | it( 'when the view does not reference a template', function () { 180 | View = Backbone.View.extend(); 181 | expect( View ).to.createElWithStandardMechanism; 182 | } ); 183 | 184 | it( 'when the view references a template, but the return value of the custom loader does not have data attributes describing the el', function () { 185 | Backbone.DeclarativeViews.custom.loadTemplate = function () { return $( "
    Some HTML
    " ); }; 186 | View = Backbone.View.extend( { template: "#noDataAttr" } ); 187 | expect( View ).to.createElWithStandardMechanism; 188 | } ); 189 | 190 | it( 'when the view references a template with a string that the custom loader cannot process, throwing a generic error', function () { 191 | // NB ... but if the loader raises the alarm deliberately and throws one of the error types belonging to 192 | // Backbone.DeclarativeViews, business as usual is over: the exception is rethrown and bubbles up. See 193 | // test further below. 194 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Error( "loadTemplate blew up" ); }; 195 | View = Backbone.View.extend( { template: "#throwsError" } ); 196 | expect( View ).to.createElWithStandardMechanism; 197 | } ); 198 | 199 | it( 'when the view has a template property, but it is a function rather than a selector', function () { 200 | // That is because only string properties get forwarded to the cache mechanism, and eventually to the 201 | // custom loader. 202 | View = Backbone.View.extend( { template: function () { 203 | return "
    "; 204 | } } ); 205 | 206 | expect( View ).to.createElWithStandardMechanism; 207 | } ); 208 | 209 | } ); 210 | 211 | describe( 'A Marionette view remains true to its standard behaviour and throws an error on render(), or maybe even earlier', function () { 212 | 213 | it( 'when the view does not reference a template', function () { 214 | View = getMarionetteView().extend(); 215 | var view = new View(); 216 | expect( function () { view.render(); } ).to.throw( Error ); 217 | } ); 218 | 219 | it( 'when the view references a template for which the custom loader returns undefined', function () { 220 | // This already blows up when the view is instantiated because the invalid return value becomes a 221 | // problem not during render(), but when the el is constructed. 222 | Backbone.DeclarativeViews.custom.loadTemplate = function () {}; 223 | View = getMarionetteView().extend( { template: "#returnsVoid" } ); 224 | 225 | expect( function () { 226 | var view = new View(); 227 | view.render(); } 228 | ).to.throw( Error ); 229 | } ); 230 | 231 | it( 'when the view references a template with a string that the custom loader cannot process, throwing an error', function () { 232 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Error( "loadTemplate blew up" ); }; 233 | View = getMarionetteView().extend( { template: "#throwsError" } ); 234 | var view = new View(); 235 | expect( function () { view.render(); } ).to.throw( Error ); 236 | } ); 237 | 238 | } ); 239 | 240 | describe( 'Custom loader error checking', function () { 241 | 242 | describe( 'A friendly error is thrown', function () { 243 | 244 | it( 'when the custom loader returns a value without throwing an error, but that value is not a jQuery object', function () { 245 | Backbone.DeclarativeViews.custom.loadTemplate = function () { return "
    Returned template HTML is not wrapped in jQuery object
    " }; 246 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid return value. The custom loadTemplate function must return a jQuery instance" ); 247 | } ); 248 | 249 | it( 'when the custom loader returns undefined without throwing an error', function () { 250 | Backbone.DeclarativeViews.custom.loadTemplate = function () {}; 251 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid return value. The custom loadTemplate function must return a jQuery instance" ); 252 | } ); 253 | 254 | } ); 255 | 256 | describe( 'An error raised deliberately in the custom loader, with one of the error types in Backbone.DeclarativeViews, bubbles up uncaught', function () { 257 | 258 | // Generic errors in the loader are caught and suppressed, and Backbone just creates a standard el (see 259 | // tests above). However, he error types of Backbone.DeclarativeViews are allowed to bubble up. That 260 | // way, a custom loader can bypass the error handling and raise the alarm if it needs to. 261 | 262 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.Error', function () { 263 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Backbone.DeclarativeViews.Error( "a message from the loader" ); }; 264 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.Error, "a message from the loader" ); 265 | } ); 266 | 267 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.TemplateError', function () { 268 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Backbone.DeclarativeViews.TemplateError( "a message from the loader" ); }; 269 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.TemplateError, "a message from the loader" ); 270 | } ); 271 | 272 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.CompilerError', function () { 273 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Backbone.DeclarativeViews.CompilerError( "a message from the loader" ); }; 274 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CompilerError, "a message from the loader" ); 275 | } ); 276 | 277 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.CustomizationError', function () { 278 | Backbone.DeclarativeViews.custom.loadTemplate = function () { throw new Backbone.DeclarativeViews.CustomizationError( "a message from the loader" ); }; 279 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "a message from the loader" ); 280 | } ); 281 | 282 | } ); 283 | 284 | } ); 285 | 286 | 287 | } ); 288 | 289 | })(); -------------------------------------------------------------------------------- /spec/default.loader.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it */ 2 | (function () { 3 | "use strict"; 4 | 5 | describe( 'Default loader modification', function () { 6 | 7 | var dataAttributes, outerTemplateHtml, innerTemplateHtml, View, 8 | originalLoader, 9 | cleanup = function () { 10 | Backbone.DeclarativeViews.clearCache(); 11 | Backbone.Marionette.TemplateCache.clear(); 12 | }; 13 | 14 | beforeEach( function () { 15 | 16 | cleanup(); 17 | 18 | dataAttributes = { 19 | "data-tag-name": "ul", 20 | "data-class-name": "listClass", 21 | "data-id": "listId", 22 | "data-attributes": '{ "lang": "en", "title": "title from data attributes" }' 23 | }; 24 | 25 | // Construct the HTML strings 26 | // NB Normalize the template HTML by piping it through jQuery 27 | innerTemplateHtml = '
  • Inner list item
  • '; 28 | outerTemplateHtml = $( '
    ' ) 29 | .html( innerTemplateHtml ) 30 | .attr( dataAttributes ) 31 | .prop( 'outerHTML' ); 32 | 33 | originalLoader = Backbone.DeclarativeViews.defaults.loadTemplate; 34 | 35 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { 36 | return $( outerTemplateHtml ); 37 | }; 38 | 39 | View = Backbone.View.extend(); 40 | } ); 41 | 42 | afterEach( function () { 43 | cleanup(); 44 | Backbone.DeclarativeViews.defaults.loadTemplate = originalLoader; 45 | } ); 46 | 47 | describe( 'Arguments provided to the (modified) default loadTemplate method', function () { 48 | 49 | var view; 50 | 51 | beforeEach( function () { 52 | sinon.spy( Backbone.DeclarativeViews.defaults, "loadTemplate" ); 53 | } ); 54 | 55 | afterEach( function () { 56 | Backbone.DeclarativeViews.defaults.loadTemplate.restore(); 57 | } ); 58 | 59 | describe( 'When the default loader is called in the context of a view', function () { 60 | 61 | describe( 'with the template being set as a property on the view class', function () { 62 | 63 | beforeEach( function () { 64 | View = Backbone.View.extend( { template: "#template" } ); 65 | view = new View(); 66 | } ); 67 | 68 | it( 'it receives the template property of the view as first argument', function () { 69 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template" ); 70 | } ); 71 | 72 | it( 'it receives the view as second argument', function () { 73 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template", view ); 74 | } ); 75 | 76 | } ); 77 | 78 | describe( 'with the template being passed in as an option during view instantiation', function () { 79 | 80 | beforeEach( function () { 81 | view = new View( { template: "#template" } ); 82 | } ); 83 | 84 | it( 'it receives the template option passed to the view as first argument', function () { 85 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template" ); 86 | } ); 87 | 88 | it( 'it receives the view as second argument', function () { 89 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template", view ); 90 | } ); 91 | 92 | } ); 93 | 94 | describe( 'with a template being set as a property on the view class, and a different template being passed in as an option', function () { 95 | 96 | beforeEach( function () { 97 | View = Backbone.View.extend( { template: "#template" } ); 98 | view = new View( { template: "#otherTemplate" } ); 99 | } ); 100 | 101 | it( 'it receives the template option passed to the view as first argument', function () { 102 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#otherTemplate" ); 103 | } ); 104 | 105 | it( 'it receives the view as second argument', function () { 106 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#otherTemplate", view ); 107 | } ); 108 | 109 | } ); 110 | 111 | } ); 112 | 113 | describe( 'When the default loader is called from the global API', function () { 114 | 115 | describe( 'with the template selector provided as the only argument', function () { 116 | 117 | beforeEach( function () { 118 | Backbone.DeclarativeViews.getCachedTemplate( "#template" ); 119 | } ); 120 | 121 | it( 'the loader receives the template selector as first argument', function () { 122 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template" ); 123 | } ); 124 | 125 | it( 'the second argument provided to the loader is undefined', function () { 126 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template", undefined ); 127 | } ); 128 | 129 | } ); 130 | 131 | describe( 'with the template selector and a view provided as arguments', function () { 132 | 133 | beforeEach( function () { 134 | view = new View(); 135 | Backbone.DeclarativeViews.getCachedTemplate( "#template", view ); 136 | } ); 137 | 138 | it( 'the loader receives the template selector as first argument', function () { 139 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template" ); 140 | } ); 141 | 142 | it( 'the loader receives the view as second argument', function () { 143 | expect( Backbone.DeclarativeViews.defaults.loadTemplate ).to.have.been.calledWith( "#template", view ); 144 | } ); 145 | 146 | } ); 147 | 148 | } ); 149 | 150 | } ); 151 | 152 | describe( 'When the jQuery object of the template is provided by a (modified) default loadTemplate method', function () { 153 | 154 | var view; 155 | 156 | beforeEach( function () { 157 | view = new View( { template: "#template" } ); 158 | } ); 159 | 160 | it( 'its data attributes get applied to the el', function () { 161 | var attributes = JSON.parse( dataAttributes["data-attributes"] ); 162 | 163 | expect( view.el.tagName.toLowerCase() ).to.equal( dataAttributes["data-tag-name"] ); 164 | expect( view.$el.attr( "class" ) ).to.equal( dataAttributes["data-class-name"] ); 165 | expect( view.$el.attr( "id" ) ).to.equal( dataAttributes["data-id"] ); 166 | expect( view.$el.attr( "lang" ) ).to.equal( attributes.lang ); 167 | expect( view.$el.attr( "title" ) ).to.equal( attributes.title ); 168 | } ); 169 | 170 | it( 'it gets stored in the cache', function () { 171 | expect( view.declarativeViews.getCachedTemplate() ).to.returnCacheValueFor( dataAttributes, outerTemplateHtml ); 172 | } ); 173 | 174 | it( 'its inner HTML is returned as the template HTML by the cache', function () { 175 | expect( view.declarativeViews.getCachedTemplate().html ).to.eql( innerTemplateHtml ); 176 | } ); 177 | 178 | } ); 179 | 180 | describe( 'Backbone default behaviour remains unchanged', function () { 181 | 182 | it( 'when the view does not reference a template', function () { 183 | View = Backbone.View.extend(); 184 | expect( View ).to.createElWithStandardMechanism; 185 | } ); 186 | 187 | it( 'when the view references a template, but the return value of the default loader does not have data attributes describing the el', function () { 188 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { return $( "
    Some HTML
    " ); }; 189 | View = Backbone.View.extend( { template: "#noDataAttr" } ); 190 | expect( View ).to.createElWithStandardMechanism; 191 | } ); 192 | 193 | it( 'when the view references a template with a string that the default loader cannot process, throwing a generic error', function () { 194 | // NB ... but if the loader raises the alarm deliberately and throws one of the error types belonging to 195 | // Backbone.DeclarativeViews, business as usual is over: the exception is rethrown and bubbles up. See 196 | // test further below. 197 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Error( "loadTemplate blew up" ); }; 198 | View = Backbone.View.extend( { template: "#throwsError" } ); 199 | expect( View ).to.createElWithStandardMechanism; 200 | } ); 201 | 202 | it( 'when the view has a template property, but it is a function rather than a selector', function () { 203 | // That is because only string properties get forwarded to the cache mechanism, and eventually to the 204 | // default loader. 205 | View = Backbone.View.extend( { template: function () { 206 | return "
    "; 207 | } } ); 208 | 209 | expect( View ).to.createElWithStandardMechanism; 210 | } ); 211 | 212 | } ); 213 | 214 | describe( 'A Marionette view remains true to its standard behaviour and throws an error on render(), or maybe even earlier', function () { 215 | 216 | it( 'when the view does not reference a template', function () { 217 | View = getMarionetteView().extend(); 218 | var view = new View(); 219 | expect( function () { view.render(); } ).to.throw( Error ); 220 | } ); 221 | 222 | it( 'when the view references a template for which the default loader returns undefined', function () { 223 | // This already blows up when the view is instantiated because the invalid return value becomes a 224 | // problem not during render(), but when the el is constructed. 225 | Backbone.DeclarativeViews.defaults.loadTemplate = function () {}; 226 | View = getMarionetteView().extend( { template: "#returnsVoid" } ); 227 | 228 | expect( function () { 229 | var view = new View(); 230 | view.render(); } 231 | ).to.throw( Error ); 232 | } ); 233 | 234 | it( 'when the view references a template with a string that the default loader cannot process, throwing an error', function () { 235 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Error( "loadTemplate blew up" ); }; 236 | View = getMarionetteView().extend( { template: "#throwsError" } ); 237 | var view = new View(); 238 | expect( function () { view.render(); } ).to.throw( Error ); 239 | } ); 240 | 241 | } ); 242 | 243 | describe( 'Default loader error checking', function () { 244 | 245 | describe( 'A friendly error is thrown', function () { 246 | 247 | it( 'when the (modified) default loader returns a value without throwing an error, but that value is not a jQuery object', function () { 248 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { return "
    Returned template HTML is not wrapped in jQuery object
    " }; 249 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid return value. The default loadTemplate function must return a jQuery instance" ); 250 | } ); 251 | 252 | it( 'when the (modified) default loader returns undefined without throwing an error', function () { 253 | Backbone.DeclarativeViews.defaults.loadTemplate = function () {}; 254 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "Invalid return value. The default loadTemplate function must return a jQuery instance" ); 255 | } ); 256 | 257 | } ); 258 | 259 | describe( 'An error raised deliberately in the (modified) default loader, with one of the error types in Backbone.DeclarativeViews, bubbles up uncaught', function () { 260 | 261 | // Generic errors in the loader are caught and suppressed, and Backbone just creates a standard el (see 262 | // tests above). However, he error types of Backbone.DeclarativeViews are allowed to bubble up. That 263 | // way, a default loader can bypass the error handling and raise the alarm if it needs to. 264 | 265 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.Error', function () { 266 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Backbone.DeclarativeViews.Error( "a message from the loader" ); }; 267 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.Error, "a message from the loader" ); 268 | } ); 269 | 270 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.TemplateError', function () { 271 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Backbone.DeclarativeViews.TemplateError( "a message from the loader" ); }; 272 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.TemplateError, "a message from the loader" ); 273 | } ); 274 | 275 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.CompilerError', function () { 276 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Backbone.DeclarativeViews.CompilerError( "a message from the loader" ); }; 277 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CompilerError, "a message from the loader" ); 278 | } ); 279 | 280 | it( 'when that error is of type Backbone.Backbone.DeclarativeViews.CustomizationError', function () { 281 | Backbone.DeclarativeViews.defaults.loadTemplate = function () { throw new Backbone.DeclarativeViews.CustomizationError( "a message from the loader" ); }; 282 | expect( function () { new View( { template: "#template" } ); } ).to.throw( Backbone.DeclarativeViews.CustomizationError, "a message from the loader" ); 283 | } ); 284 | 285 | } ); 286 | 287 | } ); 288 | 289 | 290 | } ); 291 | 292 | })(); -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function (grunt) { 3 | 4 | var LIVERELOAD_PORT = 35731, 5 | HTTP_PORT = 9400, 6 | KARMA_PORT = 9877, 7 | WATCHED_FILES_SRC = [ 8 | 'src/**/*' 9 | ], 10 | WATCHED_FILES_SPEC = [ 11 | 'spec/**/*' 12 | ], 13 | WATCHED_FILES_DIST = [ 14 | 'dist/**/*' 15 | ], 16 | WATCHED_FILES_DEMO = [ 17 | 'demo/**/*' 18 | ], 19 | 20 | SINON_SOURCE_DIR = 'node_modules/karma-chai-plugins/node_modules/sinon/lib/sinon/', 21 | 22 | path = require( "path" ), 23 | requireFromString = require( "require-from-string" ), 24 | 25 | /** 26 | * Receives an object and the name of a path property on that object. Translates the path property to a new path, 27 | * based on a directory prefix. Does not return anything, modifies the object itself. 28 | * 29 | * The directory prefix can be relative (e.g. "../../"). It may or may not end in a slash. 30 | * 31 | * @param {string} dirPrefix 32 | * @param {Object} object 33 | * @param {string} propertyName 34 | * @param {boolean} [verbose=false] 35 | */ 36 | translatePathProperty = function ( dirPrefix, object, propertyName, verbose ) { 37 | var originalPath = object[propertyName]; 38 | 39 | if ( originalPath ) { 40 | object[propertyName] = path.normalize( dirPrefix + path.sep + originalPath ); 41 | if ( verbose ) grunt.log.writeln( 'Translating path property "' + propertyName + '": ' + originalPath + " => " + object[propertyName] ); 42 | } 43 | }, 44 | 45 | /** 46 | * Reads an r.js build profile and returns it as an options set for a grunt-contrib-requirejs task. 47 | * 48 | * For a discussion, see https://github.com/gruntjs/grunt-contrib-requirejs/issues/13 49 | * 50 | * Paths in the build profile are relative to the profile location. In the returned options object, they are 51 | * transformed to be relative to the Gruntfile. (The list is nowhere near complete. More properties need to be 52 | * transformed as build profiles become more complex.) 53 | * 54 | * @param {string} profilePath relative to the Gruntfile 55 | * @param {boolean} [verbose=false] 56 | * @returns {Object} 57 | */ 58 | getRequirejsBuildProfile = function ( profilePath, verbose ) { 59 | var profileContent = grunt.file.read( profilePath ), 60 | profile = requireFromString( "module.exports = " + profileContent ), 61 | 62 | dirPrefix = path.dirname( profilePath ); 63 | 64 | if ( verbose ) grunt.log.writeln( "Loading r.js build profile " + profilePath ); 65 | 66 | // Add more paths here as needed. 67 | translatePathProperty( dirPrefix, profile, "mainConfigFile", verbose ); 68 | translatePathProperty( dirPrefix, profile, "out", verbose ); 69 | 70 | if ( verbose ) grunt.log.writeln(); 71 | 72 | return profile; 73 | }; 74 | 75 | // Project configuration. 76 | grunt.config.init({ 77 | pkg: grunt.file.readJSON('package.json'), 78 | meta: { 79 | version: '<%= pkg.version %>', 80 | banner: '// Backbone.Declarative.Views, v<%= meta.version %>\n' + 81 | '// Copyright (c) 2014-<%= grunt.template.today("yyyy") %> Michael Heim, Zeilenwechsel.de\n' + 82 | '// Distributed under MIT license\n' + 83 | '// http://github.com/hashchange/backbone.declarative.views\n' + 84 | '\n' 85 | }, 86 | 87 | preprocess: { 88 | build: { 89 | files: { 90 | 'dist/backbone.declarative.views.js': 'src/backbone.declarative.views.js' 91 | } 92 | }, 93 | interactive: { 94 | files: { 95 | 'web-mocha/index.html': 'web-mocha/_index.html' 96 | } 97 | } 98 | }, 99 | 100 | concat: { 101 | options: { 102 | banner: "<%= meta.banner %>", 103 | process: function( src, filepath ) { 104 | var bowerVersion = grunt.file.readJSON( "bower.json" ).version, 105 | npmVersion = grunt.file.readJSON( "package.json" ).version; 106 | 107 | if ( npmVersion === undefined || npmVersion === "" ) grunt.fail.fatal( "Version number not specified in package.json. Specify it in bower.json and package.json" ); 108 | if ( npmVersion !== bowerVersion ) grunt.fail.fatal( "Version numbers in package.json and bower.json are not identical. Make them match." + " " + npmVersion ); 109 | if ( ! /^\d+\.\d+.\d+$/.test( npmVersion ) ) grunt.fail.fatal( 'Version numbers in package.json and bower.json are not semantic. Provide a version number in the format n.n.n, e.g "1.2.3"' ); 110 | return src.replace( "__COMPONENT_VERSION_PLACEHOLDER__", npmVersion ); 111 | } 112 | }, 113 | build: { 114 | src: 'dist/backbone.declarative.views.js', 115 | dest: 'dist/backbone.declarative.views.js' 116 | } 117 | }, 118 | 119 | uglify: { 120 | options: { 121 | banner: "<%= meta.banner %>", 122 | mangle: { 123 | except: ['jQuery', 'Zepto', 'Backbone', '_'] 124 | }, 125 | sourceMap: true 126 | }, 127 | core: { 128 | src: 'dist/backbone.declarative.views.js', 129 | dest: 'dist/backbone.declarative.views.min.js' 130 | } 131 | }, 132 | 133 | karma: { 134 | options: { 135 | configFile: 'karma.conf.js', 136 | browsers: ['PhantomJS'], 137 | port: KARMA_PORT 138 | }, 139 | test: { 140 | reporters: ['progress'], 141 | singleRun: true 142 | }, 143 | "test-legacy": { 144 | configFile: 'karma.legacy.conf.js', 145 | reporters: ['progress'], 146 | singleRun: true 147 | }, 148 | build: { 149 | reporters: ['progress'], 150 | singleRun: true 151 | } 152 | }, 153 | 154 | jshint: { 155 | components: { 156 | // Workaround for merging .jshintrc with Gruntfile options, see http://goo.gl/Of8QoR 157 | options: grunt.util._.merge({ 158 | globals: { 159 | // Add vars which are shared between various sub-components 160 | // (before concatenation makes them local) 161 | } 162 | }, grunt.file.readJSON('.jshintrc')), 163 | files: { 164 | src: ['src/**/*.js'] 165 | } 166 | }, 167 | concatenated: { 168 | options: grunt.util._.merge({ 169 | // Suppressing 'W034: Unnecessary directive "use strict"'. 170 | // Redundant nested "use strict" is ok in concatenated file, 171 | // no adverse effects. 172 | '-W034': true 173 | }, grunt.file.readJSON('.jshintrc')), 174 | files: { 175 | src: 'dist/**/backbone.declarative.views.js' 176 | } 177 | } 178 | }, 179 | 180 | 'sails-linker': { 181 | options: { 182 | startTag: '', 183 | endTag: '', 184 | fileTmpl: '', 185 | // relative doesn't seem to have any effect, ever 186 | relative: true, 187 | // appRoot is a misnomer for "strip out this prefix from the file path before inserting", 188 | // should be stripPrefix 189 | appRoot: '' 190 | }, 191 | interactive_spec: { 192 | options: { 193 | startTag: '', 194 | endTag: '' 195 | }, 196 | files: { 197 | // the target file is changed in place; for generating copies, run preprocess first 198 | 'web-mocha/index.html': ['spec/**/*.+(spec|test|tests).js'] 199 | } 200 | }, 201 | interactive_sinon: { 202 | options: { 203 | startTag: '', 204 | endTag: '' 205 | }, 206 | files: { 207 | // the target file is changed in place; for generating copies, run preprocess first 208 | // 209 | // The util/core.js file must be loaded first, and typeof.js must be loaded before match.js. 210 | // 211 | // mock.js must be loaded last (specifically, after spy.js). For the pattern achieving it, see 212 | // http://gruntjs.com/configuring-tasks#globbing-patterns 213 | 'web-mocha/index.html': [ 214 | SINON_SOURCE_DIR + 'util/core.js', 215 | SINON_SOURCE_DIR + 'typeof.js', 216 | SINON_SOURCE_DIR + '**/*.js', 217 | '!' + SINON_SOURCE_DIR + 'mock.js', 218 | SINON_SOURCE_DIR + 'mock.js' 219 | ] 220 | } 221 | } 222 | }, 223 | 224 | requirejs : { 225 | unifiedPlainBuild : { 226 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/unified/plain-build-config.js', false ) 227 | }, 228 | unifiedMarionetteBuild : { 229 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/unified/marionette-build-config.js', false ) 230 | }, 231 | unifiedPrecompiledPlainBuild : { 232 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/unified/plain-precompiled-build-config.js', false ) 233 | }, 234 | unifiedPrecompiledMarionetteBuild : { 235 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/unified/marionette-precompiled-build-config.js', false ) 236 | }, 237 | splitBuildVendor : { 238 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/vendor-config.js', false ) 239 | }, 240 | splitBuildBackboneApp : { 241 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/backbone-app-config.js', false ) 242 | }, 243 | splitBuildMarionetteApp : { 244 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/marionette-app-config.js', false ) 245 | }, 246 | splitBuildPrecompiledBackboneApp : { 247 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/backbone-precompiled-app-config.js', false ) 248 | }, 249 | splitBuildPrecompiledMarionetteApp : { 250 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/marionette-precompiled-app-config.js', false ) 251 | } 252 | }, 253 | 254 | // Use focus to run Grunt watch with a hand-picked set of simultaneous watch targets. 255 | focus: { 256 | demo: { 257 | include: ['livereloadDemo'] 258 | }, 259 | demoCi: { 260 | include: ['build', 'livereloadDemo'] 261 | }, 262 | demoCiDirty: { 263 | include: ['buildDirty', 'livereloadDemo'] 264 | } 265 | }, 266 | 267 | // Use watch to monitor files for changes, and to kick off a task then. 268 | watch: { 269 | options: { 270 | nospawn: false 271 | }, 272 | // Live-reloads the web page when the source files or the spec files change. Meant for test pages. 273 | livereloadTest: { 274 | options: { 275 | livereload: LIVERELOAD_PORT 276 | }, 277 | files: WATCHED_FILES_SRC.concat( WATCHED_FILES_SPEC ) 278 | }, 279 | // Live-reloads the web page when the dist files or the demo files change. Meant for demo pages. 280 | livereloadDemo: { 281 | options: { 282 | livereload: LIVERELOAD_PORT 283 | }, 284 | files: WATCHED_FILES_DEMO.concat( WATCHED_FILES_DIST ) 285 | }, 286 | // Runs the "build" task (ie, runs linter and tests, then compiles the dist files) when the source files or the 287 | // spec files change. Meant for continuous integration tasks ("ci", "demo-ci"). 288 | build: { 289 | tasks: ['build'], 290 | files: WATCHED_FILES_SRC.concat( WATCHED_FILES_SPEC ) 291 | }, 292 | // Runs the "build-dirty" task (ie, compiles the dist files without running linter and tests) when the source 293 | // files change. Meant for "dirty" continuous integration tasks ("ci-dirty", "demo-ci-dirty"). 294 | buildDirty: { 295 | tasks: ['build-dirty'], 296 | files: WATCHED_FILES_SRC 297 | } 298 | }, 299 | 300 | // Spins up a web server. 301 | connect: { 302 | options: { 303 | port: HTTP_PORT, 304 | // For restricting access to localhost only, change the hostname from '*' to 'localhost' 305 | hostname: '*', 306 | open: true, 307 | base: '.' 308 | }, 309 | livereload: { 310 | livereload: LIVERELOAD_PORT 311 | }, 312 | test: { 313 | options: { 314 | open: 'http://localhost:<%= connect.options.port %>/web-mocha/', 315 | livereload: LIVERELOAD_PORT 316 | } 317 | }, 318 | testNoReload: { 319 | options: { 320 | open: 'http://localhost:<%= connect.options.port %>/web-mocha/', 321 | keepalive: true 322 | } 323 | }, 324 | demo: { 325 | options: { 326 | open: 'http://localhost:<%= connect.options.port %>/demo/', 327 | livereload: LIVERELOAD_PORT 328 | } 329 | } 330 | }, 331 | 332 | replace: { 333 | version: { 334 | src: ['bower.json', 'package.json'], 335 | overwrite: true, 336 | replacements: [{ 337 | from: /"version"\s*:\s*"((\d+\.\d+\.)(\d+))"\s*,/, 338 | to: function (matchedWord, index, fullText, regexMatches) { 339 | var version = grunt.option('inc') ? regexMatches[1] + (parseInt(regexMatches[2], 10) + 1) : grunt.option('to'); 340 | 341 | if (version === undefined) grunt.fail.fatal('Version number not specified. Use the --to option, e.g. --to=1.2.3, or the --inc option to increment the revision'); 342 | if (typeof version !== "string") grunt.fail.fatal('Version number is not a string. Provide a semantic version number, e.g. --to=1.2.3'); 343 | if (!/^\d+\.\d+.\d+$/.test(version)) grunt.fail.fatal('Version number is not semantic. Provide a version number in the format n.n.n, e.g. --to=1.2.3'); 344 | 345 | grunt.log.writeln('Modifying file: Changing the version number from ' + regexMatches[0] + ' to ' + version); 346 | return '"version": "' + version + '",'; 347 | } 348 | }] 349 | } 350 | }, 351 | getver: { 352 | files: ['bower.json', 'package.json'] 353 | } 354 | }); 355 | 356 | grunt.loadNpmTasks('grunt-preprocess'); 357 | grunt.loadNpmTasks('grunt-contrib-concat'); 358 | grunt.loadNpmTasks('grunt-contrib-jshint'); 359 | grunt.loadNpmTasks('grunt-contrib-uglify'); 360 | grunt.loadNpmTasks('grunt-karma'); 361 | grunt.loadNpmTasks('grunt-contrib-watch'); 362 | grunt.loadNpmTasks('grunt-contrib-connect'); 363 | grunt.loadNpmTasks('grunt-sails-linker'); 364 | grunt.loadNpmTasks('grunt-text-replace'); 365 | grunt.loadNpmTasks('grunt-contrib-requirejs'); 366 | grunt.loadNpmTasks('grunt-focus'); 367 | 368 | grunt.registerTask('lint', ['jshint:components']); 369 | grunt.registerTask('hint', ['jshint:components']); // alias 370 | grunt.registerTask('test', ['jshint:components', 'karma:test']); 371 | grunt.registerTask('test-legacy', ['jshint:components', 'karma:test-legacy']); 372 | grunt.registerTask('webtest', ['preprocess:interactive', 'sails-linker:interactive_sinon', 'sails-linker:interactive_spec', 'connect:testNoReload']); 373 | grunt.registerTask('interactive', ['preprocess:interactive', 'sails-linker:interactive_sinon', 'sails-linker:interactive_spec', 'connect:test', 'watch:livereloadTest']); 374 | grunt.registerTask('demo', ['connect:demo', 'focus:demo']); 375 | grunt.registerTask('build', ['jshint:components', 'karma:build', 'preprocess:build', 'concat', 'uglify', 'jshint:concatenated', 'requirejs']); 376 | grunt.registerTask('ci', ['build', 'watch:build']); 377 | grunt.registerTask('setver', ['replace:version']); 378 | grunt.registerTask('getver', function () { 379 | grunt.config.get('getver.files').forEach(function (file) { 380 | var config = grunt.file.readJSON(file); 381 | grunt.log.writeln('Version number in ' + file + ': ' + config.version); 382 | }); 383 | }); 384 | 385 | // Special tasks, not mentioned in Readme documentation: 386 | // 387 | // - test-legacy: 388 | // runs the unit tests with legacy Marionette (version 2.x) 389 | // - requirejs: 390 | // creates build files for the AMD demo with r.js 391 | // - build-dirty: 392 | // builds the project without running checks (no linter, no tests) 393 | // - ci-dirty: 394 | // builds the project without running checks (no linter, no tests) on every source change 395 | // - demo-ci: 396 | // Runs the demo (= "demo" task), and also rebuilds the project on every source change (= "ci" task) 397 | // - demo-ci-dirty: 398 | // Runs the demo (= "demo" task), and also rebuilds the project "dirty", without tests or linter, on every source 399 | // change (= "ci-dirty" task) 400 | grunt.registerTask('build-dirty', ['preprocess:build', 'concat', 'uglify', 'requirejs']); 401 | grunt.registerTask('ci-dirty', ['build-dirty', 'watch:buildDirty']); 402 | grunt.registerTask('demo-ci', ['build', 'connect:demo', 'focus:demoCi']); 403 | grunt.registerTask('demo-ci-dirty', ['build-dirty', 'connect:demo', 'focus:demoCiDirty']); 404 | 405 | // Make 'build' the default task. 406 | grunt.registerTask('default', ['build']); 407 | 408 | 409 | }; 410 | -------------------------------------------------------------------------------- /spec/marionette.cache.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it */ 2 | (function () { 3 | "use strict"; 4 | 5 | // These tests require that Marionette has been loaded (after Backbone.Declarative.Views). We also make sure 6 | // joinMarionette() has been called. 7 | Backbone.DeclarativeViews.joinMarionette(); 8 | 9 | describe( 'Integrated cache access of Marionette and Backbone.Declarative.Views', function () { 10 | 11 | var dataAttributes, dataAttributes2, dataAttributes3, 12 | $templateNode, $templateNode2, $templateNode3, 13 | modifiedDataAttributes, modifiedHtml, View, 14 | 15 | cleanup = function () { 16 | Backbone.DeclarativeViews.clearCache(); 17 | Backbone.Marionette.TemplateCache.clear(); 18 | }; 19 | 20 | beforeEach ( function () { 21 | cleanup(); 22 | 23 | var genericTemplateTag = ''; 24 | 25 | dataAttributes = { 26 | "data-tag-name": "section", 27 | "data-class-name": "dataClass", 28 | "data-id": "dataId", 29 | "data-attributes": '{ "lang": "en", "title": "title from data attributes" }' 30 | }; 31 | 32 | dataAttributes2 = { 33 | "data-tag-name": "li", 34 | "data-class-name": "dataClass2", 35 | "data-id": "dataId2", 36 | "data-attributes": '{ "lang": "fr", "title": "title from data attributes 2" }' 37 | }; 38 | 39 | dataAttributes3 = { 40 | "data-tag-name": "h2", 41 | "data-class-name": "dataClass3", 42 | "data-id": "dataId3", 43 | "data-attributes": '{ "lang": "de", "title": "title from data attributes 3" }' 44 | }; 45 | 46 | $templateNode = $( genericTemplateTag ) 47 | .attr( "id", "template" ) 48 | .attr( dataAttributes ) 49 | .text( "Content of template #1" ) 50 | .appendTo( "body" ); 51 | 52 | $templateNode2 = $( genericTemplateTag ) 53 | .attr( "id", "template2" ) 54 | .attr( dataAttributes2 ) 55 | .text( "Content of template #2" ) 56 | .appendTo( "body" ); 57 | 58 | $templateNode3 = $( genericTemplateTag ) 59 | .attr( "id", "template3" ) 60 | .attr( dataAttributes3 ) 61 | .text( "Content of template #3" ) 62 | .appendTo( "body" ); 63 | 64 | modifiedDataAttributes = { 65 | "data-tag-name": "p", 66 | "data-class-name": "modifiedClass", 67 | "data-id": "modifiedId", 68 | "data-attributes": '{ "lang": "es", "title": "title from modified data attributes" }' 69 | }; 70 | 71 | modifiedHtml = "This is the modified template markup."; 72 | 73 | View = Backbone.View.extend(); 74 | 75 | // First access, priming the cache 76 | new View( { template: "#template" } ); 77 | new View( { template: "#template2" } ); 78 | new View( { template: "#template3" } ); 79 | } ); 80 | 81 | afterEach( function () { 82 | cleanup(); 83 | $templateNode.remove(); 84 | $templateNode2.remove(); 85 | $templateNode3.remove(); 86 | } ); 87 | 88 | describe( 'The Marionette.TemplateCache.clear() method', function () { 89 | 90 | describe( 'when called with arguments', function () { 91 | 92 | it( 'clears a given template from the Marionette cache if the template string is a selector', function () { 93 | // We test this by deleting the template node after first access, then clearing the cache. 94 | // On second access, the invalid cache query should throw an error. 95 | $templateNode.remove(); 96 | Backbone.Marionette.TemplateCache.clear( "#template" ); 97 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template" ) } ).to.throw( Error, 'Could not find template: "#template"' ); 98 | } ); 99 | 100 | it( 'clears a given template from the DeclarativeViews cache if the template string is a selector', function () { 101 | // We test this by deleting the template node after first access, then clearing the cache. 102 | // On second access, the cache should store the selector string itself (as the node no 103 | // longer exists). 104 | $templateNode.remove(); 105 | Backbone.Marionette.TemplateCache.clear( "#template" ); 106 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ).html ).to.equal( "#template" ); 107 | } ); 108 | 109 | it( 'allows changes made to the underlying template node to be picked up by Marionette', function () { 110 | $templateNode 111 | .attr( modifiedDataAttributes ) 112 | .html( modifiedHtml ); 113 | 114 | Backbone.Marionette.TemplateCache.clear( "#template" ); 115 | expect( ( Backbone.Marionette.TemplateCache.get( "#template" ) )() ).to.eql( modifiedHtml ); 116 | } ); 117 | 118 | it( 'allows changes made to the underlying template node to be picked up by Backbone.DeclarativeViews', function () { 119 | $templateNode 120 | .attr( modifiedDataAttributes ) 121 | .html( modifiedHtml ); 122 | 123 | Backbone.Marionette.TemplateCache.clear( "#template" ); 124 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ) ).to.returnCacheValueFor( modifiedDataAttributes, $templateNode ); 125 | } ); 126 | 127 | it( 'clears multiple templates from the Marionette cache when the selectors are passed as multiple arguments', function () { 128 | $templateNode.remove(); 129 | $templateNode2.remove(); 130 | $templateNode3.remove(); 131 | 132 | Backbone.Marionette.TemplateCache.clear( "#template", "#template2", "#template3" ); 133 | 134 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template" ) } ).to.throw( Error, 'Could not find template: "#template"' ); 135 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template2" ) } ).to.throw( Error, 'Could not find template: "#template2"' ); 136 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template3" ) } ).to.throw( Error, 'Could not find template: "#template3"' ); 137 | } ); 138 | 139 | it( 'clears multiple templates from the DeclarativeViews cache when the selectors are passed as multiple arguments', function () { 140 | $templateNode.remove(); 141 | $templateNode2.remove(); 142 | $templateNode3.remove(); 143 | 144 | Backbone.Marionette.TemplateCache.clear( "#template", "#template2", "#template3" ); 145 | 146 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ).html ).to.equal( "#template" ); 147 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ).html ).to.equal( "#template2" ); 148 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ).html ).to.equal( "#template3" ); 149 | } ); 150 | 151 | it( 'clears multiple templates from the Marionette cache when the selectors are passed as an array', function () { 152 | $templateNode.remove(); 153 | $templateNode2.remove(); 154 | $templateNode3.remove(); 155 | 156 | Backbone.Marionette.TemplateCache.clear( [ "#template", "#template2", "#template3" ] ); 157 | 158 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template" ) } ).to.throw( Error, 'Could not find template: "#template"' ); 159 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template2" ) } ).to.throw( Error, 'Could not find template: "#template2"' ); 160 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template3" ) } ).to.throw( Error, 'Could not find template: "#template3"' ); 161 | } ); 162 | 163 | it( 'clears multiple templates from the DeclarativeViews cache when the selectors are passed as an array', function () { 164 | $templateNode.remove(); 165 | $templateNode2.remove(); 166 | $templateNode3.remove(); 167 | 168 | Backbone.Marionette.TemplateCache.clear( [ "#template", "#template2", "#template3" ] ); 169 | 170 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ).html ).to.equal( "#template" ); 171 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ).html ).to.equal( "#template2" ); 172 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ).html ).to.equal( "#template3" ); 173 | } ); 174 | 175 | it.skip( 'does not clear other templates from the Marionette cache', function () { 176 | // Delete the template nodes so that their content indeed must come from the cache 177 | // 178 | // ATTN Skipped - Marionette is buggy, clears the whole cache (as of 2.4.7 and 3.0.0). This is not 179 | // caused by the Marionette integration code of Backbone.Declarative.Views. 180 | $templateNode2.remove(); 181 | $templateNode3.remove(); 182 | 183 | // Clear template #1 from the cache 184 | Backbone.Marionette.TemplateCache.clear( "#template" ); 185 | 186 | // Check cache for template #2 and template #3 - still there? 187 | expect( ( Backbone.Marionette.TemplateCache.get( "#template2" ) )() ).to.eql( $templateNode2.html() ); 188 | expect( ( Backbone.Marionette.TemplateCache.get( "#template3" ) )() ).to.eql( $templateNode3.html() ); 189 | } ); 190 | 191 | it( 'does not clear other templates from the DeclarativeViews cache', function () { 192 | // Delete the template nodes so that their content indeed must come from the cache 193 | $templateNode2.remove(); 194 | $templateNode3.remove(); 195 | 196 | // Clear template #1 from the cache 197 | Backbone.Marionette.TemplateCache.clear( "#template" ); 198 | 199 | // Check cache for template #2 and template #3 - still there? 200 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ) ).to.returnCacheValueFor( dataAttributes2, $templateNode2 ); 201 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ) ).to.returnCacheValueFor( dataAttributes3, $templateNode3 ); 202 | } ); 203 | 204 | it( 'fails silently if the template has already been removed from the cache', function () { 205 | // Removing the template node, which is a precondition for verifying that the cache has indeed been 206 | // cleared. 207 | $templateNode.remove(); 208 | 209 | Backbone.Marionette.TemplateCache.clear( "#template" ); 210 | 211 | // Second call, should go ahead without error 212 | Backbone.Marionette.TemplateCache.clear( "#template" ); 213 | 214 | // Checking that the cache is still empty, and querying it throws the expected error for Marionette, 215 | // and stores the selector string for Backbone.Declarative.Views (because the node no longer exists) 216 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template" ) } ).to.throw( Error, 'Could not find template: "#template"' ); 217 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ).html ).to.equal( "#template" ); 218 | } ); 219 | 220 | it( 'fails silently if the template is a string containing text which is not wrapped in HTML elements (uncacheable string), and leaves the existing cache intact', function () { 221 | Backbone.Marionette.TemplateCache.clear( "This is plain text with some markup, but not wrapped in an element" ); 222 | 223 | // Check the Marionette cache for templates #1 through #3 - still there? 224 | expect( ( Backbone.Marionette.TemplateCache.get( "#template" ) )() ).to.eql( $templateNode.html() ); 225 | expect( ( Backbone.Marionette.TemplateCache.get( "#template2" ) )() ).to.eql( $templateNode2.html() ); 226 | expect( ( Backbone.Marionette.TemplateCache.get( "#template3" ) )() ).to.eql( $templateNode3.html() ); 227 | 228 | // Check the DeclarativeVies cache for templates #1 through #3 - still there? 229 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ) ).to.returnCacheValueFor( dataAttributes, $templateNode ); 230 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ) ).to.returnCacheValueFor( dataAttributes2, $templateNode2 ); 231 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ) ).to.returnCacheValueFor( dataAttributes3, $templateNode3 ); 232 | } ); 233 | 234 | it( 'fails silently if the template is not a string, and leaves the existing cache intact', function () { 235 | Backbone.Marionette.TemplateCache.clear( function () { return "

    Template content

    "; } ); 236 | 237 | // Check the Marionette cache for templates #1 through #3 - still there? 238 | expect( ( Backbone.Marionette.TemplateCache.get( "#template" ) )() ).to.eql( $templateNode.html() ); 239 | expect( ( Backbone.Marionette.TemplateCache.get( "#template2" ) )() ).to.eql( $templateNode2.html() ); 240 | expect( ( Backbone.Marionette.TemplateCache.get( "#template3" ) )() ).to.eql( $templateNode3.html() ); 241 | 242 | // Check the DeclarativeVies cache for templates #1 through #3 - still there? 243 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ) ).to.returnCacheValueFor( dataAttributes, $templateNode ); 244 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ) ).to.returnCacheValueFor( dataAttributes2, $templateNode2 ); 245 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ) ).to.returnCacheValueFor( dataAttributes3, $templateNode3 ); 246 | } ); 247 | 248 | it( 'throws an error when called with an empty string argument', function () { 249 | expect( function () { Backbone.Marionette.TemplateCache.clear( "" ); } ).to.throw( Error ); 250 | } ); 251 | 252 | } ); 253 | 254 | describe( 'when called without arguments', function () { 255 | 256 | it( 'clears the entire Marionette cache', function () { 257 | // We test this by deleting all template nodes after first access, then clearing the cache. 258 | // On second access, the invalid cache query should throw an error. 259 | $templateNode.remove(); 260 | $templateNode2.remove(); 261 | $templateNode3.remove(); 262 | 263 | Backbone.Marionette.TemplateCache.clear(); 264 | 265 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template" ) } ).to.throw( Error, 'Could not find template: "#template"' ); 266 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template2" ) } ).to.throw( Error, 'Could not find template: "#template2"' ); 267 | expect( function () { Backbone.Marionette.TemplateCache.get( "#template3" ) } ).to.throw( Error, 'Could not find template: "#template3"' ); 268 | } ); 269 | 270 | it( 'clears the entire DeclarativeViews cache', function () { 271 | // We test this by deleting all template nodes after first access, then clearing the cache. 272 | // On second access, the cache should store the selector string itself for each selector 273 | // (as the node no longer exists). 274 | $templateNode.remove(); 275 | $templateNode2.remove(); 276 | $templateNode3.remove(); 277 | 278 | Backbone.Marionette.TemplateCache.clear(); 279 | 280 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template" ).html ).to.equal( "#template" ); 281 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template2" ).html ).to.equal( "#template2" ); 282 | expect( Backbone.DeclarativeViews.getCachedTemplate( "#template3" ).html ).to.equal( "#template3" ); 283 | } ); 284 | 285 | } ); 286 | 287 | } ); 288 | 289 | } ); 290 | 291 | })(); --------------------------------------------------------------------------------