├── Resources ├── kss │ ├── app.styl │ ├── coffeedemo.styl │ ├── coffeedemo.kss │ ├── app.kss │ └── backbonedemo.kss ├── test │ ├── enabled.js │ ├── demo.js │ └── core.js ├── jade │ ├── table.jade │ └── test.jade ├── android │ ├── appicon.png │ ├── default.png │ └── images │ │ ├── res-long-land-hdpi │ │ └── default.png │ │ ├── res-long-land-ldpi │ │ └── default.png │ │ ├── res-long-port-hdpi │ │ └── default.png │ │ ├── res-long-port-ldpi │ │ └── default.png │ │ ├── res-notlong-land-hdpi │ │ └── default.png │ │ ├── res-notlong-land-ldpi │ │ └── default.png │ │ ├── res-notlong-land-mdpi │ │ └── default.png │ │ ├── res-notlong-port-hdpi │ │ └── default.png │ │ ├── res-notlong-port-ldpi │ │ └── default.png │ │ └── res-notlong-port-mdpi │ │ └── default.png ├── iphone │ ├── Default.png │ ├── appicon.png │ ├── Default@2x.png │ ├── Default-Portrait.png │ └── Default-Landscape.png ├── kranium │ ├── lib │ │ ├── images │ │ │ ├── tab-active.png │ │ │ ├── tab-inactive.png │ │ │ ├── tab-pressed.png │ │ │ ├── android-navbar-button.png │ │ │ ├── android-navbar-overlay.png │ │ │ ├── pull-to-refresh-arrow.png │ │ │ ├── android-navbar-separator.png │ │ │ └── android-navbar-button-pressed.png │ │ ├── test │ │ │ ├── tests.js │ │ │ └── jasmine-titanium-node.js │ │ ├── kss │ │ │ ├── pulltorefreshtableview.kss │ │ │ └── androidshim.kss │ │ └── backbone │ │ │ ├── underscore.js │ │ │ └── backbone.js │ └── kranium-generated-bootstrap.js ├── test-backup │ ├── tests.js │ ├── jquery-core.js │ └── traversing.js ├── kui │ ├── complex.js │ ├── todolist.js │ ├── events.js │ ├── coffeedemo.coffee │ ├── backbonedemo.js │ ├── shims.js │ └── coffeedemo.js ├── app.js └── jade.js ├── LICENSE.txt ├── CHANGELOG.txt ├── .gitignore ├── manifest ├── README ├── tiapp.xml └── LICENSE /Resources/kss/app.styl: -------------------------------------------------------------------------------- 1 | window 2 | background-color #fff -------------------------------------------------------------------------------- /Resources/test/enabled.js: -------------------------------------------------------------------------------- 1 | this.tests_enabled = true; 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Place your license text here. This file will be incorporated with your app at package time. -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | Place your change log text here. This file will be incorporated with your app at package time. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | build 3 | api.jsca 4 | .settings 5 | .project 6 | .monitor 7 | .fastdev.lock 8 | build.log 9 | -------------------------------------------------------------------------------- /Resources/jade/table.jade: -------------------------------------------------------------------------------- 1 | tableview.board 2 | - each msg, user in users 3 | label.msg= user + ' says ' + msg -------------------------------------------------------------------------------- /Resources/android/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/appicon.png -------------------------------------------------------------------------------- /Resources/android/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/default.png -------------------------------------------------------------------------------- /Resources/iphone/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/iphone/Default.png -------------------------------------------------------------------------------- /Resources/iphone/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/iphone/appicon.png -------------------------------------------------------------------------------- /Resources/iphone/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/iphone/Default@2x.png -------------------------------------------------------------------------------- /Resources/iphone/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/iphone/Default-Portrait.png -------------------------------------------------------------------------------- /Resources/iphone/Default-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/iphone/Default-Landscape.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/tab-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/tab-active.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/tab-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/tab-inactive.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/tab-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/tab-pressed.png -------------------------------------------------------------------------------- /Resources/android/images/res-long-land-hdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-long-land-hdpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-long-land-ldpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-long-land-ldpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-long-port-hdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-long-port-hdpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-long-port-ldpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-long-port-ldpi/default.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/android-navbar-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/android-navbar-button.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/android-navbar-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/android-navbar-overlay.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/pull-to-refresh-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/pull-to-refresh-arrow.png -------------------------------------------------------------------------------- /Resources/kranium/lib/images/android-navbar-separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/android-navbar-separator.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-land-hdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-land-hdpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-land-ldpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-land-ldpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-land-mdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-land-mdpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-port-hdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-port-hdpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-port-ldpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-port-ldpi/default.png -------------------------------------------------------------------------------- /Resources/android/images/res-notlong-port-mdpi/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/android/images/res-notlong-port-mdpi/default.png -------------------------------------------------------------------------------- /Resources/jade/test.jade: -------------------------------------------------------------------------------- 1 | view.board 2 | label.boardTitle Board 3 | view.boardMessages 4 | - each msg, user in users 5 | label.boardMessage= user + ' says ' + msg -------------------------------------------------------------------------------- /Resources/kranium/lib/images/android-navbar-button-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krawaller/kranium-demo/HEAD/Resources/kranium/lib/images/android-navbar-button-pressed.png -------------------------------------------------------------------------------- /manifest: -------------------------------------------------------------------------------- 1 | #appname: kraniumdemo 2 | #publisher: jaco 3 | #url: http://blog.krawaller.se 4 | #image: appicon.png 5 | #appid: se.krawaller.kraniumdemo 6 | #desc: undefined 7 | #type: mobile 8 | #guid: ebf1bac7-f0db-42c4-8552-ed65c7e86206 9 | -------------------------------------------------------------------------------- /Resources/kranium/lib/test/tests.js: -------------------------------------------------------------------------------- 1 | (function(global){ 2 | if (global.tests_enabled) { 3 | Ti.include('/kranium/lib/test/jasmine-1.0.2.js'); 4 | Ti.include('/kranium/lib/test/jasmine-titanium-node.js'); 5 | 6 | jasmine.getEnv().addReporter(new jasmine.TitaniumNodeReporter()); 7 | jasmine.getEnv().execute(); 8 | } 9 | })(this); -------------------------------------------------------------------------------- /Resources/kranium/kranium-generated-bootstrap.js: -------------------------------------------------------------------------------- 1 | /* BEWARE - generated file ahead */ 2 | (function(global){ 3 | if(global.BOOTSTRAPPED){ return; } 4 | global.DEBUG = false; 5 | global.USE_BACKBONE = false; 6 | global.TEST = false; 7 | global.BOOTSTRAPPED = true; 8 | if(K.is.ios){ 9 | K.watch("10.10.10.2", "8128"); 10 | } 11 | })(this); -------------------------------------------------------------------------------- /Resources/test-backup/tests.js: -------------------------------------------------------------------------------- 1 | (function(global){ 2 | if (global.tests_enabled) { 3 | Ti.include('/kranium/lib/test/jasmine-1.0.2.js'); 4 | Ti.include('/kranium/lib/test/jasmine-titanium-node.js'); 5 | 6 | // Include all the test files 7 | //Ti.include('/test/core.js'); 8 | 9 | jasmine.getEnv().addReporter(new jasmine.TitaniumNodeReporter()); 10 | jasmine.getEnv().execute(); 11 | } 12 | })(this); -------------------------------------------------------------------------------- /Resources/kui/complex.js: -------------------------------------------------------------------------------- 1 | exports.Class = View.extend({ 2 | init: function(o){ 3 | 4 | this.children = [{ 5 | type: 'label', 6 | width: 100, 7 | height: 20, 8 | backgroundColor: '#ff0', 9 | left: 0, 10 | text: o.name || 'pettson' 11 | }, 12 | { 13 | type: 'label', 14 | width: 100, 15 | right: 0, 16 | text: o.surname || 'findus' 17 | }]; 18 | 19 | this._super(o); 20 | 21 | } 22 | }); -------------------------------------------------------------------------------- /Resources/kui/todolist.js: -------------------------------------------------------------------------------- 1 | // kui/todolist.js 2 | 3 | exports.Class = BackboneView.extend({ 4 | type: 'tableview', 5 | editable: true, 6 | 7 | events: { 8 | click: function(e){ 9 | var model = todos.getByCid(e.rowData._modelCid); 10 | model.set({ hasCheck: !model.get('hasCheck') }); 11 | }, 12 | "delete": function(e){ 13 | var model = todos.getByCid(e.rowData._modelCid); 14 | todos.remove(model); 15 | } 16 | } 17 | }); -------------------------------------------------------------------------------- /Resources/kss/coffeedemo.styl: -------------------------------------------------------------------------------- 1 | .demoitem 2 | width: 200 3 | height 40 4 | top 10 5 | 6 | .swipeme 7 | border-radius 5 8 | background-color #f00 9 | text-align center 10 | 11 | .clickme 12 | top 10 13 | 14 | .coffeetable 15 | height 200 16 | border-width 1 17 | border-color #ccc 18 | border-radius 7 19 | 20 | .lefty 21 | left 10 22 | width 100 23 | font-size 20 24 | font-weight bold 25 | 26 | .righty 27 | right 10 28 | width 100 29 | text-align right -------------------------------------------------------------------------------- /Resources/kss/coffeedemo.kss: -------------------------------------------------------------------------------- 1 | .demoitem { 2 | width: 240; 3 | height: 40; 4 | top: 10; 5 | } 6 | .swipeme { 7 | border-radius: 15; 8 | background-color: #f00; 9 | text-align: right; 10 | } 11 | .clickme { 12 | top: 10; 13 | } 14 | .coffeetable { 15 | height: 200; 16 | border-width: 4; 17 | border-color: #ccc; 18 | border-radius: 7; 19 | } 20 | .lefty { 21 | left: 10; 22 | width: 100; 23 | font-size: 20; 24 | font-weight: bold; 25 | } 26 | .righty { 27 | right: 10; 28 | width: 100; 29 | text-align: right; 30 | } 31 | -------------------------------------------------------------------------------- /Resources/kss/app.kss: -------------------------------------------------------------------------------- 1 | window { 2 | background-color: #fff; 3 | bar-color: #23aaff; 4 | } 5 | 6 | tabbedbar { 7 | style: Ti.UI.iPhone.SystemButtonStyle.BAR; 8 | } 9 | 10 | buttonbar { 11 | style: Ti.UI.iPhone.SystemButtonStyle.BAR; 12 | } 13 | 14 | .toolbar { 15 | background-color: #555; 16 | } 17 | 18 | .board { 19 | top: 10; 20 | left: 10; 21 | } 22 | 23 | .boardMessages { 24 | top: 30; 25 | layout: vertical; 26 | } 27 | 28 | .boardMessage { 29 | height: 20; 30 | top: 10; 31 | } 32 | 33 | .boardTitle { 34 | top: 0; 35 | height: auto; 36 | font-weight: bold; 37 | } -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Welcome to your Appcelerator Titanium Mobile Project 2 | 3 | This is a blank project. Start by editing your application's app.js to 4 | make your first mobile project using Titanium. 5 | 6 | 7 | 8 | ---------------------------------- 9 | Stuff our legal folk make us say: 10 | 11 | Appcelerator, Appcelerator Titanium and associated marks and logos are 12 | trademarks of Appcelerator, Inc. 13 | 14 | Titanium is Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. 15 | 16 | Titanium is licensed under the Apache Public License (Version 2). Please 17 | see the LICENSE file for the full license. 18 | 19 | -------------------------------------------------------------------------------- /Resources/test/demo.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | // To learn how to write Jasmine tests, please read Jasmine documentation: 4 | // https://github.com/pivotal/jasmine/wiki 5 | 6 | describe('Demo', function() { 7 | describe('Titanium Object', function(){ 8 | it('Ti == Titanium', function(){ expect(Titanium).toEqual(Ti); }); 9 | it('Ti.App', function(){ expect(Titanium).toBeDefined(); }); 10 | }); 11 | 12 | describe('TabGroup', function(){ 13 | it('Has tabgroup', function(){ 14 | expect(K('tabgroup').length).toBeGreaterThan(0); 15 | }); 16 | 17 | it('First tab title should be "Shims"', function(){ 18 | expect(K('tabgroup').get(0).activeTab.title).toEqual("Shims"); 19 | }); 20 | }); 21 | }); 22 | 23 | })(); -------------------------------------------------------------------------------- /Resources/kss/backbonedemo.kss: -------------------------------------------------------------------------------- 1 | .titleLabel { 2 | color: #fff; 3 | shadow-color: #aa000000; 4 | shadow-offset-y: -1; 5 | shadow-offset-x: 0; 6 | font-size: 20dp; 7 | font-weight: bold; 8 | width: 120dp; 9 | height: 30dp; 10 | text-align: right; 11 | } 12 | 13 | .todolist { 14 | top: 44dp; 15 | } 16 | 17 | .todoToolbar { 18 | top: 0; 19 | border-bottom: true; 20 | border-top: false; 21 | } 22 | 23 | .todoInputTextField { 24 | border-style: Ti.UI.INPUT_BORDERSTYLE_ROUNDED; 25 | height: 30dp; 26 | width: 160dp; 27 | hint-text: New todo; 28 | color: #000; 29 | text-align: left; 30 | } 31 | 32 | .toolbar { 33 | background-color: #23aaff; 34 | } 35 | 36 | .todoInputTextField:android { 37 | height: 40dp; 38 | } 39 | 40 | .toolbar:android { 41 | height: 44dp; 42 | } -------------------------------------------------------------------------------- /Resources/app.js: -------------------------------------------------------------------------------- 1 | Ti.include("/kranium/lib/kranium.js"); 2 | 3 | K.initBackbone(); 4 | 5 | K({ 6 | type: 'tabgroup', 7 | tabs: [{ 8 | title: 'Shims', 9 | window: { 10 | type: 'shims' 11 | } 12 | },{ 13 | title: 'Backbone', 14 | window: { 15 | type: 'backbonedemo' 16 | } 17 | }, 18 | { 19 | title: 'Coffee', 20 | window: { 21 | title: 'Coffee Demo', 22 | children: [{ 23 | type: 'coffeedemo', 24 | text: 'What goes around' 25 | }] 26 | } 27 | }].concat(K.is.ios ? [{ 28 | title: 'Jade', 29 | window: { 30 | title: 'Jade Demo', 31 | children: [ 32 | K.jade('test.jade', { 33 | users: { 34 | jacob: 'yeah', 35 | david: 'what', 36 | conny: 'hi', 37 | aida: 'hello', 38 | calle: 'yup' 39 | } 40 | }) 41 | ] 42 | } 43 | }] : []) 44 | }).open(); 45 | -------------------------------------------------------------------------------- /Resources/kui/events.js: -------------------------------------------------------------------------------- 1 | exports.Class = Window.extend({ 2 | init: function(o){ 3 | 4 | 5 | var el = K.createLabel({ text: 'no', top: 10,height: 40, backgroundColor: '#ff0' }); 6 | var parent = K.create({ 7 | type: 'view', 8 | backgroundColor: '#f00', 9 | className: 'row', 10 | height: 100, 11 | children: [el] 12 | }); 13 | 14 | var onClick = function(){ alert('parentClick'); }; 15 | var stuff = K.create({ 16 | type: 'label', 17 | text: 'sibling', 18 | height: 40, 19 | bottom: 10, 20 | backgroundColor: '#0a0' 21 | }); 22 | 23 | K(el) 24 | .text('hey') 25 | .css({ color: '#f00' }) 26 | .bind('click', function(){ alert('meclick'); }) 27 | .parent('.row') 28 | .bind('click', onClick) 29 | .append(stuff); 30 | 31 | 32 | this.children = [parent]; 33 | 34 | this._super(o); 35 | } 36 | }); -------------------------------------------------------------------------------- /Resources/kranium/lib/kss/pulltorefreshtableview.kss: -------------------------------------------------------------------------------- 1 | .pullToRefreshHeader { 2 | background-color: #e2e7ed; 3 | width: 320; 4 | height: 60; 5 | } 6 | 7 | 8 | .pullToRefreshArrow { 9 | background-image: kranium/lib/kranium/images/pull-to-refresh-arrow.png; 10 | width: 23; 11 | height: 60; 12 | bottom: 10; 13 | left: 20; 14 | } 15 | 16 | .pullToRefreshBorder { 17 | background-color: #798eab; 18 | height: 1; 19 | bottom: 0; 20 | } 21 | 22 | .pullToRefreshStatusLabel { 23 | left: 55; 24 | width: 230; 25 | bottom: 31; 26 | height: auto; 27 | color: #576c89; 28 | text-align: center; 29 | font-size: 14; 30 | font-weight: bold; 31 | shadow-color: #fff; 32 | shadow-offset-x: 0; 33 | shadow-offset-y: 1; 34 | } 35 | 36 | .pullToRefreshLastUpdatedLabel { 37 | left: 55; 38 | width: 230; 39 | bottom: 12; 40 | height: auto; 41 | color: #576c89; 42 | text-align: center; 43 | font-size: 13; 44 | shadow-color: #fff; 45 | shadow-offset-x: 0; 46 | shadow-offset-y: 1; 47 | } 48 | 49 | .pullToRefreshActivityIndicator { 50 | left: 20; 51 | bottom: 13; 52 | width: 30; 53 | height: 30 54 | } 55 | -------------------------------------------------------------------------------- /Resources/kui/coffeedemo.coffee: -------------------------------------------------------------------------------- 1 | exports.Class = View.extend 2 | layout: "vertical", 3 | cubeRow: (num) -> title: "#{ num*num } is the cube of #{ num }", className: "cubeRow" 4 | init: -> 5 | me = @ 6 | list = [1, 2, 3] 7 | 8 | @children = [{ 9 | className: "demoitem" 10 | type: "label" 11 | text: @text + " comes around!" 12 | } 13 | { 14 | className: "demoitem swipeme" 15 | type: "label" 16 | text: "swipe me" 17 | events: 18 | swipe: (e) -> 19 | alert e 20 | app: 21 | pause: (e) -> 22 | @text = "Don't you dare pausing me!" 23 | } 24 | { 25 | className: "demoitem clickme" 26 | type: "button" 27 | title: "click me" 28 | click: (e) -> 29 | alert e 30 | } 31 | { 32 | className: "demoitem coffeetable" 33 | type: "tableview" 34 | data: [{ 35 | title: if new Date().getDay() is 5 then "Happy Friday!" else "Dullday" 36 | }, 37 | { 38 | title: if nope? then "w00t" else "Not defined" 39 | } 40 | { 41 | className: "leftRightRow" 42 | children: [{ 43 | className: "lefty" 44 | type: "label" 45 | text: "lefty" 46 | } 47 | { 48 | className: "righty" 49 | type: "label" 50 | text: "righty" 51 | }] 52 | }].concat(me.cubeRow num for num in list) 53 | }] 54 | @_super.apply(@, arguments) -------------------------------------------------------------------------------- /Resources/kui/backbonedemo.js: -------------------------------------------------------------------------------- 1 | // Define model 2 | RowModel = Backbone.Model.extend({ 3 | type: 'tableviewrow' 4 | }); 5 | 6 | // Define collection 7 | RowCollection = Backbone.Collection.extend({ 8 | model: RowModel, 9 | comparator: function(model) { 10 | return model.get("title"); 11 | } 12 | }); 13 | 14 | // Create todos collection 15 | todos = new RowCollection(); 16 | todos.add([ 17 | { title: "An example todo" }, 18 | { title: "Another example todo" }, 19 | ]); 20 | 21 | // Create todolist 22 | var todolist = K.create({ 23 | type: 'todolist', 24 | collection: todos 25 | }); 26 | 27 | exports.Class = Window.extend({ 28 | navBarHidden: true, 29 | init: function(o){ 30 | 31 | this.titleLabel = K.createLabel({ 32 | className: 'titleLabel' 33 | }); 34 | todos.bind('all', this.updateTitleLabel.bind(this)); 35 | this.updateTitleLabel(); 36 | 37 | this.children = [{ 38 | type: 'toolbar', 39 | className: 'todoToolbar', 40 | items: [{ 41 | type: 'textfield', 42 | className: 'todoInputTextField', 43 | events: { 44 | "return": function(e){ 45 | todos.add({ title: e.value }); 46 | } 47 | } 48 | }, 49 | 'spacer', 50 | this.titleLabel] 51 | }, todolist]; 52 | 53 | this._super(o); 54 | }, 55 | 56 | updateTitleLabel: function(){ 57 | var completed = todos.filter(function(m){ return m.get('hasCheck') }).length; 58 | this.titleLabel.text = completed + ' / ' + todos.length + ' todos'; 59 | } 60 | }); -------------------------------------------------------------------------------- /Resources/kui/shims.js: -------------------------------------------------------------------------------- 1 | exports.Class = Window.extend({ 2 | title: 'Shims', 3 | navBarHidden: false, 4 | init: function(o){ 5 | 6 | this.leftNavButton = { 7 | title: 'Lefty', 8 | click: function(e){ 9 | K.log(e); 10 | } 11 | }; 12 | 13 | this.rightNavButton = { 14 | title: 'Righty', 15 | click: function(e){ 16 | K.log(e); 17 | } 18 | }; 19 | 20 | this.children = [{ 21 | top: '40dp', 22 | type: 'tabbedbar', 23 | height: '40dp', 24 | backgroundColor: '#0a0', 25 | index: 0, 26 | labels: ['one', 'two', 'three'], 27 | click: function(e){ 28 | K.log(e); 29 | } 30 | }, 31 | 32 | { 33 | top: '120dp', 34 | type: 'buttonbar', 35 | height: '40dp', 36 | labels: ['one', 'two', 'three'], 37 | click: function(e){ 38 | K.log(e); 39 | } 40 | }, 41 | 42 | { 43 | top: '200dp', 44 | height: '44dp', 45 | type: 'toolbar', 46 | items: [ 47 | 'spacer', 48 | { 49 | type: 'button', 50 | title: 'a button' 51 | }, 52 | 'spacer', 53 | { 54 | type: 'label', 55 | text: 'hello' 56 | }, 57 | 'spacer' 58 | ] 59 | }, 60 | 61 | 62 | { 63 | top: '280dp', 64 | height: '44dp', 65 | type: 'toolbar', 66 | items: [ 67 | { 68 | 69 | type: 'tabbedbar', 70 | height: '44dp', 71 | width: '140dp', 72 | backgroundColor: '#0a0', 73 | index: 0, 74 | labels: ['one', 'two', 'three'], 75 | click: function(e){ 76 | K.log(e); 77 | } 78 | }, 79 | 'spacer', 80 | { 81 | type: 'buttonbar', 82 | height: '44dp', 83 | width: '140dp', 84 | labels: ['one', 'two', 'three'], 85 | click: function(e){ 86 | K.log(e); 87 | } 88 | }] 89 | }]; 90 | 91 | this._super(o); 92 | } 93 | }); -------------------------------------------------------------------------------- /tiapp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | true 7 | false 8 | true 9 | 10 | se.krawaller.kraniumdemo 11 | kraniumdemo 12 | 1.0 13 | not specified 14 | not specified 15 | not specified 16 | not specified 17 | appicon.png 18 | false 19 | false 20 | default 21 | false 22 | false 23 | false 24 | true 25 | ebf1bac7-f0db-42c4-8552-ed65c7e86206 26 | 27 | 28 | Ti.UI.PORTRAIT 29 | 30 | 31 | Ti.UI.PORTRAIT 32 | Ti.UI.UPSIDE_PORTRAIT 33 | Ti.UI.LANDSCAPE_LEFT 34 | Ti.UI.LANDSCAPE_RIGHT 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 32768 45 | 46 | -------------------------------------------------------------------------------- /Resources/kranium/lib/kss/androidshim.kss: -------------------------------------------------------------------------------- 1 | .navBar { 2 | top: 0; 3 | height: 44dp; 4 | color: #fff; 5 | } 6 | 7 | .navBarGradient { 8 | top: 0; 9 | height: 44dp; 10 | } 11 | 12 | .navBaredContent { 13 | top: 44dp; 14 | } 15 | 16 | .navBarLabel { 17 | height: 44dp; 18 | color: #fff; 19 | font-size: 15dp; 20 | font-weight: bold; 21 | } 22 | 23 | .navButton { 24 | height: 44dp; 25 | width: 80dp; 26 | background-image: false; 27 | color: #fff; 28 | font-size: 13dp; 29 | background-image: kranium/lib/images/android-navbar-button.png; 30 | background-selected-image: kranium/lib/images/android-navbar-button-pressed.png; 31 | } 32 | 33 | .navBarSeparator { 34 | top: 0; 35 | width: 2; 36 | height: 44dp; 37 | background-image: kranium/lib/images/android-navbar-separator.png; 38 | } 39 | 40 | .rightNavButton { 41 | right: 0; 42 | } 43 | 44 | .leftNavButton { 45 | left: 0; 46 | } 47 | 48 | .tabbedBarLabel { 49 | height: 44dp; 50 | text-align: center; 51 | color: #fff; 52 | } 53 | 54 | .buttonBarLabel { 55 | height: 44dp; 56 | text-align: center; 57 | color: #fff; 58 | } 59 | 60 | .toolbarItem { 61 | height: 44dp; 62 | text-align: center; 63 | color: #fff; 64 | } 65 | 66 | .tabButton { 67 | height: 50dp; 68 | bottom: 0; 69 | } 70 | 71 | .tabButtonLabel { 72 | color: #fff; 73 | font-size: 11dp; 74 | height: 20dp; 75 | text-align: center; 76 | } 77 | 78 | .tabButtonLabelWithIcon { 79 | top: 32dp; 80 | } 81 | 82 | .tabButtonLabelWithoutIcon { 83 | top: 16dp; 84 | } 85 | 86 | .tabButtonIcon { 87 | default-image: null; 88 | width: 32dp; 89 | height: 32dp; 90 | top: 2dp; 91 | } 92 | 93 | .tabButtonSeparator { 94 | top: 2; 95 | right: 0; 96 | width: 1; 97 | background-color: #aaa; 98 | } 99 | 100 | .tabButtonContainer { 101 | bottom: 0; 102 | height: 50dp; 103 | background-color: #aaa; 104 | } 105 | 106 | .tabGroupWindow { 107 | background-color: #000; 108 | } 109 | 110 | .tabButtonActive { background-image: kranium/lib/images/tab-active.png; } 111 | .tabButtonInactive { background-image: kranium/lib/images/tab-inactive.png; } 112 | .tabButtonPressed { background-image: kranium/lib/images/tab-pressed.png; } 113 | -------------------------------------------------------------------------------- /Resources/kui/coffeedemo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | exports.Class = View.extend({ 3 | layout: "vertical", 4 | cubeRow: function(num) { 5 | return { 6 | title: "" + (num * num) + " is the cube of " + num, 7 | className: "cubeRow" 8 | }; 9 | }, 10 | init: function() { 11 | var list, me, num; 12 | me = this; 13 | list = [1, 2, 3]; 14 | this.children = [ 15 | { 16 | className: "demoitem", 17 | type: "label", 18 | text: this.text + " comes around!" 19 | }, { 20 | className: "demoitem swipeme", 21 | type: "label", 22 | text: "swipe me", 23 | events: { 24 | swipe: function(e) { 25 | return alert(e); 26 | }, 27 | app: { 28 | pause: function(e) { 29 | return this.text = "Don't you dare pausing me!"; 30 | } 31 | } 32 | } 33 | }, { 34 | className: "demoitem clickme", 35 | type: "button", 36 | title: "click me", 37 | click: function(e) { 38 | return alert(e); 39 | } 40 | }, { 41 | className: "demoitem coffeetable", 42 | type: "tableview", 43 | data: [ 44 | { 45 | title: new Date().getDay() === 5 ? "Happy Friday!" : "Dullday" 46 | }, { 47 | title: typeof nope !== "undefined" && nope !== null ? "w00t" : "Not defined" 48 | }, { 49 | className: "leftRightRow", 50 | children: [ 51 | { 52 | className: "lefty", 53 | type: "label", 54 | text: "lefty" 55 | }, { 56 | className: "righty", 57 | type: "label", 58 | text: "righty" 59 | } 60 | ] 61 | } 62 | ].concat((function() { 63 | var _i, _len, _results; 64 | _results = []; 65 | for (_i = 0, _len = list.length; _i < _len; _i++) { 66 | num = list[_i]; 67 | _results.push(me.cubeRow(num)); 68 | } 69 | return _results; 70 | })()) 71 | } 72 | ]; 73 | return this._super.apply(this, arguments); 74 | } 75 | }); 76 | }).call(this); 77 | -------------------------------------------------------------------------------- /Resources/test/core.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | // To learn how to write Jasmine tests, please read Jasmine documentation: 4 | // https://github.com/pivotal/jasmine/wiki 5 | 6 | describe('Core', function() { 7 | var view = K.createView(), 8 | label = K.createLabel(), 9 | button = K.createButton(); 10 | 11 | describe('methods', function(){ 12 | it('stringify', function(){ 13 | expect( 14 | K([label, view]).stringify() 15 | ).toEqual('["", ""]'); 16 | }); 17 | 18 | it('get array of all elements', function(){ 19 | expect( 20 | K.stringify( 21 | K( 22 | [K.createLabel({ text: "get" }), view] 23 | ).get() 24 | ) 25 | ).toEqual('["", ""]'); 26 | }); 27 | 28 | it('get first element found', function(){ 29 | expect( 30 | K.stringify( 31 | K( 32 | [K.createLabel({ text: "get" }), view] 33 | ).get(0) 34 | ) 35 | ).toEqual('""'); 36 | }); 37 | 38 | it('size', function(){ 39 | expect( 40 | K([label, view, button]).size() 41 | ).toEqual(3); 42 | }); 43 | 44 | it('index(element)', function(){ 45 | var view = K.createView(); 46 | expect( 47 | K([label, view, button]).index(view) 48 | ).toEqual(1); 49 | }); 50 | 51 | it('first', function(){ 52 | expect( 53 | K([view, label, button]).first().stringify() 54 | ).toEqual('[""]'); 55 | }); 56 | 57 | it('last', function(){ 58 | expect( 59 | K([view, label, button]).last().stringify() 60 | ).toEqual('[""]'); 61 | }); 62 | 63 | it('add', function(){ 64 | expect( 65 | K([view, label]).add(button).stringify() 66 | ).toEqual('["", "", ""]'); 67 | }); 68 | 69 | it('find', function(){ 70 | 71 | expect( 72 | K({ 73 | type: 'view', 74 | children: [{ 75 | type: 'label', 76 | text: 'not' 77 | }, 78 | { 79 | type: 'view', 80 | children: [{ 81 | type: 'label', 82 | className: 'me', 83 | text: 'me' 84 | }] 85 | }] 86 | }).find('label.me').stringify() 87 | ).toEqual('[""]'); 88 | }); 89 | 90 | it('closest("selector")', function(){ 91 | expect( 92 | K({ 93 | type: 'view', 94 | className: 'view1', 95 | children: [{ 96 | type: 'label', 97 | text: 'not' 98 | }, 99 | { 100 | type: 'view', 101 | className: 'view2', 102 | children: [{ 103 | type: 'label', 104 | className: 'me', 105 | text: 'me' 106 | }] 107 | }] 108 | }).find('label.me').closest('view').stringify() 109 | ).toEqual('[""]'); 110 | }); 111 | }); 112 | }); 113 | 114 | })(); -------------------------------------------------------------------------------- /Resources/kranium/lib/test/jasmine-titanium-node.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | if (!jasmine) { 4 | throw new Exception("jasmine library does not exist in global namespace!"); 5 | } 6 | 7 | /*! 8 | * TitaniumReporterNode 9 | * Copyright (c) 2011 Jacob Waller 10 | * Based on TitaniumReporter, copyright (c) 2011 Guilherme Chapiewski 11 | * MIT Licensed 12 | * 13 | * jasmine.getEnv().addReporter(new jasmine.TitaniumReporter()); 14 | * jasmine.getEnv().execute(); 15 | */ 16 | var TitaniumNodeReporter = function() { 17 | this.failedCount = 0; 18 | this.successCount = 0; 19 | this.failures = []; 20 | }; 21 | 22 | var sys = { 23 | puts: function(msg){ socketwrite(msg, 'puts'); }, 24 | print: function(msg){ socketwrite(msg, 'print'); }, 25 | }; 26 | 27 | ansiColor = function(color) { 28 | var colors = { 29 | green: "32", 30 | red: "31", 31 | yellow: "33" 32 | }; 33 | 34 | if (color) { 35 | sys.print("\033[" + colors[color] + "m") 36 | } else { 37 | sys.print("\033[0m") 38 | } 39 | }; 40 | 41 | TitaniumNodeReporter.prototype = { 42 | reportRunnerResults: function(runner) { 43 | sys.puts("\n") 44 | for (var i = 0; i < this.failures.length; i++) { 45 | var failure = this.failures[i]; 46 | sys.puts("Failing test in '" + failure.getFullName() + "'") 47 | var items = failure.results().getItems(); 48 | for (var j = 0; j < items.length; j++) { 49 | var item = items[j]; 50 | if (item.passed()) continue; 51 | 52 | sys.puts(item.trace.message); 53 | } 54 | sys.puts("") 55 | } 56 | var endTime = (new Date()).getTime(); 57 | 58 | customsocketwrite({ 59 | action: 'notify', 60 | title: (runner.topLevelSuites()||[{ description: 'bah' }])[0].description, 61 | msg: 'Passed ' + this.successCount + "/" + (this.failedCount + this.successCount) 62 | }); 63 | 64 | sys.puts("Tests finished in " + (endTime - this.startTime) + "ms. " + this.successCount + " success, " + this.failedCount + " failed."); 65 | }, 66 | 67 | 68 | reportRunnerStarting: function(runner) { 69 | this.startTime = (new Date()).getTime(); 70 | sys.print('\x1b[1J\x1b[H'); 71 | sys.puts("Starting tests..."); 72 | }, 73 | 74 | reportSpecResults: function(spec) { 75 | var result = spec.results(); 76 | if (result.passed()) { 77 | this.successCount++; 78 | ansiColor("green"); 79 | sys.print("."); 80 | } else if (result.skipped) { 81 | ansiColor("yellow"); 82 | sys.print("*"); 83 | } else { 84 | ansiColor("red"); 85 | this.failedCount++; 86 | this.failures.push(spec); 87 | sys.print("F"); 88 | } 89 | ansiColor(null); 90 | }, 91 | 92 | reportSpecStarting: function(spec) { 93 | //this.log('[' + spec.suite.description + '] ' + spec.description + '... '); 94 | }, 95 | 96 | reportSuiteResults: function(suite) { 97 | /*var results = suite.results(); 98 | var xhr = Ti.Network.createHTTPClient(); 99 | xhr.open("GET", 'http://localhost:9000/?m=' + encodeURIComponent('Passed ' + results.passedCount + '/' + results.totalCount) + '&t=' + encodeURIComponent(suite.description||"")); 100 | xhr.send();*/ 101 | //this.log('[' + suite.description + '] ' + results.passedCount + ' of ' + results.totalCount + ' assertions passed.

