├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── benchmark ├── bench.html ├── bench.js └── suite.js ├── bower.json ├── build.json ├── component.json ├── composer.json ├── lumbar.json ├── package.json ├── src ├── collection.js ├── data-object.js ├── deferrable.js ├── event.js ├── form.js ├── fragments │ └── scope.handlebars ├── helper-view.js ├── helpers │ ├── button-link.js │ ├── collection.js │ ├── element.js │ ├── empty.js │ ├── loading.js │ ├── super.js │ ├── template.js │ ├── url.js │ └── view.js ├── layout.js ├── loading.js ├── mixin.js ├── mobile.js ├── mobile │ └── tap-highlight.js ├── model.js ├── server-marshal.js ├── server-side.js ├── thorax.js └── util.js ├── tasks ├── build.js ├── fruit-loops.js ├── util │ └── git.js └── version.js └── test ├── README.md ├── fruit-loops.html ├── jquery-backbone-1-0.html ├── jquery.html ├── lib ├── backbone-1-0.js ├── expect.js ├── handlebars-reset.js ├── json2.js └── sinon-ie.js ├── src ├── collection.js ├── deferrable.js ├── event.js ├── form.js ├── helper-view.js ├── helpers │ ├── button-link.js │ ├── collection.js │ ├── element.js │ ├── empty.js │ ├── super.js │ ├── template.js │ ├── url.js │ └── view.js ├── layout.js ├── loading.js ├── model.js ├── server-marshal.js ├── server-side.js ├── thorax.js └── util.js ├── zepto-backbone-1-0.html └── zepto.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .project 3 | node_modules 4 | components 5 | build 6 | dist 7 | *.sublime-project 8 | *.sublime-workspace 9 | sauce_connect.log -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | /*dotfiles browser, thorax, mocha, chai*/ 2 | { 3 | "node": false, 4 | "esnext": false, 5 | "browser": true, 6 | "bitwise": true, 7 | "curly": true, 8 | "eqeqeq": false, 9 | "eqnull": true, 10 | "forin": true, 11 | "immed": false, 12 | "latedef": false, 13 | "newcap": true, 14 | "noarg": true, 15 | "noempty": true, 16 | "nonew": false, 17 | "plusplus": false, 18 | "regexp": false, 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "trailing": true, 23 | "maxparams": 5, 24 | "asi": false, 25 | "boss": false, 26 | "expr": true, 27 | "laxbreak": true, 28 | "loopfunc": true, 29 | "shadow": true, 30 | "nonstandard": true, 31 | "onevar": false, 32 | "-W082": true, 33 | "predef": [ 34 | "$", 35 | "_", 36 | "Backbone", 37 | "Handlebars", 38 | "Thorax", 39 | "describe", 40 | "it", 41 | "before", 42 | "after", 43 | "beforeEach", 44 | "afterEach", 45 | "chai", 46 | "expect", 47 | "sinon" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 0.1 5 | before_script: 6 | - npm install -g grunt-cli 7 | - "./node_modules/bower/bin/bower install" 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | - sleep 5 11 | notifications: 12 | webhooks: 13 | urls: 14 | secure: RaVEv4a1Dk75pN5R+0/gfdZleNvw++ojCkwxS1Nrm6hCWLAdfRpDL5QPq/a6iKYu6+HFEt5b37YUIx14Q12L7BhVBtLQhbxEwRV0gD00CcfuxqHENv52vNIVXH0MkENFwW0hE9HaMVXkFho0IvHmSBYNXSQywyP6LhL3fP+Ia+Y= 15 | on_success: change 16 | on_failure: always 17 | env: 18 | global: 19 | - secure: BE8kRmR7tK6Akxru7zEdXs6W6vQnJYWluBtKi3DUJx29CQ6lzD8OvnBLOl2MaVeeXK63xDxhKQC8OQZ8Ux5Y78WYnWpKtA+r/GZuhkMQCIx7z+xpnBlWZCGjxK0USrlnuZL1sBA1AI7lB3burXuxivAfj++j1WdcX2jBl91204U= 20 | - secure: TCgiYb5FeyuXq2bmCguavSMZuAUhz2oZZPIIY+ksIPxEZY6eoIRvMDOZeJnUknNnDzX/XhXXFGSgSZ6fKflNH4xEeQeonxRWczh0sF5p8i8tv+ZUs4PECBuy4phRBISfJ4mtF5ai42t1x4EBgv506Zo9Ea6I/akAj9H7+d7b1bw= 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Reporting Issues 4 | 5 | Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. Jsfiddle provides a Thorax framework target to ease creating test cases. 6 | 7 | Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed. 8 | 9 | ## Pull Requests 10 | 11 | We also accept [pull requests][pull-request]! 12 | 13 | Generally we like to see pull requests that 14 | - Maintain the existing code style 15 | - Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request) 16 | - Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 17 | - Have tests 18 | 19 | ## Building 20 | 21 | To build you'll need a few things installed: 22 | 23 | * Node.js 24 | * [Grunt](http://gruntjs.com/getting-started) 25 | 26 | Project dependencies may be installed via `npm install`. 27 | 28 | The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/jquery/test.html` and `http://localhost:9999/zepto/test.html`. 29 | 30 | If you notice any problems, please report them to the GitHub issue tracker. 31 | 32 | ## Releasing 33 | 34 | Thorax utilizes the [release yeoman generator][generator-release] to perform most release tasks. 35 | 36 | ```sh 37 | npm install -g yo generator-release 38 | ``` 39 | 40 | A full release may be completed with the following: 41 | 42 | ```sh 43 | grunt clean thorax:build 44 | yo release 45 | npm publish 46 | yo release:publish cdnjs thorax build/release 47 | yo release:publish components thorax build/release 48 | ``` 49 | 50 | [generator-release]: https://github.com/walmartlabs/generator-release 51 | [pull-request]: https://github.com/walmartlabs/thorax/pull/new/master 52 | [issue]: https://github.com/walmartlabs/thorax/issues/new 53 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | var grep = grunt.option('grep'), 3 | mochaArgs = grep ? '?grep=' + grep : ''; 4 | 5 | grunt.initConfig({ 6 | jshint: { 7 | options: { 8 | jshintrc: '.jshintrc' 9 | }, 10 | files: [ 11 | 'src/**/*.js' 12 | ] 13 | }, 14 | 15 | clean: ['build'], 16 | 17 | 'phoenix-build': { 18 | dev: { 19 | options: { 20 | lumbarFile: 'build.json', 21 | build: true, 22 | output: 'build/dev', 23 | sourceMap: true 24 | } 25 | } 26 | }, 27 | 28 | connect: { 29 | server: { 30 | options: { 31 | base: 'build/dev', 32 | hostname: '*', 33 | port: 9999 34 | } 35 | } 36 | }, 37 | 38 | 'mocha_phantomjs': { 39 | options: { 40 | reporter: 'dot' 41 | }, 42 | quick: { 43 | options: { 44 | urls: [ 45 | 'http://localhost:9999/jquery/test.html' + mochaArgs, 46 | 'http://localhost:9999/zepto/test.html' + mochaArgs 47 | ] 48 | } 49 | }, 50 | legacy: { 51 | options: { 52 | urls: [ 53 | 'http://localhost:9999/jquery-backbone-1-0/test.html' + mochaArgs, 54 | 'http://localhost:9999/zepto-backbone-1-0/test.html' + mochaArgs 55 | ] 56 | } 57 | } 58 | }, 59 | 60 | 'saucelabs-mocha': { 61 | options: { 62 | testname: 'thorax', 63 | build: process.env.TRAVIS_JOB_ID, 64 | tunnelArgs: [ 65 | '--verbose' 66 | ] 67 | }, 68 | jquery: { 69 | options: { 70 | tags: ['jquery'], 71 | urls: [ 72 | 'http://localhost:9999/jquery/test.html', 73 | 'http://localhost:9999/jquery-backbone-1-0/test.html' 74 | ], 75 | browsers: [ 76 | {browserName: 'chrome'}, 77 | {browserName: 'firefox'}, 78 | {browserName: 'safari', version: 7, platform: 'OS X 10.9'}, 79 | {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, 80 | {browserName: 'internet explorer', version: 8, platform: 'XP'} 81 | ] 82 | } 83 | }, 84 | zepto: { 85 | options: { 86 | tags: ['zepto'], 87 | urls: [ 88 | 'http://localhost:9999/zepto/test.html' 89 | ], 90 | browsers: [ 91 | {browserName: 'chrome'}, 92 | {browserName: 'firefox'}, 93 | {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'} 94 | ] 95 | } 96 | } 97 | }, 98 | 99 | watch: { 100 | scripts: { 101 | options: { 102 | atBegin: true 103 | }, 104 | 105 | files: ['src/**/*.js', 'test/**/*.js'], 106 | tasks: ['jshint', 'phoenix-build:dev', 'mocha_phantomjs:quick', 'fruit-loops:test'] 107 | } 108 | } 109 | }); 110 | 111 | grunt.loadNpmTasks('grunt-contrib-clean'); 112 | grunt.loadNpmTasks('grunt-contrib-connect'); 113 | grunt.loadNpmTasks('grunt-contrib-jshint'); 114 | grunt.loadNpmTasks('grunt-contrib-watch'); 115 | grunt.loadNpmTasks('grunt-mocha-phantomjs'); 116 | grunt.loadNpmTasks('grunt-saucelabs'); 117 | grunt.loadNpmTasks('phoenix-build'); 118 | 119 | grunt.loadTasks('tasks'); 120 | 121 | 122 | grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['saucelabs-mocha:zepto', 'saucelabs-mocha:jquery'] : []); 123 | 124 | grunt.registerTask('test', ['clean', 'connect', 'jshint', 'phoenix-build', 'fruit-loops:test', 'mocha_phantomjs', 'sauce']); 125 | grunt.registerTask('dev', ['clean', 'connect', 'watch']); 126 | }; 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2013 @WalmartLabs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | -------------------------------------------------------------------------------- /benchmark/bench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |template
')[0]; 14 | } 15 | }); 16 | childReturningElement.render(); 17 | expect(childReturningElement.$('p').html()).to.equal('template'); 18 | var childReturning$ = new Thorax.View({ 19 | template: function() { 20 | return $('template
'); 21 | } 22 | }); 23 | childReturning$.render(); 24 | expect(childReturning$.$('p').html()).to.equal('template'); 25 | }); 26 | 27 | it("template yield", function() { 28 | Handlebars.templates['yield-child'] = Handlebars.compile('{{@yield}}'); 29 | Handlebars.templates['yield-parent'] = Handlebars.compile('{{#template "yield-child"}}content{{/template}}
'); 30 | var view = new Thorax.View({ 31 | name: 'yield-parent' 32 | }); 33 | view.render(); 34 | expect(view.$('p > span').html()).to.equal('content'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/src/helpers/url.js: -------------------------------------------------------------------------------- 1 | describe('url helper', function() { 2 | it("url helper", function() { 3 | var href = Handlebars.helpers.url.call({}, '/a/{{b}}', {'expand-tokens': true}); 4 | expect(href).to.equal('#/a/'); 5 | href = Handlebars.helpers.url.call({b: 'b'}, '/a/{{b}}', {'expand-tokens': true}); 6 | expect(href).to.equal('#/a/b'); 7 | href = Handlebars.helpers.url.call({b: 'c'}, '/a/{{b}}', {'expand-tokens': true}); 8 | expect(href).to.equal('#/a/c'); 9 | 10 | href = Handlebars.helpers.url('a', 'c', {}); 11 | expect(href).to.equal('#a/c'); 12 | }); 13 | 14 | describe('urls are properly encoded', function () { 15 | it('when joining multiple arguments automatically', function () { 16 | // uses encodeURIComponent in /src/helpers/url.js 17 | var slug = "hello world, sup!", 18 | actual = Handlebars.helpers.url('articles', slug, {}), 19 | expected = '#articles/hello%20world%2C%20sup!'; 20 | 21 | expect(actual).to.equal(expected); 22 | }); 23 | it('when using expand-tokens=true (bug)', function () { 24 | // uses Thorax.Util.expandToken from /src/util.js, line 260 25 | var context = {slug: "hello world, sup!"}, 26 | actual = Handlebars.helpers.url.call(context, '/articles/{{slug}}', {'expand-tokens': true}), 27 | expected = '#/articles/hello%20world%2C%20sup!'; 28 | 29 | expect(actual).to.equal(expected); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/src/helpers/view.js: -------------------------------------------------------------------------------- 1 | describe('view helper', function() { 2 | it("throws an error when template compiled without data", function() { 3 | expect(function() { 4 | Handlebars.helpers.view({}, {}); 5 | }).to.throwError(); 6 | }); 7 | 8 | it("throws an error when any hash arguments are passed on an instance", function() { 9 | var view = new Thorax.View({ 10 | instance: new Thorax.View({ 11 | template: Handlebars.compile('') 12 | }), 13 | template: Handlebars.compile('{{view instance tag="span" key="value"}}') 14 | }); 15 | expect(function() { 16 | view.render(); 17 | }).to.throwError(); 18 | }); 19 | 20 | it("should allow hash arguments when a view class name is passed", function() { 21 | Thorax.View.extend({ 22 | tagName: 'p', 23 | name: 'HashArgsClassTest', 24 | template: Handlebars.compile('{{key}}') 25 | }); 26 | var view = new Thorax.View({ 27 | template: Handlebars.compile('{{view "Outer.Inner"}}
{{key}}
'), 161 | key: 'value' 162 | }); 163 | view.render(); 164 | expect(view.$('p').html()).to.equal('value'); 165 | 166 | var view = new (Thorax.View.extend({ 167 | template: Handlebars.compile('{{key}}
'), 168 | key: 'value' 169 | }))(); 170 | view.render(); 171 | expect(view.$('p').html()).to.equal('value'); 172 | 173 | var Child = Thorax.View.extend({ 174 | template: Handlebars.compile('My Layout View {{layout-element}}
') 319 | }); 320 | 321 | var childView = new Thorax.View({ 322 | template: Handlebars.compile('My Child View
') 323 | }); 324 | 325 | layoutView.setView(childView, {serverRender: true, async: false}); 326 | 327 | expect(layoutView.$(".layout-view").length).to.not.equal(0); 328 | expect(layoutView.$(".child-view").length).to.not.equal(0); 329 | 330 | layoutView.render(); 331 | 332 | expect(layoutView.$(".layout-view").length).to.not.equal(0); 333 | expect(layoutView.$(".child-view").length).to.not.equal(0); 334 | }); 335 | }); 336 | -------------------------------------------------------------------------------- /test/src/model.js: -------------------------------------------------------------------------------- 1 | /*global $serverSide */ 2 | 3 | describe('model', function() { 4 | it("shouldFetch", function() { 5 | var options = {fetch: true}; 6 | var a = new (Thorax.Model.extend())(); 7 | expect(a.shouldFetch(options)).to.not.be.ok(); 8 | 9 | var b = new (Thorax.Model.extend({urlRoot: '/'}))(); 10 | expect(b.shouldFetch(options)).to.be(true); 11 | 12 | var c = new (Thorax.Model.extend({urlRoot: '/'}))(); 13 | c.set({key: 'value'}); 14 | expect(c.shouldFetch(options)).to.not.be.ok(); 15 | 16 | var d = new (Thorax.Collection.extend())(); 17 | expect(d.shouldFetch(options)).to.not.be.ok(); 18 | 19 | var e = new (Thorax.Collection.extend({url: '/'}))(); 20 | expect(e.shouldFetch(options)).to.be(true); 21 | 22 | var f = new (Thorax.Collection.extend({url: '/'}))(); 23 | expect(e.shouldFetch({fetch: false})).to.be(false); 24 | }); 25 | 26 | it("allow model url to be a string", function() { 27 | var model = new (Thorax.Model.extend({ 28 | url: '/test' 29 | }))(); 30 | expect(model.shouldFetch({fetch: true})).to.be(true); 31 | }); 32 | 33 | describe('model view binding', function() { 34 | var model, 35 | template; 36 | beforeEach(function() { 37 | model = new Thorax.Model({letter: 'a'}); 38 | template = Handlebars.compile('