'); 102 | }, 103 | 104 | log: function(str) { 105 | //this.updateTestResults(str); 106 | } 107 | }; 108 | 109 | // export public 110 | jasmine.TitaniumNodeReporter = TitaniumNodeReporter; 111 | })(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 Appcelerator, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | (or the full text of the license is below) 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | 18 | 19 | Apache License 20 | Version 2.0, January 2004 21 | http://www.apache.org/licenses/ 22 | 23 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 24 | 25 | 1. Definitions. 26 | 27 | "License" shall mean the terms and conditions for use, reproduction, 28 | and distribution as defined by Sections 1 through 9 of this document. 29 | 30 | "Licensor" shall mean the copyright owner or entity authorized by 31 | the copyright owner that is granting the License. 32 | 33 | "Legal Entity" shall mean the union of the acting entity and all 34 | other entities that control, are controlled by, or are under common 35 | control with that entity. For the purposes of this definition, 36 | "control" means (i) the power, direct or indirect, to cause the 37 | direction or management of such entity, whether by contract or 38 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 39 | outstanding shares, or (iii) beneficial ownership of such entity. 40 | 41 | "You" (or "Your") shall mean an individual or Legal Entity 42 | exercising permissions granted by this License. 43 | 44 | "Source" form shall mean the preferred form for making modifications, 45 | including but not limited to software source code, documentation 46 | source, and configuration files. 47 | 48 | "Object" form shall mean any form resulting from mechanical 49 | transformation or translation of a Source form, including but 50 | not limited to compiled object code, generated documentation, 51 | and conversions to other media types. 52 | 53 | "Work" shall mean the work of authorship, whether in Source or 54 | Object form, made available under the License, as indicated by a 55 | copyright notice that is included in or attached to the work 56 | (an example is provided in the Appendix below). 57 | 58 | "Derivative Works" shall mean any work, whether in Source or Object 59 | form, that is based on (or derived from) the Work and for which the 60 | editorial revisions, annotations, elaborations, or other modifications 61 | represent, as a whole, an original work of authorship. For the purposes 62 | of this License, Derivative Works shall not include works that remain 63 | separable from, or merely link (or bind by name) to the interfaces of, 64 | the Work and Derivative Works thereof. 65 | 66 | "Contribution" shall mean any work of authorship, including 67 | the original version of the Work and any modifications or additions 68 | to that Work or Derivative Works thereof, that is intentionally 69 | submitted to Licensor for inclusion in the Work by the copyright owner 70 | or by an individual or Legal Entity authorized to submit on behalf of 71 | the copyright owner. For the purposes of this definition, "submitted" 72 | means any form of electronic, verbal, or written communication sent 73 | to the Licensor or its representatives, including but not limited to 74 | communication on electronic mailing lists, source code control systems, 75 | and issue tracking systems that are managed by, or on behalf of, the 76 | Licensor for the purpose of discussing and improving the Work, but 77 | excluding communication that is conspicuously marked or otherwise 78 | designated in writing by the copyright owner as "Not a Contribution." 79 | 80 | "Contributor" shall mean Licensor and any individual or Legal Entity 81 | on behalf of whom a Contribution has been received by Licensor and 82 | subsequently incorporated within the Work. 83 | 84 | 2. Grant of Copyright License. Subject to the terms and conditions of 85 | this License, each Contributor hereby grants to You a perpetual, 86 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 87 | copyright license to reproduce, prepare Derivative Works of, 88 | publicly display, publicly perform, sublicense, and distribute the 89 | Work and such Derivative Works in Source or Object form. 90 | 91 | 3. Grant of Patent License. Subject to the terms and conditions of 92 | this License, each Contributor hereby grants to You a perpetual, 93 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 94 | (except as stated in this section) patent license to make, have made, 95 | use, offer to sell, sell, import, and otherwise transfer the Work, 96 | where such license applies only to those patent claims licensable 97 | by such Contributor that are necessarily infringed by their 98 | Contribution(s) alone or by combination of their Contribution(s) 99 | with the Work to which such Contribution(s) was submitted. If You 100 | institute patent litigation against any entity (including a 101 | cross-claim or counterclaim in a lawsuit) alleging that the Work 102 | or a Contribution incorporated within the Work constitutes direct 103 | or contributory patent infringement, then any patent licenses 104 | granted to You under this License for that Work shall terminate 105 | as of the date such litigation is filed. 106 | 107 | 4. Redistribution. You may reproduce and distribute copies of the 108 | Work or Derivative Works thereof in any medium, with or without 109 | modifications, and in Source or Object form, provided that You 110 | meet the following conditions: 111 | 112 | (a) You must give any other recipients of the Work or 113 | Derivative Works a copy of this License; and 114 | 115 | (b) You must cause any modified files to carry prominent notices 116 | stating that You changed the files; and 117 | 118 | (c) You must retain, in the Source form of any Derivative Works 119 | that You distribute, all copyright, patent, trademark, and 120 | attribution notices from the Source form of the Work, 121 | excluding those notices that do not pertain to any part of 122 | the Derivative Works; and 123 | 124 | (d) If the Work includes a "NOTICE" text file as part of its 125 | distribution, then any Derivative Works that You distribute must 126 | include a readable copy of the attribution notices contained 127 | within such NOTICE file, excluding those notices that do not 128 | pertain to any part of the Derivative Works, in at least one 129 | of the following places: within a NOTICE text file distributed 130 | as part of the Derivative Works; within the Source form or 131 | documentation, if provided along with the Derivative Works; or, 132 | within a display generated by the Derivative Works, if and 133 | wherever such third-party notices normally appear. The contents 134 | of the NOTICE file are for informational purposes only and 135 | do not modify the License. You may add Your own attribution 136 | notices within Derivative Works that You distribute, alongside 137 | or as an addendum to the NOTICE text from the Work, provided 138 | that such additional attribution notices cannot be construed 139 | as modifying the License. 140 | 141 | You may add Your own copyright statement to Your modifications and 142 | may provide additional or different license terms and conditions 143 | for use, reproduction, or distribution of Your modifications, or 144 | for any such Derivative Works as a whole, provided Your use, 145 | reproduction, and distribution of the Work otherwise complies with 146 | the conditions stated in this License. 147 | 148 | 5. Submission of Contributions. Unless You explicitly state otherwise, 149 | any Contribution intentionally submitted for inclusion in the Work 150 | by You to the Licensor shall be under the terms and conditions of 151 | this License, without any additional terms or conditions. 152 | Notwithstanding the above, nothing herein shall supersede or modify 153 | the terms of any separate license agreement you may have executed 154 | with Licensor regarding such Contributions. 155 | 156 | 6. Trademarks. This License does not grant permission to use the trade 157 | names, trademarks, service marks, or product names of the Licensor, 158 | except as required for reasonable and customary use in describing the 159 | origin of the Work and reproducing the content of the NOTICE file. 160 | 161 | 7. Disclaimer of Warranty. Unless required by applicable law or 162 | agreed to in writing, Licensor provides the Work (and each 163 | Contributor provides its Contributions) on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 165 | implied, including, without limitation, any warranties or conditions 166 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 167 | PARTICULAR PURPOSE. You are solely responsible for determining the 168 | appropriateness of using or redistributing the Work and assume any 169 | risks associated with Your exercise of permissions under this License. 170 | 171 | 8. Limitation of Liability. In no event and under no legal theory, 172 | whether in tort (including negligence), contract, or otherwise, 173 | unless required by applicable law (such as deliberate and grossly 174 | negligent acts) or agreed to in writing, shall any Contributor be 175 | liable to You for damages, including any direct, indirect, special, 176 | incidental, or consequential damages of any character arising as a 177 | result of this License or out of the use or inability to use the 178 | Work (including but not limited to damages for loss of goodwill, 179 | work stoppage, computer failure or malfunction, or any and all 180 | other commercial damages or losses), even if such Contributor 181 | has been advised of the possibility of such damages. 182 | 183 | 9. Accepting Warranty or Additional Liability. While redistributing 184 | the Work or Derivative Works thereof, You may choose to offer, 185 | and charge a fee for, acceptance of support, warranty, indemnity, 186 | or other liability obligations and/or rights consistent with this 187 | License. However, in accepting such obligations, You may act only 188 | on Your own behalf and on Your sole responsibility, not on behalf 189 | of any other Contributor, and only if You agree to indemnify, 190 | defend, and hold each Contributor harmless for any liability 191 | incurred by, or claims asserted against, such Contributor by reason 192 | of your accepting any such warranty or additional liability. 193 | 194 | END OF TERMS AND CONDITIONS 195 | 196 | APPENDIX: How to apply the Apache License to your work. 197 | 198 | To apply the Apache License to your work, attach the following 199 | boilerplate notice, with the fields enclosed by brackets "[]" 200 | replaced with your own identifying information. (Don't include 201 | the brackets!) The text should be enclosed in the appropriate 202 | comment syntax for the file format. We also recommend that a 203 | file or class name and description of purpose be included on the 204 | same "printed page" as the copyright notice for easier 205 | identification within third-party archives. 206 | 207 | Copyright [yyyy] [name of copyright owner] 208 | 209 | Licensed under the Apache License, Version 2.0 (the "License"); 210 | you may not use this file except in compliance with the License. 211 | You may obtain a copy of the License at 212 | 213 | http://www.apache.org/licenses/LICENSE-2.0 214 | 215 | Unless required by applicable law or agreed to in writing, software 216 | distributed under the License is distributed on an "AS IS" BASIS, 217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 218 | See the License for the specific language governing permissions and 219 | limitations under the License. -------------------------------------------------------------------------------- /Resources/test-backup/jquery-core.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | // To learn how to write Jasmine tests, please read Jasmine documentation: 4 | // https://github.com/pivotal/jasmine/wiki 5 | 6 | // ok 7 | //^\s*ok\(\s*([^,]+),\s*["]([^"]+)["]\s*\); 8 | //it("\2", function(){ expect(\1).toBeDefined(); }); 9 | 10 | // equals 11 | //^\s*equals\(\s*([^,]+),\s*([^,]+),\s*["]([^"]+)["]\s*\); 12 | //it("\3", function(){ expect(\1).toEqual(\2); }); 13 | 14 | // same 15 | //^\s*same\(\s*([^,]+),\s*([^,]+),\s*["]([^"]+)["]\s*\); 16 | //it("\3", function(){ expect(\1).toBeSameTiElementsAs(\2); }); 17 | 18 | beforeEach(function(){ 19 | //Ti.API.log('winchildren', win.children); 20 | 21 | this.addMatchers({ 22 | toBeSameTiElementsAs: function(els) { 23 | 24 | function getUUID(el){ 25 | return el._uuid; 26 | } 27 | 28 | var thisStr = (Array.isArray(this.actual) ? this.actual : [this.actual]).map(getUUID).join(', '), 29 | compStr = (Array.isArray(els) ? els : [els]).map(getUUID).join(', '); 30 | 31 | return thisStr === compStr; 32 | } 33 | }); 34 | 35 | var els = []; 36 | Object.keys(K.elsByName).forEach(function(key){ 37 | els.concat(K.elsByName[key]||[]); 38 | }); 39 | els.forEach(function(el){ 40 | el.getParent().remove(el); 41 | }); 42 | 43 | /*(win.children||[]).forEach(function(child){ 44 | win.remove(child); 45 | });*/ 46 | 47 | K.reset(); 48 | }); 49 | 50 | describe('jQuery-Core', function() { 51 | 52 | describe('Basic requirements', function(){ 53 | it('Array.push()', function(){ expect(Array.prototype.push).toBeDefined(); }); 54 | it('Function.apply()', function(){ expect(Function.prototype.apply).toBeDefined(); }); 55 | it('getElementById', function(){ expect(getElementById).toBeDefined(); }); 56 | it('getElementsByTagName', function(){ expect(getElementsByTagName).toBeDefined(); }); 57 | it('RegExp', function(){ expect(RegExp).toBeDefined(); }); 58 | it('K', function(){ expect(K).toBeDefined(); }); 59 | }); 60 | 61 | describe('K()', function(){ 62 | it("K() === K([])", function(){ expect(K().length).toEqual(0); }); 63 | it("K(undefined) === K([])", function(){ expect(K(undefined).length).toEqual(0); }); 64 | it("K(null) === K([])", function(){ expect(K(null).length).toEqual(0); }); 65 | it("K('') === K([])", function(){ expect(K("").length).toEqual(0); }); 66 | it("K('#') === K([])", function(){ expect(K("#").length).toEqual(0); }); 67 | }); 68 | 69 | describe('element connectors', function(){ 70 | it("Free KElement hasn't parentNode", function(){ expect(K({ type: 'label' }).get(0).getParent()).toBeNull(); }); 71 | it("Added KElement has parentNode", function(){ expect(K({ type: 'label' }).appendTo(win).get(0).getParent()._uuid).not.toBeUndefined(); }); 72 | it("Added KElement has correct parentNode", function(){ expect(K({ type: 'label' }).appendTo(win).get(0).getParent()._uuid).toEqual(win._uuid); }); 73 | 74 | }); 75 | 76 | var labelBlueprints = []; 77 | for(var i = 0; i < 6; i++){ labelBlueprints.push({ width: 50, height: 50, top: i*10, left: 10*i, opacity: 0.3, backgroundColor: '#f00', type: 'label', text: i, className: 'lengthTest', id: 'label' + i }); } 78 | 79 | describe("length", function () { 80 | it("Get Number of Elements Found", function () { 81 | K(labelBlueprints).appendTo(win); 82 | expect(K(".lengthTest", win).length).toEqual(6); 83 | }); 84 | }); 85 | 86 | describe("size()", function () { 87 | it("Get Number of Elements Found", function () { 88 | K(labelBlueprints).appendTo(win); 89 | expect(K(".lengthTest", win).size()).toEqual(6); 90 | }); 91 | }); 92 | 93 | var getElBlueprints = [ 94 | { type: 'view', id: 'getView', className: 'getEl', backgroundColor: '#0f0' }, 95 | { type: 'label', id: 'getLabel', text: 'get()', className: 'getEl' }, 96 | { type: 'imageview', id: 'getImageView', className: 'getEl', image: 'http://krawaller.se/logo_mini.png' } 97 | ]; 98 | 99 | describe("get()", function () { 100 | it("Get All Elements", function(){ 101 | var els = K(getElBlueprints).appendTo(win); 102 | expect(els.get()).toBeSameTiElementsAs(K.elsByClassName.getEl); 103 | }); 104 | }); 105 | 106 | describe("toArray()", function () { 107 | it("Convert K object to an Array", function(){ 108 | var els = K(getElBlueprints).appendTo(win); 109 | expect(els.toArray()).toBeSameTiElementsAs(K.elsByClassName.getEl); 110 | }); 111 | }); 112 | 113 | describe("get(Number)", function () { 114 | it("Get A Single Element", function () { 115 | K(labelBlueprints).appendTo(win); 116 | expect(K("label").get(0)).toBeSameTiElementsAs(getElementById("label0")); 117 | }); 118 | 119 | it("Try get with index larger elements count", function(){ 120 | expect(K('#label0').get(1)).toBeUndefined(); 121 | }); 122 | }); 123 | 124 | describe("get(-Number)", function () { 125 | it("Get a single element with negative index", function () { 126 | K(labelBlueprints).appendTo(win); 127 | var id = labelBlueprints[labelBlueprints.length - 1].id, 128 | el = getElementById(id); 129 | 130 | expect(K("label").get(-1)).toBeSameTiElementsAs(el); 131 | }); 132 | 133 | it("Try get with index negative index larger then elements count", function(){ 134 | expect(K('#label0').get(-2)).toBeUndefined(); 135 | }); 136 | }); 137 | 138 | describe("each(Function)", function () { 139 | it('Execute a function, Relative', function () { 140 | K(labelBlueprints).appendTo(win); 141 | 142 | var div = K("label"); 143 | div.each(function () { 144 | this.foo = 'zoo'; 145 | }); 146 | var pass = true; 147 | for (var i = 0; i < div.size(); i++) { 148 | if (div.get(i).foo != "zoo") pass = false; 149 | } 150 | expect(pass).toBeTruthy(); 151 | }); 152 | }); 153 | 154 | describe("slice()", function () { 155 | var $labels; 156 | beforeEach(function(){ 157 | $labels = K(labelBlueprints).appendTo(win); 158 | }); 159 | 160 | it("slice(1,2)", function(){ 161 | expect($labels.slice(1,2).get()).toBeSameTiElementsAs(q('label1')); 162 | }); 163 | it("slice(1)", function(){ 164 | expect($labels.slice(1).get()).toBeSameTiElementsAs(q('label1, label2, label3, label4, label5')); 165 | }); 166 | it("slice(0,3)", function(){ 167 | expect($labels.slice(0,3).get()).toBeSameTiElementsAs(q('label0, label1, label2')); 168 | }); 169 | 170 | it("slice(-1)", function () { 171 | var id = labelBlueprints[labelBlueprints.length - 1].id, 172 | el = q(id); 173 | 174 | expect($labels.slice(-1).get()).toBeSameTiElementsAs(el); 175 | }); 176 | it("eq(1)", function () { 177 | expect($labels.eq(1).get()).toBeSameTiElementsAs(q(labelBlueprints[1].id)); 178 | }); 179 | it("eq('2')", function () { 180 | expect($labels.eq('2').get()).toBeSameTiElementsAs(q(labelBlueprints[2].id)); 181 | }); 182 | it("eq(-1)", function () { 183 | expect($labels.eq(-1).get()).toBeSameTiElementsAs(q(labelBlueprints[labelBlueprints.length - 1].id)); 184 | }); 185 | }); 186 | 187 | describe("first()/last()", function () { 188 | var $labels, 189 | $none; 190 | beforeEach(function(){ 191 | $labels = K(labelBlueprints).appendTo(win); 192 | $none = K('none'); 193 | }); 194 | 195 | it("first()", function () { 196 | expect($labels.first().get()).toBeSameTiElementsAs(q("label0")); 197 | }); 198 | it("last()", function () { 199 | expect($labels.last().get()).toBeSameTiElementsAs(q("label5")); 200 | }); 201 | it("first() none", function () { 202 | expect($none.first().get()).toBeSameTiElementsAs([]); 203 | }); 204 | it("last() none", function () { 205 | expect($none.last().get()).toBeSameTiElementsAs([]); 206 | }); 207 | }); 208 | 209 | /*describe("map()", function () { 210 | 211 | same( 212 | K("#ap").map(function () { 213 | return K(this).find("a").get(); 214 | }).get(), q("google", "groups", "anchor1", "mark"), "Array Map"); 215 | 216 | same( 217 | K("#ap > a").map(function () { 218 | return this.parentNode; 219 | }).get(), q("ap", "ap", "ap"), "Single Map"); 220 | 221 | //for #2616 222 | var keys = K.map({ 223 | a: 1, 224 | b: 2 225 | }, function (v, k) { 226 | return k; 227 | }); 228 | it("Map the keys from a hash to an array", function () { 229 | expect(keys.join("")).toEqual("ab"); 230 | }); 231 | 232 | var values = K.map({ 233 | a: 1, 234 | b: 2 235 | }, function (v, k) { 236 | return v; 237 | }); 238 | it("Map the values from a hash to an array", function () { 239 | expect(values.join("")).toEqual("12"); 240 | }); 241 | 242 | // object with length prop 243 | var values = K.map({ 244 | a: 1, 245 | b: 2, 246 | length: 3 247 | }, function (v, k) { 248 | return v; 249 | }); 250 | it("Map the values from a hash with a length property to an array", function () { 251 | expect(values.join("")).toEqual("123"); 252 | }); 253 | 254 | var scripts = document.getElementsByTagName("script"); 255 | var mapped = K.map(scripts, function (v, k) { 256 | return v; 257 | }); 258 | it("Map an array(-like) to a hash", function () { 259 | expect(mapped.length).toEqual(scripts.length); 260 | }); 261 | 262 | var flat = K.map(Array(4), function (v, k) { 263 | return k % 2 ? k : [k, k, k]; //try mixing array and regular returns 264 | }); 265 | it("try the new flatten technique(#2616)", function () { 266 | expect(flat.join("")).toEqual("00012223"); 267 | }); 268 | });*/ 269 | 270 | 271 | 272 | /*describe("K.merge()", function () { 273 | 274 | var parse = K.merge; 275 | 276 | same(parse([], []), [], "Empty arrays"); 277 | 278 | same(parse([1], [2]), [1, 2], "Basic"); 279 | same(parse([1, 2], [3, 4]), [1, 2, 3, 4], "Basic"); 280 | 281 | same(parse([1, 2], []), [1, 2], "Second empty"); 282 | same(parse([], [1, 2]), [1, 2], "First empty"); 283 | 284 | // Fixed at [5998], #3641 285 | same(parse([-2, -1], [0, 1, 2]), [-2, -1, 0, 1, 2], "Second array including a zero (falsy)"); 286 | 287 | // After fixing #5527 288 | same(parse([], [null, undefined]), [null, undefined], "Second array including null and undefined values"); 289 | same(parse({ 290 | length: 0 291 | }, [1, 2]), { 292 | length: 2, 293 | 0: 1, 294 | 1: 2 295 | }, "First array like"); 296 | }); 297 | 298 | describe("K.extend(Object, Object)", function () { 299 | 300 | var settings = { 301 | xnumber1: 5, 302 | xnumber2: 7, 303 | xstring1: "peter", 304 | xstring2: "pan" 305 | }, 306 | options = { 307 | xnumber2: 1, 308 | xstring2: "x", 309 | xxx: "newstring" 310 | }, 311 | optionsCopy = { 312 | xnumber2: 1, 313 | xstring2: "x", 314 | xxx: "newstring" 315 | }, 316 | merged = { 317 | xnumber1: 5, 318 | xnumber2: 1, 319 | xstring1: "peter", 320 | xstring2: "x", 321 | xxx: "newstring" 322 | }, 323 | deep1 = { 324 | foo: { 325 | bar: true 326 | } 327 | }, 328 | deep1copy = { 329 | foo: { 330 | bar: true 331 | } 332 | }, 333 | deep2 = { 334 | foo: { 335 | baz: true 336 | }, 337 | foo2: document 338 | }, 339 | deep2copy = { 340 | foo: { 341 | baz: true 342 | }, 343 | foo2: document 344 | }, 345 | deepmerged = { 346 | foo: { 347 | bar: true, 348 | baz: true 349 | }, 350 | foo2: document 351 | }, 352 | arr = [1, 2, 3], 353 | nestedarray = { 354 | arr: arr 355 | }; 356 | 357 | K.extend(settings, options); 358 | it("Check if extended: settings must be extended", function () { 359 | expect(settings).toBe(merged); 360 | }); 361 | it("Check if not modified: options must not be modified", function () { 362 | expect(options).toBe(optionsCopy); 363 | }); 364 | 365 | K.extend(settings, null, options); 366 | it("Check if extended: settings must be extended", function () { 367 | expect(settings).toBe(merged); 368 | }); 369 | it("Check if not modified: options must not be modified", function () { 370 | expect(options).toBe(optionsCopy); 371 | }); 372 | 373 | K.extend(true, deep1, deep2); 374 | it("Check if foo: settings must be extended", function () { 375 | expect(deep1.foo).toBe(deepmerged.foo); 376 | }); 377 | it("Check if not deep2: options must not be modified", function () { 378 | expect(deep2.foo).toBe(deep2copy.foo); 379 | }); 380 | it("Make sure that a deep clone was not attempted on the document", function () { 381 | expect(deep1.foo2).toEqual(document); 382 | }); 383 | 384 | ok(K.extend(true, {}, nestedarray).arr !== arr, "Deep extend of object must clone child array"); 385 | 386 | // #5991 387 | ok(K.isArray(K.extend(true, { 388 | arr: {} 389 | }, nestedarray).arr), "Cloned array heve to be an Array"); 390 | ok(K.isPlainObject(K.extend(true, { 391 | arr: arr 392 | }, { 393 | arr: {} 394 | }).arr), "Cloned object heve to be an plain object"); 395 | 396 | var empty = {}; 397 | var optionsWithLength = { 398 | foo: { 399 | length: -1 400 | } 401 | }; 402 | K.extend(true, empty, optionsWithLength); 403 | it("The length property must copy correctly", function () { 404 | expect(empty.foo).toBe(optionsWithLength.foo); 405 | }); 406 | 407 | empty = {}; 408 | var optionsWithDate = { 409 | foo: { 410 | date: new Date 411 | } 412 | }; 413 | K.extend(true, empty, optionsWithDate); 414 | it("Dates copy correctly", function () { 415 | expect(empty.foo).toBe(optionsWithDate.foo); 416 | }); 417 | 418 | var myKlass = function () {}; 419 | var customObject = new myKlass(); 420 | var optionsWithCustomObject = { 421 | foo: { 422 | date: customObject 423 | } 424 | }; 425 | empty = {}; 426 | K.extend(true, empty, optionsWithCustomObject); 427 | it("Custom objects copy correctly (no methods)", function () { 428 | expect(empty.foo && empty.foo.date === customObject).toBeDefined(); 429 | }); 430 | 431 | // Makes the class a little more realistic 432 | myKlass.prototype = { 433 | someMethod: function () {} 434 | }; 435 | empty = {}; 436 | K.extend(true, empty, optionsWithCustomObject); 437 | it("Custom objects copy correctly", function () { 438 | expect(empty.foo && empty.foo.date === customObject).toBeDefined(); 439 | }); 440 | 441 | var ret = K.extend(true, { 442 | foo: 4 443 | }, { 444 | foo: new Number(5) 445 | }); 446 | it("Wrapped numbers copy correctly", function () { 447 | expect(ret.foo == 5).toBeDefined(); 448 | }); 449 | 450 | var nullUndef; 451 | nullUndef = K.extend({}, options, { 452 | xnumber2: null 453 | }); 454 | it("Check to make sure null values are copied", function () { 455 | expect(nullUndef.xnumber2 === null).toBeDefined(); 456 | }); 457 | 458 | nullUndef = K.extend({}, options, { 459 | xnumber2: undefined 460 | }); 461 | it("Check to make sure undefined values are not copied", function () { 462 | expect(nullUndef.xnumber2 === options.xnumber2).toBeDefined(); 463 | }); 464 | 465 | nullUndef = K.extend({}, options, { 466 | xnumber0: null 467 | }); 468 | it("Check to make sure null values are inserted", function () { 469 | expect(nullUndef.xnumber0 === null).toBeDefined(); 470 | }); 471 | 472 | var target = {}; 473 | var recursive = { 474 | foo: target, 475 | bar: 5 476 | }; 477 | K.extend(true, target, recursive); 478 | it("Check to make sure a recursive obj doesn't go never-ending loop by not copying it over", function () { 479 | expect(target).toBe({ 480 | bar: 5 481 | }); 482 | }); 483 | 484 | var ret = K.extend(true, { 485 | foo: [] 486 | }, { 487 | foo: [0] 488 | }); // 1907 489 | it("Check to make sure a value with coersion 'false' copies over when necessary to fix #1907", function () { 490 | expect(ret.foo.length).toEqual(1); 491 | }); 492 | 493 | var ret = K.extend(true, { 494 | foo: "1,2,3" 495 | }, { 496 | foo: [1, 2, 3] 497 | }); 498 | it("Check to make sure values equal with coersion (but not actually equal) overwrite correctly", function () { 499 | expect(typeof ret.foo != "string").toBeDefined(); 500 | }); 501 | 502 | var ret = K.extend(true, { 503 | foo: "bar" 504 | }, { 505 | foo: null 506 | }); 507 | it("Make sure a null value doesn't crash with deep extend, for #1908", function () { 508 | expect(typeof ret.foo !== 'undefined').toBeDefined(); 509 | }); 510 | 511 | var obj = { 512 | foo: null 513 | }; 514 | K.extend(true, obj, { 515 | foo: "notnull" 516 | }); 517 | it("Make sure a null value can be overwritten", function () { 518 | expect(obj.foo).toEqual("notnull"); 519 | }); 520 | 521 | function func() {} 522 | K.extend(func, { 523 | key: "value" 524 | }); 525 | it("Verify a function can be extended", function () { 526 | expect(func.key).toEqual("value"); 527 | }); 528 | 529 | var defaults = { 530 | xnumber1: 5, 531 | xnumber2: 7, 532 | xstring1: "peter", 533 | xstring2: "pan" 534 | }, 535 | defaultsCopy = { 536 | xnumber1: 5, 537 | xnumber2: 7, 538 | xstring1: "peter", 539 | xstring2: "pan" 540 | }, 541 | options1 = { 542 | xnumber2: 1, 543 | xstring2: "x" 544 | }, 545 | options1Copy = { 546 | xnumber2: 1, 547 | xstring2: "x" 548 | }, 549 | options2 = { 550 | xstring2: "xx", 551 | xxx: "newstringx" 552 | }, 553 | options2Copy = { 554 | xstring2: "xx", 555 | xxx: "newstringx" 556 | }, 557 | merged2 = { 558 | xnumber1: 5, 559 | xnumber2: 1, 560 | xstring1: "peter", 561 | xstring2: "xx", 562 | xxx: "newstringx" 563 | }; 564 | 565 | var settings = K.extend({}, defaults, options1, options2); 566 | it("Check if extended: settings must be extended", function () { 567 | expect(settings).toBe(merged2); 568 | }); 569 | it("Check if not modified: options1 must not be modified", function () { 570 | expect(defaults).toBe(defaultsCopy); 571 | }); 572 | it("Check if not modified: options1 must not be modified", function () { 573 | expect(options1).toBe(options1Copy); 574 | }); 575 | it("Check if not modified: options2 must not be modified", function () { 576 | expect(options2).toBe(options2Copy); 577 | }); 578 | }); 579 | 580 | describe("K.proxy", function () { 581 | 582 | var test = function () { 583 | equals(this, thisObject, "Make sure that scope is set properly."); 584 | }; 585 | var thisObject = { 586 | foo: "bar", 587 | method: test 588 | }; 589 | 590 | // Make sure normal works 591 | test.call(thisObject); 592 | 593 | // Basic scoping 594 | K.proxy(test, thisObject)(); 595 | 596 | // Make sure it doesn't freak out 597 | equals(K.proxy(null, thisObject), undefined, "Make sure no function was returned."); 598 | 599 | // Partial application 600 | var test2 = function (a) { 601 | equals(a, "pre-applied", "Ensure arguments can be pre-applied."); 602 | }; 603 | K.proxy(test2, null, "pre-applied")(); 604 | 605 | // Partial application w/ normal arguments 606 | var test3 = function (a, b) { 607 | equals(b, "normal", "Ensure arguments can be pre-applied and passed as usual."); 608 | }; 609 | K.proxy(test3, null, "pre-applied")("normal"); 610 | 611 | // Test old syntax 612 | var test4 = { 613 | meth: function (a) { 614 | equals(a, "boom", "Ensure old syntax works."); 615 | } 616 | }; 617 | K.proxy(test4, "meth")("boom"); 618 | }); 619 | 620 | 621 | it('get predefined label text', function() { 622 | expect(K({ type: 'label', text: 'labelText' }).text()).toBe('labelText'); 623 | }); 624 | 625 | it('set label text', function() { 626 | expect(K({ type: 'label' }).text('setLabelText').text()).toBe('setLabelText'); 627 | });*/ 628 | 629 | }); 630 | 631 | })(); -------------------------------------------------------------------------------- /Resources/jade.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | // CommonJS require() 4 | function require(p){var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path)));return mod.exports}require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p[0])return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i"))},visitBlockComment:function(comment){!comment.buffer||(0==comment.val.indexOf("if")?(this.buffer("")):(this.buffer("")))},visitCode:function(code,obj,tagName){valueAttrByTagName={label:"text",textfield:"value",button:"title",def:"text"},tagName?this.buf.push(", "+(valueAttrByTagName[tagName]||valueAttrByTagName.def)+": ("+code.val.trim()+")"):this.buf.push(code.val),code.block&&(code.buffer||this.buf.push("{"),this.visit(code.block,obj),code.buffer||this.buf.push("}"))},visitEach:function(each,obj){this.buf.push("each("+each.obj+", function("+each.val+", "+each.key+"){ "),this.visit(each.block,!0),this.buf.push(" });")},visitAttributes:function(attrs,obj){var classes=[],buf=[];attrs.forEach(function(attr){attr.name=="class"?classes.push(attr.val):buf.push(attr.name+": "+attr.val)}),classes.length&&(classes=classes.join(" + ' ' + "),buf.push("className: "+classes)),this.buf.push(", "+buf.join(", "))}};function escape(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(//g,">").replace(/"/g,""")}}),require.register("doctypes.js",function(module,exports,require){}),require.register("filters.js",function(module,exports,require){}),require.register("inline-tags.js",function(module,exports,require){}),require.register("jade.js",function(module,exports,require){var Parser=require("./parser"),Compiler=require("./compiler");exports.version="0.13.0";var cache=exports.cache={};exports.filters=require("./filters"),exports.utils=require("./utils"),exports.Compiler=Compiler,exports.Parser=Parser;function rethrow(err,str,filename,lineno){var context=3,lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}function parse(str,options){var filename=options.filename;try{var parser=new Parser(str,filename);options.debug&&parser.debug();var compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();options.debug&&console.log("\nCompiled Function:\n\n%s",js.replace(/^/gm," "));try{return"var __els = [];\n"+(options.self?"var self = locals || {}, __ = __ || locals.__;\n"+js:"with (locals || {}) {"+js+"}")+"return __els;"}catch(err){process.compile(js,filename||"Jade");return}}catch(err){rethrow(err,str,filename,parser.lexer.lineno)}}exports.compile=function(str,options){var options=options||{},input=JSON.stringify(str),filename=options.filename?JSON.stringify(options.filename):"undefined",fn=["var __ = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",parse(String(str),options||{}),"} catch (err) {"," rethrow(err, __.input, __.filename, __.lineno);","}"].join("\n");return new Function("locals",fn)}}),require.register("lexer.js",function(module,exports,require){var Lexer=module.exports=function Lexer(str){this.input=str.replace(/\r\n|\r/g,"\n"),this.deferredTokens=[],this.lastIndents=0,this.lineno=1,this.stash=[],this.indentStack=[],this.indentRe=null,this.pipeless=!1};Lexer.prototype={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input)){this.consume(captures[0].length);return this.tok(type,captures[1])}},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},indexOfDelimiters:function(start,end){var str=this.input,nstart=0,nend=0,pos=0;for(var i=0,len=str.length;iindents)this.stash.push(this.tok("outdent")),this.indentStack.shift();tok=this.stash.pop()}else indents&&indents!=this.indentStack[0]?(this.indentStack.unshift(indents),tok=this.tok("indent",indents)):tok=this.tok("newline");return tok}},pipelessText:function(){if(this.pipeless){if("\n"==this.input[0])return;var i=this.input.indexOf("\n");-1==i&&(i=this.input.length);var str=this.input.substr(0,i);this.consume(str.length);return this.tok("text",str)}},colon:function(){return this.scan(/^: */,":")},advance:function(){return this.stashed()||this.next()},next:function(){return this.deferred()||this.eos()||this.pipelessText()||this.doctype()||this.include()||this.mixin()||this.tag()||this.filter()||this.each()||this.code()||this.id()||this.className()||this.attrs()||this.indent()||this.comment()||this.colon()||this.text()}}}),require.register("nodes/block-comment.js",function(module,exports,require){}),require.register("nodes/block.js",function(module,exports,require){var Node=require("./node"),Block=module.exports=function Block(node){this.nodes=[],node&&this.push(node)};Block.prototype=new Node,Block.prototype.constructor=Block,Block.prototype.push=function(node){return this.nodes.push(node)},Block.prototype.unshift=function(node){return this.nodes.unshift(node)}}),require.register("nodes/code.js",function(module,exports,require){var Node=require("./node"),Code=module.exports=function Code(val,buffer,escape){this.val=val,this.buffer=buffer,this.escape=escape,/^ *else/.test(val)&&(this.instrumentLineNumber=!1)};Code.prototype=new Node,Code.prototype.constructor=Code}),require.register("nodes/comment.js",function(module,exports,require){var Node=require("./node"),Comment=module.exports=function Comment(val,buffer){this.val=val,this.buffer=buffer};Comment.prototype=new Node,Comment.prototype.constructor=Comment}),require.register("nodes/doctype.js",function(module,exports,require){}),require.register("nodes/each.js",function(module,exports,require){var Node=require("./node"),Each=module.exports=function Each(obj,val,key,block){this.obj=obj,this.val=val,this.key=key,this.block=block};Each.prototype=new Node,Each.prototype.constructor=Each}),require.register("nodes/filter.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Filter=module.exports=function Filter(name,block,attrs){this.name=name,this.block=block,this.attrs=attrs,this.isASTFilter=block instanceof Block};Filter.prototype=new Node,Filter.prototype.constructor=Filter}),require.register("nodes/index.js",function(module,exports,require){exports.Node=require("./node"),exports.Tag=require("./tag"),exports.Code=require("./code"),exports.Each=require("./each"),exports.Text=require("./text"),exports.Block=require("./block"),exports.Mixin=require("./mixin"),exports.Filter=require("./filter"),exports.Comment=require("./comment"),exports.BlockComment=require("./block-comment"),exports.Doctype=require("./doctype")}),require.register("nodes/mixin.js",function(module,exports,require){}),require.register("nodes/node.js",function(module,exports,require){var Node=module.exports=function(){}}),require.register("nodes/tag.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Tag=module.exports=function Tag(name,block){this.name=name,this.attrs=[],this.block=block||new Block};Tag.prototype=new Node,Tag.prototype.constructor=Tag,Tag.prototype.setAttribute=function(name,val){this.attrs.push({name:name,val:val});return this},Tag.prototype.removeAttribute=function(name){for(var i=0,len=this.attrs.length;i= result.computed && (result = {value : value, computed : computed}); 227 | }); 228 | return result.value; 229 | }; 230 | 231 | // Return the minimum element (or element-based computation). 232 | _.min = function(obj, iterator, context) { 233 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); 234 | var result = {computed : Infinity}; 235 | each(obj, function(value, index, list) { 236 | var computed = iterator ? iterator.call(context, value, index, list) : value; 237 | computed < result.computed && (result = {value : value, computed : computed}); 238 | }); 239 | return result.value; 240 | }; 241 | 242 | // Sort the object's values by a criterion produced by an iterator. 243 | _.sortBy = function(obj, iterator, context) { 244 | return _.pluck(_.map(obj, function(value, index, list) { 245 | return { 246 | value : value, 247 | criteria : iterator.call(context, value, index, list) 248 | }; 249 | }).sort(function(left, right) { 250 | var a = left.criteria, b = right.criteria; 251 | return a < b ? -1 : a > b ? 1 : 0; 252 | }), 'value'); 253 | }; 254 | 255 | // Groups the object's values by a criterion produced by an iterator 256 | _.groupBy = function(obj, iterator) { 257 | var result = {}; 258 | each(obj, function(value, index) { 259 | var key = iterator(value, index); 260 | (result[key] || (result[key] = [])).push(value); 261 | }); 262 | return result; 263 | }; 264 | 265 | // Use a comparator function to figure out at what index an object should 266 | // be inserted so as to maintain order. Uses binary search. 267 | _.sortedIndex = function(array, obj, iterator) { 268 | iterator || (iterator = _.identity); 269 | var low = 0, high = array.length; 270 | while (low < high) { 271 | var mid = (low + high) >> 1; 272 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 273 | } 274 | return low; 275 | }; 276 | 277 | // Safely convert anything iterable into a real, live array. 278 | _.toArray = function(iterable) { 279 | if (!iterable) return []; 280 | if (iterable.toArray) return iterable.toArray(); 281 | if (_.isArray(iterable)) return slice.call(iterable); 282 | if (_.isArguments(iterable)) return slice.call(iterable); 283 | return _.values(iterable); 284 | }; 285 | 286 | // Return the number of elements in an object. 287 | _.size = function(obj) { 288 | return _.toArray(obj).length; 289 | }; 290 | 291 | // Array Functions 292 | // --------------- 293 | 294 | // Get the first element of an array. Passing **n** will return the first N 295 | // values in the array. Aliased as `head`. The **guard** check allows it to work 296 | // with `_.map`. 297 | _.first = _.head = function(array, n, guard) { 298 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 299 | }; 300 | 301 | // Returns everything but the first entry of the array. Aliased as `tail`. 302 | // Especially useful on the arguments object. Passing an **index** will return 303 | // the rest of the values in the array from that index onward. The **guard** 304 | // check allows it to work with `_.map`. 305 | _.rest = _.tail = function(array, index, guard) { 306 | return slice.call(array, (index == null) || guard ? 1 : index); 307 | }; 308 | 309 | // Get the last element of an array. 310 | _.last = function(array) { 311 | return array[array.length - 1]; 312 | }; 313 | 314 | // Trim out all falsy values from an array. 315 | _.compact = function(array) { 316 | return _.filter(array, function(value){ return !!value; }); 317 | }; 318 | 319 | // Return a completely flattened version of an array. 320 | _.flatten = function(array) { 321 | return _.reduce(array, function(memo, value) { 322 | if (_.isArray(value)) return memo.concat(_.flatten(value)); 323 | memo[memo.length] = value; 324 | return memo; 325 | }, []); 326 | }; 327 | 328 | // Return a version of the array that does not contain the specified value(s). 329 | _.without = function(array) { 330 | return _.difference(array, slice.call(arguments, 1)); 331 | }; 332 | 333 | // Produce a duplicate-free version of the array. If the array has already 334 | // been sorted, you have the option of using a faster algorithm. 335 | // Aliased as `unique`. 336 | _.uniq = _.unique = function(array, isSorted) { 337 | return _.reduce(array, function(memo, el, i) { 338 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; 339 | return memo; 340 | }, []); 341 | }; 342 | 343 | // Produce an array that contains the union: each distinct element from all of 344 | // the passed-in arrays. 345 | _.union = function() { 346 | return _.uniq(_.flatten(arguments)); 347 | }; 348 | 349 | // Produce an array that contains every item shared between all the 350 | // passed-in arrays. (Aliased as "intersect" for back-compat.) 351 | _.intersection = _.intersect = function(array) { 352 | var rest = slice.call(arguments, 1); 353 | return _.filter(_.uniq(array), function(item) { 354 | return _.every(rest, function(other) { 355 | return _.indexOf(other, item) >= 0; 356 | }); 357 | }); 358 | }; 359 | 360 | // Take the difference between one array and another. 361 | // Only the elements present in just the first array will remain. 362 | _.difference = function(array, other) { 363 | return _.filter(array, function(value){ return !_.include(other, value); }); 364 | }; 365 | 366 | // Zip together multiple lists into a single array -- elements that share 367 | // an index go together. 368 | _.zip = function() { 369 | var args = slice.call(arguments); 370 | var length = _.max(_.pluck(args, 'length')); 371 | var results = new Array(length); 372 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 373 | return results; 374 | }; 375 | 376 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 377 | // we need this function. Return the position of the first occurrence of an 378 | // item in an array, or -1 if the item is not included in the array. 379 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 380 | // If the array is large and already in sort order, pass `true` 381 | // for **isSorted** to use binary search. 382 | _.indexOf = function(array, item, isSorted) { 383 | if (array == null) return -1; 384 | var i, l; 385 | if (isSorted) { 386 | i = _.sortedIndex(array, item); 387 | return array[i] === item ? i : -1; 388 | } 389 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 390 | for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; 391 | return -1; 392 | }; 393 | 394 | 395 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 396 | _.lastIndexOf = function(array, item) { 397 | if (array == null) return -1; 398 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 399 | var i = array.length; 400 | while (i--) if (array[i] === item) return i; 401 | return -1; 402 | }; 403 | 404 | // Generate an integer Array containing an arithmetic progression. A port of 405 | // the native Python `range()` function. See 406 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 407 | _.range = function(start, stop, step) { 408 | if (arguments.length <= 1) { 409 | stop = start || 0; 410 | start = 0; 411 | } 412 | step = arguments[2] || 1; 413 | 414 | var len = Math.max(Math.ceil((stop - start) / step), 0); 415 | var idx = 0; 416 | var range = new Array(len); 417 | 418 | while(idx < len) { 419 | range[idx++] = start; 420 | start += step; 421 | } 422 | 423 | return range; 424 | }; 425 | 426 | // Function (ahem) Functions 427 | // ------------------ 428 | 429 | // Create a function bound to a given object (assigning `this`, and arguments, 430 | // optionally). Binding with arguments is also known as `curry`. 431 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 432 | // We check for `func.bind` first, to fail fast when `func` is undefined. 433 | _.bind = function(func, obj) { 434 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 435 | var args = slice.call(arguments, 2); 436 | return function() { 437 | return func.apply(obj, args.concat(slice.call(arguments))); 438 | }; 439 | }; 440 | 441 | // Bind all of an object's methods to that object. Useful for ensuring that 442 | // all callbacks defined on an object belong to it. 443 | _.bindAll = function(obj) { 444 | var funcs = slice.call(arguments, 1); 445 | if (funcs.length == 0) funcs = _.functions(obj); 446 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 447 | return obj; 448 | }; 449 | 450 | // Memoize an expensive function by storing its results. 451 | _.memoize = function(func, hasher) { 452 | var memo = {}; 453 | hasher || (hasher = _.identity); 454 | return function() { 455 | var key = hasher.apply(this, arguments); 456 | return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 457 | }; 458 | }; 459 | 460 | // Delays a function for the given number of milliseconds, and then calls 461 | // it with the arguments supplied. 462 | _.delay = function(func, wait) { 463 | var args = slice.call(arguments, 2); 464 | return setTimeout(function(){ return func.apply(func, args); }, wait); 465 | }; 466 | 467 | // Defers a function, scheduling it to run after the current call stack has 468 | // cleared. 469 | _.defer = function(func) { 470 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 471 | }; 472 | 473 | // Internal function used to implement `_.throttle` and `_.debounce`. 474 | var limit = function(func, wait, debounce) { 475 | var timeout; 476 | return function() { 477 | var context = this, args = arguments; 478 | var throttler = function() { 479 | timeout = null; 480 | func.apply(context, args); 481 | }; 482 | if (debounce) clearTimeout(timeout); 483 | if (debounce || !timeout) timeout = setTimeout(throttler, wait); 484 | }; 485 | }; 486 | 487 | // Returns a function, that, when invoked, will only be triggered at most once 488 | // during a given window of time. 489 | _.throttle = function(func, wait) { 490 | return limit(func, wait, false); 491 | }; 492 | 493 | // Returns a function, that, as long as it continues to be invoked, will not 494 | // be triggered. The function will be called after it stops being called for 495 | // N milliseconds. 496 | _.debounce = function(func, wait) { 497 | return limit(func, wait, true); 498 | }; 499 | 500 | // Returns a function that will be executed at most one time, no matter how 501 | // often you call it. Useful for lazy initialization. 502 | _.once = function(func) { 503 | var ran = false, memo; 504 | return function() { 505 | if (ran) return memo; 506 | ran = true; 507 | return memo = func.apply(this, arguments); 508 | }; 509 | }; 510 | 511 | // Returns the first function passed as an argument to the second, 512 | // allowing you to adjust arguments, run code before and after, and 513 | // conditionally execute the original function. 514 | _.wrap = function(func, wrapper) { 515 | return function() { 516 | var args = [func].concat(slice.call(arguments)); 517 | return wrapper.apply(this, args); 518 | }; 519 | }; 520 | 521 | // Returns a function that is the composition of a list of functions, each 522 | // consuming the return value of the function that follows. 523 | _.compose = function() { 524 | var funcs = slice.call(arguments); 525 | return function() { 526 | var args = slice.call(arguments); 527 | for (var i = funcs.length - 1; i >= 0; i--) { 528 | args = [funcs[i].apply(this, args)]; 529 | } 530 | return args[0]; 531 | }; 532 | }; 533 | 534 | // Returns a function that will only be executed after being called N times. 535 | _.after = function(times, func) { 536 | return function() { 537 | if (--times < 1) { return func.apply(this, arguments); } 538 | }; 539 | }; 540 | 541 | 542 | // Object Functions 543 | // ---------------- 544 | 545 | // Retrieve the names of an object's properties. 546 | // Delegates to **ECMAScript 5**'s native `Object.keys` 547 | _.keys = nativeKeys || function(obj) { 548 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 549 | var keys = []; 550 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; 551 | return keys; 552 | }; 553 | 554 | // Retrieve the values of an object's properties. 555 | _.values = function(obj) { 556 | return _.map(obj, _.identity); 557 | }; 558 | 559 | // Return a sorted list of the function names available on the object. 560 | // Aliased as `methods` 561 | _.functions = _.methods = function(obj) { 562 | var names = []; 563 | for (var key in obj) { 564 | if (_.isFunction(obj[key])) names.push(key); 565 | } 566 | return names.sort(); 567 | }; 568 | 569 | // Extend a given object with all the properties in passed-in object(s). 570 | _.extend = function(obj) { 571 | each(slice.call(arguments, 1), function(source) { 572 | for (var prop in source) { 573 | if (source[prop] !== void 0) obj[prop] = source[prop]; 574 | } 575 | }); 576 | return obj; 577 | }; 578 | 579 | // Fill in a given object with default properties. 580 | _.defaults = function(obj) { 581 | each(slice.call(arguments, 1), function(source) { 582 | for (var prop in source) { 583 | if (obj[prop] == null) obj[prop] = source[prop]; 584 | } 585 | }); 586 | return obj; 587 | }; 588 | 589 | // Create a (shallow-cloned) duplicate of an object. 590 | _.clone = function(obj) { 591 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 592 | }; 593 | 594 | // Invokes interceptor with the obj, and then returns obj. 595 | // The primary purpose of this method is to "tap into" a method chain, in 596 | // order to perform operations on intermediate results within the chain. 597 | _.tap = function(obj, interceptor) { 598 | interceptor(obj); 599 | return obj; 600 | }; 601 | 602 | // Perform a deep comparison to check if two objects are equal. 603 | _.isEqual = function(a, b) { 604 | // Check object identity. 605 | if (a === b) return true; 606 | // Different types? 607 | var atype = typeof(a), btype = typeof(b); 608 | if (atype != btype) return false; 609 | // Basic equality test (watch out for coercions). 610 | if (a == b) return true; 611 | // One is falsy and the other truthy. 612 | if ((!a && b) || (a && !b)) return false; 613 | // Unwrap any wrapped objects. 614 | if (a._chain) a = a._wrapped; 615 | if (b._chain) b = b._wrapped; 616 | // One of them implements an isEqual()? 617 | if (a.isEqual) return a.isEqual(b); 618 | if (b.isEqual) return b.isEqual(a); 619 | // Check dates' integer values. 620 | if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); 621 | // Both are NaN? 622 | if (_.isNaN(a) && _.isNaN(b)) return false; 623 | // Compare regular expressions. 624 | if (_.isRegExp(a) && _.isRegExp(b)) 625 | return a.source === b.source && 626 | a.global === b.global && 627 | a.ignoreCase === b.ignoreCase && 628 | a.multiline === b.multiline; 629 | // If a is not an object by this point, we can't handle it. 630 | if (atype !== 'object') return false; 631 | // Check for different array lengths before comparing contents. 632 | if (a.length && (a.length !== b.length)) return false; 633 | // Nothing else worked, deep compare the contents. 634 | var aKeys = _.keys(a), bKeys = _.keys(b); 635 | // Different object sizes? 636 | if (aKeys.length != bKeys.length) return false; 637 | // Recursive comparison of contents. 638 | for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; 639 | return true; 640 | }; 641 | 642 | // Is a given array or object empty? 643 | _.isEmpty = function(obj) { 644 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 645 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; 646 | return true; 647 | }; 648 | 649 | // Is a given value a DOM element? 650 | _.isElement = function(obj) { 651 | return !!(obj && obj.nodeType == 1); 652 | }; 653 | 654 | // Is a given value an array? 655 | // Delegates to ECMA5's native Array.isArray 656 | _.isArray = nativeIsArray || function(obj) { 657 | return toString.call(obj) === '[object Array]'; 658 | }; 659 | 660 | // Is a given variable an object? 661 | _.isObject = function(obj) { 662 | return obj === Object(obj); 663 | }; 664 | 665 | // Is a given variable an arguments object? 666 | _.isArguments = function(obj) { 667 | return !!(obj && hasOwnProperty.call(obj, 'callee')); 668 | }; 669 | 670 | // Is a given value a function? 671 | _.isFunction = function(obj) { 672 | return !!(obj && obj.constructor && obj.call && obj.apply); 673 | }; 674 | 675 | // Is a given value a string? 676 | _.isString = function(obj) { 677 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); 678 | }; 679 | 680 | // Is a given value a number? 681 | _.isNumber = function(obj) { 682 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); 683 | }; 684 | 685 | // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript 686 | // that does not equal itself. 687 | _.isNaN = function(obj) { 688 | return obj !== obj; 689 | }; 690 | 691 | // Is a given value a boolean? 692 | _.isBoolean = function(obj) { 693 | return obj === true || obj === false; 694 | }; 695 | 696 | // Is a given value a date? 697 | _.isDate = function(obj) { 698 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); 699 | }; 700 | 701 | // Is the given value a regular expression? 702 | _.isRegExp = function(obj) { 703 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); 704 | }; 705 | 706 | // Is a given value equal to null? 707 | _.isNull = function(obj) { 708 | return obj === null; 709 | }; 710 | 711 | // Is a given variable undefined? 712 | _.isUndefined = function(obj) { 713 | return obj === void 0; 714 | }; 715 | 716 | // Utility Functions 717 | // ----------------- 718 | 719 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 720 | // previous owner. Returns a reference to the Underscore object. 721 | _.noConflict = function() { 722 | root._ = previousUnderscore; 723 | return this; 724 | }; 725 | 726 | // Keep the identity function around for default iterators. 727 | _.identity = function(value) { 728 | return value; 729 | }; 730 | 731 | // Run a function **n** times. 732 | _.times = function (n, iterator, context) { 733 | for (var i = 0; i < n; i++) iterator.call(context, i); 734 | }; 735 | 736 | // Add your own custom functions to the Underscore object, ensuring that 737 | // they're correctly added to the OOP wrapper as well. 738 | _.mixin = function(obj) { 739 | each(_.functions(obj), function(name){ 740 | addToWrapper(name, _[name] = obj[name]); 741 | }); 742 | }; 743 | 744 | // Generate a unique integer id (unique within the entire client session). 745 | // Useful for temporary DOM ids. 746 | var idCounter = 0; 747 | _.uniqueId = function(prefix) { 748 | var id = idCounter++; 749 | return prefix ? prefix + id : id; 750 | }; 751 | 752 | // By default, Underscore uses ERB-style template delimiters, change the 753 | // following template settings to use alternative delimiters. 754 | _.templateSettings = { 755 | evaluate : /<%([\s\S]+?)%>/g, 756 | interpolate : /<%=([\s\S]+?)%>/g 757 | }; 758 | 759 | // JavaScript micro-templating, similar to John Resig's implementation. 760 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 761 | // and correctly escapes quotes within interpolated code. 762 | _.template = function(str, data) { 763 | var c = _.templateSettings; 764 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 765 | 'with(obj||{}){__p.push(\'' + 766 | str.replace(/\\/g, '\\\\') 767 | .replace(/'/g, "\\'") 768 | .replace(c.interpolate, function(match, code) { 769 | return "'," + code.replace(/\\'/g, "'") + ",'"; 770 | }) 771 | .replace(c.evaluate || null, function(match, code) { 772 | return "');" + code.replace(/\\'/g, "'") 773 | .replace(/[\r\n\t]/g, ' ') + "__p.push('"; 774 | }) 775 | .replace(/\r/g, '\\r') 776 | .replace(/\n/g, '\\n') 777 | .replace(/\t/g, '\\t') 778 | + "');}return __p.join('');"; 779 | var func = new Function('obj', tmpl); 780 | return data ? func(data) : func; 781 | }; 782 | 783 | // The OOP Wrapper 784 | // --------------- 785 | 786 | // If Underscore is called as a function, it returns a wrapped object that 787 | // can be used OO-style. This wrapper holds altered versions of all the 788 | // underscore functions. Wrapped objects may be chained. 789 | var wrapper = function(obj) { this._wrapped = obj; }; 790 | 791 | // Expose `wrapper.prototype` as `_.prototype` 792 | _.prototype = wrapper.prototype; 793 | 794 | // Helper function to continue chaining intermediate results. 795 | var result = function(obj, chain) { 796 | return chain ? _(obj).chain() : obj; 797 | }; 798 | 799 | // A method to easily add functions to the OOP wrapper. 800 | var addToWrapper = function(name, func) { 801 | wrapper.prototype[name] = function() { 802 | var args = slice.call(arguments); 803 | unshift.call(args, this._wrapped); 804 | return result(func.apply(_, args), this._chain); 805 | }; 806 | }; 807 | 808 | // Add all of the Underscore functions to the wrapper object. 809 | _.mixin(_); 810 | 811 | // Add all mutator Array functions to the wrapper. 812 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 813 | var method = ArrayProto[name]; 814 | wrapper.prototype[name] = function() { 815 | method.apply(this._wrapped, arguments); 816 | return result(this._wrapped, this._chain); 817 | }; 818 | }); 819 | 820 | // Add all accessor Array functions to the wrapper. 821 | each(['concat', 'join', 'slice'], function(name) { 822 | var method = ArrayProto[name]; 823 | wrapper.prototype[name] = function() { 824 | return result(method.apply(this._wrapped, arguments), this._chain); 825 | }; 826 | }); 827 | 828 | // Start chaining a wrapped Underscore object. 829 | wrapper.prototype.chain = function() { 830 | this._chain = true; 831 | return this; 832 | }; 833 | 834 | // Extracts the result from a wrapped and chained object. 835 | wrapper.prototype.value = function() { 836 | return this._wrapped; 837 | }; 838 | 839 | })(); -------------------------------------------------------------------------------- /Resources/kranium/lib/backbone/backbone.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.5.3 2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Backbone may be freely distributed under the MIT license. 4 | // For all details and documentation: 5 | // http://documentcloud.github.com/backbone 6 | 7 | (function(){ 8 | 9 | // Initial Setup 10 | // ------------- 11 | 12 | // Save a reference to the global object. 13 | var root = this; 14 | 15 | // Save the previous value of the `Backbone` variable. 16 | var previousBackbone = root.Backbone; 17 | 18 | // The top-level namespace. All public Backbone classes and modules will 19 | // be attached to this. Exported for both CommonJS and the browser. 20 | var Backbone; 21 | if (typeof exports !== 'undefined') { 22 | Backbone = exports; 23 | } else { 24 | Backbone = root.Backbone = {}; 25 | } 26 | 27 | // Current version of the library. Keep in sync with `package.json`. 28 | Backbone.VERSION = '0.5.3'; 29 | 30 | // Require Underscore, if we're on the server, and it's not already present. 31 | var _ = root._; 32 | if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._; 33 | 34 | // For Backbone's purposes, jQuery or Zepto owns the `$` variable. 35 | var $ = root.jQuery || root.Zepto; 36 | 37 | // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable 38 | // to its previous owner. Returns a reference to this Backbone object. 39 | Backbone.noConflict = function() { 40 | root.Backbone = previousBackbone; 41 | return this; 42 | }; 43 | 44 | // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will 45 | // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a 46 | // `X-Http-Method-Override` header. 47 | Backbone.emulateHTTP = false; 48 | 49 | // Turn on `emulateJSON` to support legacy servers that can't deal with direct 50 | // `application/json` requests ... will encode the body as 51 | // `application/x-www-form-urlencoded` instead and will send the model in a 52 | // form param named `model`. 53 | Backbone.emulateJSON = false; 54 | 55 | // Backbone.Events 56 | // ----------------- 57 | 58 | // A module that can be mixed in to *any object* in order to provide it with 59 | // custom events. You may `bind` or `unbind` a callback function to an event; 60 | // `trigger`-ing an event fires all callbacks in succession. 61 | // 62 | // var object = {}; 63 | // _.extend(object, Backbone.Events); 64 | // object.bind('expand', function(){ alert('expanded'); }); 65 | // object.trigger('expand'); 66 | // 67 | Backbone.Events = { 68 | 69 | // Bind an event, specified by a string name, `ev`, to a `callback` function. 70 | // Passing `"all"` will bind the callback to all events fired. 71 | bind : function(ev, callback, context) { 72 | var calls = this._callbacks || (this._callbacks = {}); 73 | var list = calls[ev] || (calls[ev] = []); 74 | list.push([callback, context]); 75 | return this; 76 | }, 77 | 78 | // Remove one or many callbacks. If `callback` is null, removes all 79 | // callbacks for the event. If `ev` is null, removes all bound callbacks 80 | // for all events. 81 | unbind : function(ev, callback) { 82 | var calls; 83 | if (!ev) { 84 | this._callbacks = {}; 85 | } else if (calls = this._callbacks) { 86 | if (!callback) { 87 | calls[ev] = []; 88 | } else { 89 | var list = calls[ev]; 90 | if (!list) return this; 91 | for (var i = 0, l = list.length; i < l; i++) { 92 | if (list[i] && callback === list[i][0]) { 93 | list[i] = null; 94 | break; 95 | } 96 | } 97 | } 98 | } 99 | return this; 100 | }, 101 | 102 | // Trigger an event, firing all bound callbacks. Callbacks are passed the 103 | // same arguments as `trigger` is, apart from the event name. 104 | // Listening for `"all"` passes the true event name as the first argument. 105 | trigger : function(eventName) { 106 | var list, calls, ev, callback, args; 107 | var both = 2; 108 | if (!(calls = this._callbacks)) return this; 109 | while (both--) { 110 | ev = both ? eventName : 'all'; 111 | if (list = calls[ev]) { 112 | for (var i = 0, l = list.length; i < l; i++) { 113 | if (!(callback = list[i])) { 114 | list.splice(i, 1); i--; l--; 115 | } else { 116 | args = both ? Array.prototype.slice.call(arguments, 1) : arguments; 117 | callback[0].apply(callback[1] || this, args); 118 | } 119 | } 120 | } 121 | } 122 | return this; 123 | } 124 | 125 | }; 126 | 127 | // Backbone.Model 128 | // -------------- 129 | 130 | // Create a new model, with defined attributes. A client id (`cid`) 131 | // is automatically generated and assigned for you. 132 | Backbone.Model = function(attributes, options) { 133 | var defaults; 134 | attributes || (attributes = {}); 135 | if (defaults = this.defaults) { 136 | if (_.isFunction(defaults)) defaults = defaults.call(this); 137 | attributes = _.extend({}, defaults, attributes); 138 | } 139 | this.attributes = {}; 140 | this._escapedAttributes = {}; 141 | this.cid = _.uniqueId('c'); 142 | this.set(attributes, {silent : true}); 143 | this._changed = false; 144 | this._previousAttributes = _.clone(this.attributes); 145 | if (options && options.collection) this.collection = options.collection; 146 | this.initialize(attributes, options); 147 | }; 148 | 149 | // Attach all inheritable methods to the Model prototype. 150 | _.extend(Backbone.Model.prototype, Backbone.Events, { 151 | 152 | // A snapshot of the model's previous attributes, taken immediately 153 | // after the last `"change"` event was fired. 154 | _previousAttributes : null, 155 | 156 | // Has the item been changed since the last `"change"` event? 157 | _changed : false, 158 | 159 | // The default name for the JSON `id` attribute is `"id"`. MongoDB and 160 | // CouchDB users may want to set this to `"_id"`. 161 | idAttribute : 'id', 162 | 163 | // Initialize is an empty function by default. Override it with your own 164 | // initialization logic. 165 | initialize : function(){}, 166 | 167 | // Return a copy of the model's `attributes` object. 168 | toJSON : function() { 169 | return _.clone(this.attributes); 170 | }, 171 | 172 | // Get the value of an attribute. 173 | get : function(attr) { 174 | return this.attributes[attr]; 175 | }, 176 | 177 | // Get the HTML-escaped value of an attribute. 178 | escape : function(attr) { 179 | var html; 180 | if (html = this._escapedAttributes[attr]) return html; 181 | var val = this.attributes[attr]; 182 | return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val); 183 | }, 184 | 185 | // Returns `true` if the attribute contains a value that is not null 186 | // or undefined. 187 | has : function(attr) { 188 | return this.attributes[attr] != null; 189 | }, 190 | 191 | // Set a hash of model attributes on the object, firing `"change"` unless you 192 | // choose to silence it. 193 | set : function(attrs, options) { 194 | 195 | // Extract attributes and options. 196 | options || (options = {}); 197 | if (!attrs) return this; 198 | if (attrs.attributes) attrs = attrs.attributes; 199 | var now = this.attributes, escaped = this._escapedAttributes; 200 | 201 | // Run validation. 202 | if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false; 203 | 204 | // Check for changes of `id`. 205 | if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; 206 | 207 | // We're about to start triggering change events. 208 | var alreadyChanging = this._changing; 209 | this._changing = true; 210 | 211 | // Update attributes. 212 | for (var attr in attrs) { 213 | var val = attrs[attr]; 214 | if (!_.isEqual(now[attr], val)) { 215 | now[attr] = val; 216 | delete escaped[attr]; 217 | this._changed = true; 218 | if (!options.silent) this.trigger('change:' + attr, this, val, options); 219 | } 220 | } 221 | 222 | // Fire the `"change"` event, if the model has been changed. 223 | if (!alreadyChanging && !options.silent && this._changed) this.change(options); 224 | this._changing = false; 225 | return this; 226 | }, 227 | 228 | // Remove an attribute from the model, firing `"change"` unless you choose 229 | // to silence it. `unset` is a noop if the attribute doesn't exist. 230 | unset : function(attr, options) { 231 | if (!(attr in this.attributes)) return this; 232 | options || (options = {}); 233 | var value = this.attributes[attr]; 234 | 235 | // Run validation. 236 | var validObj = {}; 237 | validObj[attr] = void 0; 238 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; 239 | 240 | // Remove the attribute. 241 | delete this.attributes[attr]; 242 | delete this._escapedAttributes[attr]; 243 | if (attr == this.idAttribute) delete this.id; 244 | this._changed = true; 245 | if (!options.silent) { 246 | this.trigger('change:' + attr, this, void 0, options); 247 | this.change(options); 248 | } 249 | return this; 250 | }, 251 | 252 | // Clear all attributes on the model, firing `"change"` unless you choose 253 | // to silence it. 254 | clear : function(options) { 255 | options || (options = {}); 256 | var attr; 257 | var old = this.attributes; 258 | 259 | // Run validation. 260 | var validObj = {}; 261 | for (attr in old) validObj[attr] = void 0; 262 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; 263 | 264 | this.attributes = {}; 265 | this._escapedAttributes = {}; 266 | this._changed = true; 267 | if (!options.silent) { 268 | for (attr in old) { 269 | this.trigger('change:' + attr, this, void 0, options); 270 | } 271 | this.change(options); 272 | } 273 | return this; 274 | }, 275 | 276 | // Fetch the model from the server. If the server's representation of the 277 | // model differs from its current attributes, they will be overriden, 278 | // triggering a `"change"` event. 279 | fetch : function(options) { 280 | options || (options = {}); 281 | var model = this; 282 | var success = options.success; 283 | options.success = function(resp, status, xhr) { 284 | if (!model.set(model.parse(resp, xhr), options)) return false; 285 | if (success) success(model, resp); 286 | }; 287 | options.error = wrapError(options.error, model, options); 288 | return (this.sync || Backbone.sync).call(this, 'read', this, options); 289 | }, 290 | 291 | // Set a hash of model attributes, and sync the model to the server. 292 | // If the server returns an attributes hash that differs, the model's 293 | // state will be `set` again. 294 | save : function(attrs, options) { 295 | options || (options = {}); 296 | if (attrs && !this.set(attrs, options)) return false; 297 | var model = this; 298 | var success = options.success; 299 | options.success = function(resp, status, xhr) { 300 | if (!model.set(model.parse(resp, xhr), options)) return false; 301 | if (success) success(model, resp, xhr); 302 | }; 303 | options.error = wrapError(options.error, model, options); 304 | var method = this.isNew() ? 'create' : 'update'; 305 | return (this.sync || Backbone.sync).call(this, method, this, options); 306 | }, 307 | 308 | // Destroy this model on the server if it was already persisted. Upon success, the model is removed 309 | // from its collection, if it has one. 310 | destroy : function(options) { 311 | options || (options = {}); 312 | if (this.isNew()) return this.trigger('destroy', this, this.collection, options); 313 | var model = this; 314 | var success = options.success; 315 | options.success = function(resp) { 316 | model.trigger('destroy', model, model.collection, options); 317 | if (success) success(model, resp); 318 | }; 319 | options.error = wrapError(options.error, model, options); 320 | return (this.sync || Backbone.sync).call(this, 'delete', this, options); 321 | }, 322 | 323 | // Default URL for the model's representation on the server -- if you're 324 | // using Backbone's restful methods, override this to change the endpoint 325 | // that will be called. 326 | url : function() { 327 | var base = getUrl(this.collection) || this.urlRoot || urlError(); 328 | if (this.isNew()) return base; 329 | return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); 330 | }, 331 | 332 | // **parse** converts a response into the hash of attributes to be `set` on 333 | // the model. The default implementation is just to pass the response along. 334 | parse : function(resp, xhr) { 335 | return resp; 336 | }, 337 | 338 | // Create a new model with identical attributes to this one. 339 | clone : function() { 340 | return new this.constructor(this); 341 | }, 342 | 343 | // A model is new if it has never been saved to the server, and lacks an id. 344 | isNew : function() { 345 | return this.id == null; 346 | }, 347 | 348 | // Call this method to manually fire a `change` event for this model. 349 | // Calling this will cause all objects observing the model to update. 350 | change : function(options) { 351 | this.trigger('change', this, options); 352 | this._previousAttributes = _.clone(this.attributes); 353 | this._changed = false; 354 | }, 355 | 356 | // Determine if the model has changed since the last `"change"` event. 357 | // If you specify an attribute name, determine if that attribute has changed. 358 | hasChanged : function(attr) { 359 | if (attr) return this._previousAttributes[attr] != this.attributes[attr]; 360 | return this._changed; 361 | }, 362 | 363 | // Return an object containing all the attributes that have changed, or false 364 | // if there are no changed attributes. Useful for determining what parts of a 365 | // view need to be updated and/or what attributes need to be persisted to 366 | // the server. 367 | changedAttributes : function(now) { 368 | now || (now = this.attributes); 369 | var old = this._previousAttributes; 370 | var changed = false; 371 | for (var attr in now) { 372 | if (!_.isEqual(old[attr], now[attr])) { 373 | changed = changed || {}; 374 | changed[attr] = now[attr]; 375 | } 376 | } 377 | return changed; 378 | }, 379 | 380 | // Get the previous value of an attribute, recorded at the time the last 381 | // `"change"` event was fired. 382 | previous : function(attr) { 383 | if (!attr || !this._previousAttributes) return null; 384 | return this._previousAttributes[attr]; 385 | }, 386 | 387 | // Get all of the attributes of the model at the time of the previous 388 | // `"change"` event. 389 | previousAttributes : function() { 390 | return _.clone(this._previousAttributes); 391 | }, 392 | 393 | // Run validation against a set of incoming attributes, returning `true` 394 | // if all is well. If a specific `error` callback has been passed, 395 | // call that instead of firing the general `"error"` event. 396 | _performValidation : function(attrs, options) { 397 | var error = this.validate(attrs); 398 | if (error) { 399 | if (options.error) { 400 | options.error(this, error, options); 401 | } else { 402 | this.trigger('error', this, error, options); 403 | } 404 | return false; 405 | } 406 | return true; 407 | } 408 | 409 | }); 410 | 411 | // Backbone.Collection 412 | // ------------------- 413 | 414 | // Provides a standard collection class for our sets of models, ordered 415 | // or unordered. If a `comparator` is specified, the Collection will maintain 416 | // its models in sort order, as they're added and removed. 417 | Backbone.Collection = function(models, options) { 418 | options || (options = {}); 419 | if (options.comparator) this.comparator = options.comparator; 420 | _.bindAll(this, '_onModelEvent', '_removeReference'); 421 | this._reset(); 422 | if (models) this.reset(models, {silent: true}); 423 | this.initialize.apply(this, arguments); 424 | }; 425 | 426 | // Define the Collection's inheritable methods. 427 | _.extend(Backbone.Collection.prototype, Backbone.Events, { 428 | 429 | // The default model for a collection is just a **Backbone.Model**. 430 | // This should be overridden in most cases. 431 | model : Backbone.Model, 432 | 433 | // Initialize is an empty function by default. Override it with your own 434 | // initialization logic. 435 | initialize : function(){}, 436 | 437 | // The JSON representation of a Collection is an array of the 438 | // models' attributes. 439 | toJSON : function() { 440 | return this.map(function(model){ return model.toJSON(); }); 441 | }, 442 | 443 | // Add a model, or list of models to the set. Pass **silent** to avoid 444 | // firing the `added` event for every new model. 445 | add : function(models, options) { 446 | if (_.isArray(models)) { 447 | for (var i = 0, l = models.length; i < l; i++) { 448 | this._add(models[i], options); 449 | } 450 | } else { 451 | this._add(models, options); 452 | } 453 | return this; 454 | }, 455 | 456 | // Remove a model, or a list of models from the set. Pass silent to avoid 457 | // firing the `removed` event for every model removed. 458 | remove : function(models, options) { 459 | if (_.isArray(models)) { 460 | for (var i = 0, l = models.length; i < l; i++) { 461 | this._remove(models[i], options); 462 | } 463 | } else { 464 | this._remove(models, options); 465 | } 466 | return this; 467 | }, 468 | 469 | // Get a model from the set by id. 470 | get : function(id) { 471 | if (id == null) return null; 472 | return this._byId[id.id != null ? id.id : id]; 473 | }, 474 | 475 | // Get a model from the set by client id. 476 | getByCid : function(cid) { 477 | return cid && this._byCid[cid.cid || cid]; 478 | }, 479 | 480 | // Get the model at the given index. 481 | at: function(index) { 482 | return this.models[index]; 483 | }, 484 | 485 | // Force the collection to re-sort itself. You don't need to call this under normal 486 | // circumstances, as the set will maintain sort order as each item is added. 487 | sort : function(options) { 488 | options || (options = {}); 489 | if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); 490 | this.models = this.sortBy(this.comparator); 491 | if (!options.silent) this.trigger('reset', this, options); 492 | return this; 493 | }, 494 | 495 | // Pluck an attribute from each model in the collection. 496 | pluck : function(attr) { 497 | return _.map(this.models, function(model){ return model.get(attr); }); 498 | }, 499 | 500 | // When you have more items than you want to add or remove individually, 501 | // you can reset the entire set with a new list of models, without firing 502 | // any `added` or `removed` events. Fires `reset` when finished. 503 | reset : function(models, options) { 504 | models || (models = []); 505 | options || (options = {}); 506 | this.each(this._removeReference); 507 | this._reset(); 508 | this.add(models, {silent: true}); 509 | if (!options.silent) this.trigger('reset', this, options); 510 | return this; 511 | }, 512 | 513 | // Fetch the default set of models for this collection, resetting the 514 | // collection when they arrive. If `add: true` is passed, appends the 515 | // models to the collection instead of resetting. 516 | fetch : function(options) { 517 | options || (options = {}); 518 | var collection = this; 519 | var success = options.success; 520 | options.success = function(resp, status, xhr) { 521 | collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); 522 | if (success) success(collection, resp); 523 | }; 524 | options.error = wrapError(options.error, collection, options); 525 | return (this.sync || Backbone.sync).call(this, 'read', this, options); 526 | }, 527 | 528 | // Create a new instance of a model in this collection. After the model 529 | // has been created on the server, it will be added to the collection. 530 | // Returns the model, or 'false' if validation on a new model fails. 531 | create : function(model, options) { 532 | var coll = this; 533 | options || (options = {}); 534 | model = this._prepareModel(model, options); 535 | if (!model) return false; 536 | var success = options.success; 537 | options.success = function(nextModel, resp, xhr) { 538 | coll.add(nextModel, options); 539 | if (success) success(nextModel, resp, xhr); 540 | }; 541 | model.save(null, options); 542 | return model; 543 | }, 544 | 545 | // **parse** converts a response into a list of models to be added to the 546 | // collection. The default implementation is just to pass it through. 547 | parse : function(resp, xhr) { 548 | return resp; 549 | }, 550 | 551 | // Proxy to _'s chain. Can't be proxied the same way the rest of the 552 | // underscore methods are proxied because it relies on the underscore 553 | // constructor. 554 | chain: function () { 555 | return _(this.models).chain(); 556 | }, 557 | 558 | // Reset all internal state. Called when the collection is reset. 559 | _reset : function(options) { 560 | this.length = 0; 561 | this.models = []; 562 | this._byId = {}; 563 | this._byCid = {}; 564 | }, 565 | 566 | // Prepare a model to be added to this collection 567 | _prepareModel: function(model, options) { 568 | if (!(model instanceof Backbone.Model)) { 569 | var attrs = model; 570 | model = new this.model(attrs, {collection: this}); 571 | if (model.validate && !model._performValidation(attrs, options)) model = false; 572 | } else if (!model.collection) { 573 | model.collection = this; 574 | } 575 | return model; 576 | }, 577 | 578 | // Internal implementation of adding a single model to the set, updating 579 | // hash indexes for `id` and `cid` lookups. 580 | // Returns the model, or 'false' if validation on a new model fails. 581 | _add : function(model, options) { 582 | options || (options = {}); 583 | model = this._prepareModel(model, options); 584 | if (!model) return false; 585 | var already = this.getByCid(model); 586 | if (already) throw new Error(["Can't add the same model to a set twice", already.id]); 587 | this._byId[model.id] = model; 588 | this._byCid[model.cid] = model; 589 | var index = options.at != null ? options.at : 590 | this.comparator ? this.sortedIndex(model, this.comparator) : 591 | this.length; 592 | this.models.splice(index, 0, model); 593 | model.bind('all', this._onModelEvent); 594 | this.length++; 595 | if (!options.silent) model.trigger('add', model, this, options); 596 | return model; 597 | }, 598 | 599 | // Internal implementation of removing a single model from the set, updating 600 | // hash indexes for `id` and `cid` lookups. 601 | _remove : function(model, options) { 602 | options || (options = {}); 603 | model = this.getByCid(model) || this.get(model); 604 | if (!model) return null; 605 | delete this._byId[model.id]; 606 | delete this._byCid[model.cid]; 607 | this.models.splice(this.indexOf(model), 1); 608 | this.length--; 609 | if (!options.silent) model.trigger('remove', model, this, options); 610 | this._removeReference(model); 611 | return model; 612 | }, 613 | 614 | // Internal method to remove a model's ties to a collection. 615 | _removeReference : function(model) { 616 | if (this == model.collection) { 617 | delete model.collection; 618 | } 619 | model.unbind('all', this._onModelEvent); 620 | }, 621 | 622 | // Internal method called every time a model in the set fires an event. 623 | // Sets need to update their indexes when models change ids. All other 624 | // events simply proxy through. "add" and "remove" events that originate 625 | // in other collections are ignored. 626 | _onModelEvent : function(ev, model, collection, options) { 627 | if ((ev == 'add' || ev == 'remove') && collection != this) return; 628 | if (ev == 'destroy') { 629 | this._remove(model, options); 630 | } 631 | if (model && ev === 'change:' + model.idAttribute) { 632 | delete this._byId[model.previous(model.idAttribute)]; 633 | this._byId[model.id] = model; 634 | } 635 | this.trigger.apply(this, arguments); 636 | } 637 | 638 | }); 639 | 640 | // Underscore methods that we want to implement on the Collection. 641 | var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 642 | 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 643 | 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 644 | 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy']; 645 | 646 | // Mix in each Underscore method as a proxy to `Collection#models`. 647 | _.each(methods, function(method) { 648 | Backbone.Collection.prototype[method] = function() { 649 | return _[method].apply(_, [this.models].concat(_.toArray(arguments))); 650 | }; 651 | }); 652 | 653 | // Backbone.Router 654 | // ------------------- 655 | 656 | // Routers map faux-URLs to actions, and fire events when routes are 657 | // matched. Creating a new one sets its `routes` hash, if not set statically. 658 | Backbone.Router = function(options) { 659 | options || (options = {}); 660 | if (options.routes) this.routes = options.routes; 661 | this._bindRoutes(); 662 | this.initialize.apply(this, arguments); 663 | }; 664 | 665 | // Cached regular expressions for matching named param parts and splatted 666 | // parts of route strings. 667 | var namedParam = /:([\w\d]+)/g; 668 | var splatParam = /\*([\w\d]+)/g; 669 | var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; 670 | 671 | // Set up all inheritable **Backbone.Router** properties and methods. 672 | _.extend(Backbone.Router.prototype, Backbone.Events, { 673 | 674 | // Initialize is an empty function by default. Override it with your own 675 | // initialization logic. 676 | initialize : function(){}, 677 | 678 | // Manually bind a single named route to a callback. For example: 679 | // 680 | // this.route('search/:query/p:num', 'search', function(query, num) { 681 | // ... 682 | // }); 683 | // 684 | route : function(route, name, callback) { 685 | Backbone.history || (Backbone.history = new Backbone.History); 686 | if (!_.isRegExp(route)) route = this._routeToRegExp(route); 687 | Backbone.history.route(route, _.bind(function(fragment) { 688 | var args = this._extractParameters(route, fragment); 689 | callback.apply(this, args); 690 | this.trigger.apply(this, ['route:' + name].concat(args)); 691 | }, this)); 692 | }, 693 | 694 | // Simple proxy to `Backbone.history` to save a fragment into the history. 695 | navigate : function(fragment, triggerRoute) { 696 | Backbone.history.navigate(fragment, triggerRoute); 697 | }, 698 | 699 | // Bind all defined routes to `Backbone.history`. We have to reverse the 700 | // order of the routes here to support behavior where the most general 701 | // routes can be defined at the bottom of the route map. 702 | _bindRoutes : function() { 703 | if (!this.routes) return; 704 | var routes = []; 705 | for (var route in this.routes) { 706 | routes.unshift([route, this.routes[route]]); 707 | } 708 | for (var i = 0, l = routes.length; i < l; i++) { 709 | this.route(routes[i][0], routes[i][1], this[routes[i][1]]); 710 | } 711 | }, 712 | 713 | // Convert a route string into a regular expression, suitable for matching 714 | // against the current location hash. 715 | _routeToRegExp : function(route) { 716 | route = route.replace(escapeRegExp, "\\$&") 717 | .replace(namedParam, "([^\/]*)") 718 | .replace(splatParam, "(.*?)"); 719 | return new RegExp('^' + route + '$'); 720 | }, 721 | 722 | // Given a route, and a URL fragment that it matches, return the array of 723 | // extracted parameters. 724 | _extractParameters : function(route, fragment) { 725 | return route.exec(fragment).slice(1); 726 | } 727 | 728 | }); 729 | 730 | // Backbone.History 731 | // ---------------- 732 | 733 | // Handles cross-browser history management, based on URL fragments. If the 734 | // browser does not support `onhashchange`, falls back to polling. 735 | Backbone.History = function() { 736 | this.handlers = []; 737 | _.bindAll(this, 'checkUrl'); 738 | }; 739 | 740 | // Cached regex for cleaning hashes. 741 | var hashStrip = /^#*/; 742 | 743 | // Cached regex for detecting MSIE. 744 | var isExplorer = /msie [\w.]+/; 745 | 746 | // Has the history handling already been started? 747 | var historyStarted = false; 748 | 749 | // Set up all inheritable **Backbone.History** properties and methods. 750 | _.extend(Backbone.History.prototype, { 751 | 752 | // The default interval to poll for hash changes, if necessary, is 753 | // twenty times a second. 754 | interval: 50, 755 | 756 | // Get the cross-browser normalized URL fragment, either from the URL, 757 | // the hash, or the override. 758 | getFragment : function(fragment, forcePushState) { 759 | if (fragment == null) { 760 | if (this._hasPushState || forcePushState) { 761 | fragment = window.location.pathname; 762 | var search = window.location.search; 763 | if (search) fragment += search; 764 | if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length); 765 | } else { 766 | fragment = window.location.hash; 767 | } 768 | } 769 | return decodeURIComponent(fragment.replace(hashStrip, '')); 770 | }, 771 | 772 | // Start the hash change handling, returning `true` if the current URL matches 773 | // an existing route, and `false` otherwise. 774 | start : function(options) { 775 | 776 | // Figure out the initial configuration. Do we need an iframe? 777 | // Is pushState desired ... is it available? 778 | if (historyStarted) throw new Error("Backbone.history has already been started"); 779 | this.options = _.extend({}, {root: '/'}, this.options, options); 780 | this._wantsPushState = !!this.options.pushState; 781 | this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); 782 | var fragment = this.getFragment(); 783 | var docMode = document.documentMode; 784 | var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); 785 | if (oldIE) { 786 | this.iframe = $('