├── .travis.yml ├── tests ├── utils │ └── common-setup.js ├── .eslintrc ├── api-test.js ├── event-target-test.js ├── dom-test.js ├── context-test.js ├── dom-event-delegate-test.js └── test-service-provider-test.js ├── examples └── todo │ ├── css │ └── app.css │ ├── bower_components │ ├── todomvc-common │ │ ├── bg.png │ │ ├── base.js │ │ └── base.css │ └── jquery-1.11.1.min │ │ └── .bower.json │ ├── js │ ├── app.js │ ├── modules │ │ ├── page.js │ │ ├── header.js │ │ ├── footer.js │ │ └── list.js │ ├── services │ │ ├── todos-db.js │ │ └── router.js │ └── behaviors │ │ └── todo.js │ └── index.html ├── lib ├── wrap-start.partial ├── box.js ├── .eslintrc ├── wrap-end.partial ├── dom-native.js ├── dom-jquery.js ├── event-target.js ├── context.js ├── application-stub.js ├── test-service-provider.js └── dom-event-delegate.js ├── .gitignore ├── config ├── copyright.txt ├── karma-conf.js └── testing-utils-karma-conf.js ├── package.json ├── .eslintrc ├── CHANGELOG.md ├── dist ├── t3.min.js ├── t3-native.min.js ├── t3-jquery.min.js ├── t3-native-2.7.0.min.js ├── t3-jquery-2.7.0.min.js ├── t3.min.js.map ├── t3-jquery.min.js.map └── t3-native.min.js.map ├── LICENSE ├── README.md └── Makefile.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | -------------------------------------------------------------------------------- /tests/utils/common-setup.js: -------------------------------------------------------------------------------- 1 | /* eslint no-undef: 0, no-unused-vars: 0 */ 2 | 3 | var assert = chai.assert; 4 | -------------------------------------------------------------------------------- /examples/todo/css/app.css: -------------------------------------------------------------------------------- 1 | #clear-completed { 2 | display: none; 3 | } 4 | .has-completed-tasks #clear-completed { 5 | display: block; 6 | } -------------------------------------------------------------------------------- /examples/todo/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box/t3js/master/examples/todo/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /lib/wrap-start.partial: -------------------------------------------------------------------------------- 1 | // Start wrapper 2 | // We use this to make sure we don't assign globals unless we actually want to 3 | (function(window) { 4 | -------------------------------------------------------------------------------- /examples/todo/js/app.js: -------------------------------------------------------------------------------- 1 | // Since T3 exports Box as a namespace, assign a shortcut Application to Box.Application 2 | var Application = Box.Application; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | node_modules 3 | doc 4 | *.swp 5 | *.iml 6 | .idea 7 | .DS_Store 8 | npm-debug.log 9 | dist/t3-[0-9]*.js 10 | dist/t3-testing-[0-9]*.js 11 | 12 | # Karama generated test coverage 13 | coverage-client 14 | /.dir-locals.el 15 | -------------------------------------------------------------------------------- /lib/box.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Base namespaces for Box JavaScript. 3 | * @author Box 4 | */ 5 | 6 | /* eslint-disable no-unused-vars */ 7 | 8 | /** 9 | * The one global object for Box JavaScript. 10 | * @namespace 11 | */ 12 | var Box = {}; 13 | /* eslint-enable no-unused-vars */ 14 | -------------------------------------------------------------------------------- /lib/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "brace-style": 2, 4 | "func-style": [2, "declaration"], 5 | "strict": [2, "function"], 6 | "guard-for-in": 2, 7 | "no-floating-decimal": 2, 8 | "no-underscore-dangle": 0, 9 | "no-nested-ternary": 2, 10 | "quotes": [2, "single"], 11 | "radix": 2, 12 | "wrap-iife": 2, 13 | "space-after-keywords": 2, 14 | "valid-jsdoc": [2, { 15 | "prefer": { 16 | "return": "returns" 17 | } 18 | }], 19 | // We allow unused args 20 | "no-unused-vars": [2, {"args": "none"}] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/todo/bower_components/jquery-1.11.1.min/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-1.11.1.min", 3 | "_cacheHeaders": { 4 | "ETag": "\"53628b86-1762a\"", 5 | "Last-Modified": "Thu, 01 May 2014 17:59:34 GMT", 6 | "Content-Length": "95786", 7 | "Content-Type": "application/x-javascript; charset=utf-8" 8 | }, 9 | "_release": "e-tag:53628b86-", 10 | "main": "index.js", 11 | "_source": "http://code.jquery.com/jquery-1.11.1.min.js", 12 | "_target": "*", 13 | "_originalSource": "http://code.jquery.com/jquery-1.11.1.min.js", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /config/copyright.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright 2016 Box, Inc. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /lib/wrap-end.partial: -------------------------------------------------------------------------------- 1 | if (typeof define === 'function' && define.amd) { 2 | // AMD 3 | define('t3', [], function() { 4 | return Box; 5 | }); 6 | } else if (typeof module === 'object' && typeof module.exports === 'object') { 7 | // CommonJS/npm, we want to export Box instead of assigning to global Window 8 | module.exports = Box; 9 | } else { 10 | // Make sure not to override Box namespace 11 | window.Box = window.Box || {}; 12 | 13 | // Copy all properties onto namespace (ES3 safe for loop) 14 | for (var key in Box) { 15 | if (Box.hasOwnProperty(key)) { 16 | window.Box[key] = Box[key]; 17 | } 18 | } 19 | } 20 | 21 | // Potentially window is not defined yet, so bind to 'this' instead 22 | }(typeof window !== 'undefined' ? window : this)); 23 | // End Wrapper 24 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "window": false, 7 | "document": false, 8 | "Box": false, 9 | "$": false, 10 | "mocha": false, 11 | "chai": false, 12 | "sinon": false, 13 | "assert": true, 14 | "leche": true 15 | }, 16 | "rules": { 17 | "brace-style": 2, 18 | "func-style": [2, "declaration"], 19 | "strict": [2, "function"], 20 | "guard-for-in": 2, 21 | "no-floating-decimal": 2, 22 | "no-underscore-dangle": 0, 23 | "no-nested-ternary": 2, 24 | "quotes": [2, "single"], 25 | "radix": 2, 26 | "wrap-iife": 2, 27 | "space-after-keywords": 2, 28 | "valid-jsdoc": [2, { 29 | "prefer": { 30 | "return": "returns" 31 | } 32 | }], 33 | // We allow unused args 34 | "no-unused-vars": [2, {"args": "none"}] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/todo/js/modules/page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Page Module 3 | * @author Box 4 | */ 5 | 6 | /* 7 | * This module only handles routing on the page (via anchor links) 8 | */ 9 | Application.addModule('page', function(context) { 10 | 11 | 'use strict'; 12 | 13 | //-------------------------------------------------------------------------- 14 | // Private 15 | //-------------------------------------------------------------------------- 16 | 17 | var routerService; 18 | 19 | //-------------------------------------------------------------------------- 20 | // Public 21 | //-------------------------------------------------------------------------- 22 | 23 | return { 24 | 25 | /** 26 | * Initializes the module. Caches a data store object to todos 27 | * @returns {void} 28 | */ 29 | init: function() { 30 | var baseUrl = context.getGlobal('location').pathname; 31 | 32 | routerService = context.getService('router'); 33 | routerService.init([ 34 | baseUrl, 35 | baseUrl + 'active', 36 | baseUrl + 'completed' 37 | ]); 38 | }, 39 | 40 | /** 41 | * Destroys the module. 42 | * @returns {void} 43 | */ 44 | destroy: function() { 45 | routerService.destroy(); 46 | } 47 | 48 | }; 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /examples/todo/bower_components/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | if (location.hostname === 'todomvc.com') { 5 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 6 | } 7 | 8 | function getSourcePath() { 9 | // If accessed via tastejs.github.io/todomvc/, strip the project path. 10 | if (location.hostname.indexOf('github.io') > 0) { 11 | return location.pathname.replace(/todomvc\//, ''); 12 | } 13 | return location.pathname; 14 | } 15 | 16 | function appendSourceLink() { 17 | var sourceLink = document.createElement('a'); 18 | var paragraph = document.createElement('p'); 19 | var footer = document.getElementById('info'); 20 | var urlBase = 'https://github.com/tastejs/todomvc/tree/gh-pages'; 21 | 22 | if (footer) { 23 | sourceLink.href = urlBase + getSourcePath(); 24 | sourceLink.appendChild(document.createTextNode('Check out the source')); 25 | paragraph.appendChild(sourceLink); 26 | footer.appendChild(paragraph); 27 | } 28 | } 29 | 30 | function redirect() { 31 | if (location.hostname === 'tastejs.github.io') { 32 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 33 | } 34 | } 35 | 36 | appendSourceLink(); 37 | redirect(); 38 | })(); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "t3js", 3 | "description": "T3 Javascript Framework", 4 | "version": "2.7.0", 5 | "author": "Box (https://www.box.com/)", 6 | "files": [ 7 | "LICENSE", 8 | "README.md", 9 | "dist" 10 | ], 11 | "bugs": "http://github.com/box/t3js/issues", 12 | "homepage": "http://t3js.org", 13 | "license": "Apache-2.0", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/box/t3js" 17 | }, 18 | "scripts": { 19 | "test": "node Makefile.js test", 20 | "test-watch": "node Makefile.js test-watch", 21 | "lint": "node Makefile.js lint", 22 | "dist": "node Makefile.js dist", 23 | "patch": "node Makefile.js patch", 24 | "minor": "node Makefile.js minor", 25 | "major": "node Makefile.js major" 26 | }, 27 | "main": "dist/t3-native.js", 28 | "devDependencies": { 29 | "assertive-chai": "^1.0.2", 30 | "dateformat": "^1.0.11", 31 | "eslint": "^1.1.0", 32 | "jquery": "^1.11.1", 33 | "karma": "^0.12.31", 34 | "karma-coverage": "^0.2.7", 35 | "karma-mocha": "^0.1.10", 36 | "karma-mocha-reporter": "^1.0.2", 37 | "karma-phantomjs-launcher": "^0.1.4", 38 | "karma-threshold-reporter": "^0.1.15", 39 | "leche": "^2.1.1", 40 | "mocha": "^2.2.1", 41 | "phantomjs": "^1.9.16", 42 | "semver": "^4.3.3", 43 | "shelljs": "^0.4.0", 44 | "shelljs-nodecli": "^0.1.1", 45 | "sinon": "^1.14.1", 46 | "uglify-js": "^2.4.17" 47 | }, 48 | "keywords": [ 49 | "javascript", 50 | "framework", 51 | "browser", 52 | "client-side", 53 | "modular", 54 | "component" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /tests/api-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for T3 API (package.json and dist files) 3 | * @author Box 4 | */ 5 | 6 | /* eslint-env node */ 7 | /* eslint strict: [2, "global"] */ 8 | 9 | 'use strict'; 10 | 11 | // @NOTE(nzakas): This file runs in Node.js, not Karma! 12 | 13 | //------------------------------------------------------------------------------ 14 | // Requirements 15 | //------------------------------------------------------------------------------ 16 | 17 | var assert = require('assertive-chai').assert, 18 | leche = require('leche'), 19 | path = require('path'), 20 | defaultT3 = require('../dist/t3'), 21 | nativeT3 = require('../dist/t3-native'), 22 | jqueryT3 = require('../dist/t3-jquery'), 23 | pkg = require('../package.json'); 24 | 25 | //------------------------------------------------------------------------------ 26 | // Tests 27 | //------------------------------------------------------------------------------ 28 | 29 | describe('API', function() { 30 | 31 | describe('Defaults', function() { 32 | it('should use native DOM in default file', function() { 33 | assert.equal(defaultT3.DOM.type, 'native'); 34 | }); 35 | 36 | it('should use native DOM in native file', function() { 37 | assert.equal(nativeT3.DOM.type, 'native'); 38 | }); 39 | 40 | it('should use jQuery DOM in jQuery file', function() { 41 | assert.equal(jqueryT3.DOM.type, 'jquery'); 42 | }); 43 | }); 44 | 45 | describe('Exports', function() { 46 | 47 | leche.withData({ 48 | 'Default T3': defaultT3, 49 | 'Native T3': nativeT3, 50 | 'jQuery T3': jqueryT3 51 | }, function(T3) { 52 | 53 | leche.withData([ 54 | 'DOM', 55 | 'DOMEventDelegate', 56 | 'EventTarget', 57 | 'Application', 58 | 'Context' 59 | ], function(name) { 60 | it('should have Box.' + name, function() { 61 | assert.isDefined(T3[name]); 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | }); 68 | 69 | describe('package.json', function() { 70 | 71 | it('should export native T3', function() { 72 | assert.equal(require(path.join('../', pkg.main)), nativeT3); 73 | }); 74 | 75 | }); 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /examples/todo/js/modules/header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Header Module 3 | * @author Box 4 | */ 5 | 6 | /* 7 | * Handles creation of new todo items 8 | */ 9 | Application.addModule('header', function(context) { 10 | 11 | 'use strict'; 12 | 13 | //-------------------------------------------------------------------------- 14 | // Private 15 | //-------------------------------------------------------------------------- 16 | 17 | var ENTER_KEY = 13; 18 | 19 | var todosDB; 20 | 21 | //-------------------------------------------------------------------------- 22 | // Public 23 | //-------------------------------------------------------------------------- 24 | 25 | return { 26 | 27 | /** 28 | * Initializes the module. Caches a data store object to todos 29 | * @returns {void} 30 | */ 31 | init: function() { 32 | todosDB = context.getService('todos-db'); 33 | }, 34 | 35 | /** 36 | * Destroys the module. 37 | * @returns {void} 38 | */ 39 | destroy: function() { 40 | todosDB = null; 41 | }, 42 | 43 | /** 44 | * Handles the keydown event for the module. 45 | * @param {Event} event A DOM-normalized event object. 46 | * @param {HTMLElement} element The nearest HTML element with a data-type 47 | * attribute specified or null if there is none. 48 | * @param {string} elementType The value of data-type for the nearest 49 | * element with that attribute specified or null if there is none. 50 | * @returns {void} 51 | */ 52 | onkeydown: function(event, element, elementType) { 53 | 54 | // code to be run when a click occurs 55 | if (elementType === 'new-todo-input') { 56 | 57 | if (event.keyCode === ENTER_KEY) { 58 | 59 | var todoTitle = (element.value).trim(); 60 | 61 | if (todoTitle.length) { 62 | var newTodoId = todosDB.add(todoTitle); 63 | 64 | context.broadcast('todoadded', { 65 | id: newTodoId 66 | }); 67 | 68 | // Clear input afterwards 69 | element.value = ''; 70 | } 71 | 72 | event.preventDefault(); 73 | event.stopPropagation(); 74 | } 75 | 76 | } 77 | 78 | } 79 | }; 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /lib/dom-native.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview DOM abstraction to use native browser functionality to add and remove event listeners 3 | * in T3 4 | * @author jdivock 5 | */ 6 | 7 | 8 | Box.NativeDOM = (function(){ 9 | 'use strict'; 10 | 11 | return { 12 | 13 | type: 'native', 14 | 15 | /** 16 | * Returns the first element that is a descendant of the element 17 | * on which it is invoked that matches the specified group of selectors. 18 | * @param {HTMLElement} root parent element to query off of 19 | * @param {string} selector query string to match on 20 | * 21 | * @returns {HTMLElement} first element found matching query 22 | */ 23 | query: function(root, selector){ 24 | return root.querySelector(selector); 25 | }, 26 | 27 | /** 28 | * Returns a non-live NodeList of all elements descended from the 29 | * element on which it is invoked that match the specified group of CSS selectors. 30 | * @param {HTMLElement} root parent element to query off of 31 | * @param {string} selector query string to match on 32 | * 33 | * @returns {Array} elements found matching query 34 | */ 35 | queryAll: function(root, selector){ 36 | return root.querySelectorAll(selector); 37 | }, 38 | 39 | /** 40 | * Adds event listener to element using native event listener 41 | * @param {HTMLElement} element Target to attach listener to 42 | * @param {string} type Name of the action to listen for 43 | * @param {function} listener Function to be executed on action 44 | * 45 | * @returns {void} 46 | */ 47 | on: function(element, type, listener) { 48 | element.addEventListener(type, listener, false); 49 | }, 50 | 51 | /** 52 | * Removes event listener to element using native event listener functions 53 | * @param {HTMLElement} element Target to remove listener from 54 | * @param {string} type Name of the action remove listener from 55 | * @param {function} listener Function to be removed from action 56 | * 57 | * @returns {void} 58 | */ 59 | off: function(element, type, listener) { 60 | element.removeEventListener(type, listener, false); 61 | } 62 | }; 63 | }()); 64 | 65 | Box.DOM = Box.NativeDOM; 66 | -------------------------------------------------------------------------------- /lib/dom-jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview DOM abstraction to use jquery to add and remove event listeners 3 | * in T3 4 | * @author jdivock 5 | */ 6 | 7 | /* eslint-env jquery */ 8 | 9 | Box.JQueryDOM = (function() { 10 | 'use strict'; 11 | 12 | return { 13 | 14 | type: 'jquery', 15 | 16 | /** 17 | * Returns the first element that is a descendant of the element 18 | * on which it is invoked that matches the specified group of selectors. 19 | * @param {HTMLElement} root parent element to query off of 20 | * @param {string} selector query string to match on 21 | * 22 | * @returns {HTMLElement} first element found matching query 23 | */ 24 | query: function(root, selector) { 25 | // Aligning with native which returns null if not found 26 | return jQuery(root).find(selector)[0] || null; 27 | }, 28 | 29 | /** 30 | * Returns a non-live NodeList of all elements descended from the 31 | * element on which it is invoked that match the specified group of CSS selectors. 32 | * @param {HTMLElement} root parent element to query off of 33 | * @param {string} selector query string to match on 34 | * 35 | * @returns {Array} elements found matching query 36 | */ 37 | queryAll: function(root, selector) { 38 | return jQuery.makeArray(jQuery(root).find(selector)); 39 | }, 40 | 41 | /** 42 | * Adds event listener to element via jquery 43 | * @param {HTMLElement} element Target to attach listener to 44 | * @param {string} type Name of the action to listen for 45 | * @param {function} listener Function to be executed on action 46 | * 47 | * @returns {void} 48 | */ 49 | on: function(element, type, listener) { 50 | jQuery(element).on(type, listener); 51 | }, 52 | 53 | /** 54 | * Removes event listener to element via jquery 55 | * @param {HTMLElement} element Target to remove listener from 56 | * @param {string} type Name of the action remove listener from 57 | * @param {function} listener Function to be removed from action 58 | * 59 | * @returns {void} 60 | */ 61 | off: function(element, type, listener) { 62 | jQuery(element).off(type, listener); 63 | } 64 | }; 65 | }()); 66 | 67 | Box.DOM = Box.JQueryDOM; 68 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "window": false, 4 | "document": false, 5 | "Box": true, 6 | "$": false 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "no-alert": 2, 11 | "no-array-constructor": 2, 12 | "no-caller": 2, 13 | "no-catch-shadow": 2, 14 | "no-empty-label": 2, 15 | "no-eval": 2, 16 | "no-extend-native": 2, 17 | "no-extra-bind": 2, 18 | "no-extra-parens": [2, "functions"], 19 | "no-floating-decimal": 2, 20 | "no-implied-eval": 2, 21 | "no-iterator": 2, 22 | "no-label-var": 2, 23 | "no-labels": 2, 24 | "no-lone-blocks": 2, 25 | "no-loop-func": 2, 26 | "no-multi-spaces": 2, 27 | "no-multi-str": 2, 28 | "no-native-reassign": 2, 29 | "no-nested-ternary": 2, 30 | "no-new": 2, 31 | "no-new-func": 2, 32 | "no-new-object": 2, 33 | "no-new-wrappers": 2, 34 | "no-octal-escape": 2, 35 | "no-process-exit": 2, 36 | "no-proto": 2, 37 | "no-return-assign": 2, 38 | "no-script-url": 2, 39 | "no-sequences": 2, 40 | "no-shadow": 2, 41 | "no-shadow-restricted-names": 2, 42 | "no-spaced-func": 2, 43 | "no-trailing-spaces": 2, 44 | "no-undef-init": 2, 45 | "no-underscore-dangle": 0, 46 | "no-unused-expressions": 2, 47 | 48 | // We allow unused args 49 | "no-unused-vars": [2, {"args": "none"}], 50 | "no-use-before-define": 2, 51 | "no-with": 2, 52 | 53 | "brace-style": 2, 54 | "camelcase": 2, 55 | "comma-spacing": 2, 56 | "consistent-return": 2, 57 | "curly": [2, "all"], 58 | "dot-notation": [2, { "allowKeywords": true }], 59 | "eol-last": 2, 60 | "eqeqeq": 2, 61 | "func-style": [2, "declaration"], 62 | "guard-for-in": 2, 63 | "indent": [2, "tab"], 64 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 65 | "new-cap": 2, 66 | "new-parens": 2, 67 | "quotes": [2, "single"], 68 | "radix": 2, 69 | "semi": 2, 70 | "semi-spacing": [2, {"before": false, "after": true}], 71 | "space-after-keywords": 2, 72 | "space-infix-ops": 2, 73 | "space-return-throw-case": 2, 74 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 75 | "strict": [2, "function"], 76 | "wrap-iife": 2, 77 | "valid-jsdoc": [2, { 78 | "prefer": { 79 | "return": "returns" 80 | } 81 | }], 82 | "yoda": [2, "never"] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config/karma-conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Karma configuration for T3 development. 3 | * @author nzakas 4 | */ 5 | 6 | "use strict"; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | // None! 13 | 14 | //------------------------------------------------------------------------------ 15 | // Public 16 | //------------------------------------------------------------------------------ 17 | 18 | module.exports = function(config) { 19 | 20 | config.set({ 21 | 22 | // base path that will be used to resolve all patterns (eg. files, exclude) 23 | basePath: '../', 24 | 25 | // frameworks to use 26 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 27 | frameworks: ['mocha'], 28 | 29 | // list of files / patterns to load in the browser 30 | files: [ 31 | 'tests/utils/assert.js', 32 | 'node_modules/assertive-chai/assertive-chai.js', 33 | 'node_modules/sinon/pkg/sinon.js', 34 | 'node_modules/jquery/dist/jquery.js', 35 | 'node_modules/leche/dist/leche.js', 36 | 'tests/utils/common-setup.js', 37 | 'lib/box.js', 38 | 'lib/dom-native.js', 39 | 'lib/dom-jquery.js', 40 | 'lib/dom-event-delegate.js', 41 | 'lib/context.js', 42 | 'lib/event-target.js', 43 | 'lib/application.js', 44 | 'tests/*.js' 45 | ], 46 | 47 | // list of files to exclude 48 | exclude: [ 49 | 'tests/api-test.js', 50 | 'tests/test-service-provider-test.js' 51 | ], 52 | 53 | // preprocess matching files before serving them to the browser 54 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 55 | preprocessors: { 56 | 'lib/*.js': ['coverage'] 57 | }, 58 | 59 | // web server port 60 | port: 9876, 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS'], 65 | 66 | // enable / disable watching file and executing tests whenever any file changes 67 | autoWatch: false, 68 | 69 | // Continuous Integration mode 70 | // if true, Karma captures browsers, runs the tests and exits 71 | singleRun: true, 72 | 73 | // test results reporter to use 74 | // possible values: 'dots', 'progress' 75 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 76 | reporters: ['mocha', 'coverage', 'threshold'], 77 | 78 | // output HTML report of code coverage 79 | coverageReporter: { 80 | type: 'html', 81 | dir: 'coverage-client' 82 | }, 83 | 84 | // set coverage limits 85 | thresholdReporter: { 86 | statements: 94, 87 | branches: 80, 88 | functions: 98, 89 | lines: 94 90 | }, 91 | 92 | // output console.log calls to the console (default is false) 93 | captureConsole: true, 94 | 95 | // enable / disable colors in the output (reporters and logs) 96 | colors: true, 97 | 98 | // level of logging 99 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 100 | logLevel: config.LOG_INFO 101 | }); 102 | 103 | }; 104 | -------------------------------------------------------------------------------- /tests/event-target-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for event-target 3 | * @author Box 4 | */ 5 | 6 | describe('Box.EventTarget', function() { 7 | 8 | 'use strict'; 9 | 10 | var sandbox = sinon.sandbox.create(); 11 | var eventTarget; 12 | 13 | beforeEach(function() { 14 | eventTarget = new Box.EventTarget(); 15 | }); 16 | 17 | afterEach(function() { 18 | sandbox.verifyAndRestore(); 19 | }); 20 | 21 | describe('Multiple event handlers', function() { 22 | 23 | it('should be called for custom event', function() { 24 | eventTarget.on('myevent', sandbox.mock()); 25 | eventTarget.on('myevent', sandbox.mock()); 26 | eventTarget.fire('myevent'); 27 | }); 28 | 29 | it('should be prevented for duplicate handlers', function() { 30 | var callback = sandbox.mock(); 31 | eventTarget.on('myevent', callback); 32 | eventTarget.on('myevent', callback); 33 | eventTarget.on('myevent', callback); 34 | eventTarget.fire('myevent'); 35 | }); 36 | 37 | }); 38 | 39 | describe('Separate event handlers', function() { 40 | 41 | it('should be called for separate custom events', function() { 42 | eventTarget.on('myevent1', sandbox.mock()); 43 | eventTarget.on('myevent2', sandbox.mock().never()); 44 | eventTarget.fire('myevent1'); 45 | }); 46 | 47 | }); 48 | 49 | describe('Event handler', function() { 50 | 51 | it('should be called for custom event', function() { 52 | eventTarget.on('myevent', sandbox.mock()); 53 | eventTarget.fire('myevent'); 54 | }); 55 | 56 | it('should be called for custom event', function() { 57 | eventTarget.on('myevent', sandbox.mock()); 58 | eventTarget.fire('myevent'); 59 | }); 60 | 61 | it('should be called with custom event object for custom event', function() { 62 | var handler = sandbox.mock().withArgs({ 63 | type: 'myevent', 64 | data: undefined 65 | }); 66 | 67 | eventTarget.on('myevent', handler); 68 | eventTarget.fire('myevent'); 69 | }); 70 | 71 | it('should be called with custom event object and extra data for custom event', function() { 72 | var handler = sandbox.mock().withArgs({ 73 | type: 'myevent', 74 | data: { 75 | foo: 'bar', 76 | time: 'now' 77 | } 78 | }); 79 | 80 | eventTarget.on('myevent', handler); 81 | eventTarget.fire('myevent', { 82 | foo: 'bar', 83 | time: 'now' 84 | }); 85 | }); 86 | 87 | it('should not be called for custom event after being removed', function() { 88 | var handler = sandbox.spy(); 89 | eventTarget.on('myevent', handler); 90 | 91 | eventTarget.off('myevent', handler); 92 | 93 | eventTarget.fire('myevent'); 94 | assert.ok(handler.notCalled); 95 | }); 96 | 97 | it('should be called even after another event handler for the same type removes itself', function() { 98 | var handler1, 99 | handler2 = sandbox.spy(); 100 | 101 | handler1 = function() { 102 | // this handler removes itself 103 | this.off('myevent', handler1); 104 | }; 105 | 106 | eventTarget.on('myevent', handler1); 107 | eventTarget.on('myevent', handler2); 108 | 109 | eventTarget.fire('myevent'); 110 | assert.ok(handler2.called); 111 | }); 112 | 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /config/testing-utils-karma-conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Karma configuration for T3 Testing Utilities 3 | * @author jtan 4 | */ 5 | 6 | "use strict"; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | // None! 13 | 14 | //------------------------------------------------------------------------------ 15 | // Public 16 | //------------------------------------------------------------------------------ 17 | 18 | module.exports = function(config) { 19 | 20 | config.set({ 21 | 22 | // base path that will be used to resolve all patterns (eg. files, exclude) 23 | basePath: '../', 24 | 25 | // frameworks to use 26 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 27 | frameworks: ['mocha'], 28 | 29 | // list of files / patterns to load in the browser 30 | files: [ 31 | 'tests/utils/assert.js', 32 | 'node_modules/assertive-chai/assertive-chai.js', 33 | 'node_modules/sinon/pkg/sinon.js', 34 | 'node_modules/jquery/dist/jquery.js', 35 | 'node_modules/leche/dist/leche.js', 36 | 'tests/utils/common-setup.js', 37 | 'lib/box.js', 38 | 'lib/dom-native.js', 39 | 'lib/dom-jquery.js', 40 | 'lib/dom-event-delegate.js', 41 | 'lib/event-target.js', 42 | 'lib/application-stub.js', 43 | 'lib/test-service-provider.js', 44 | // Actual tests to run 45 | 'tests/test-service-provider-test.js', 46 | ], 47 | 48 | // list of files to exclude 49 | exclude: [ 50 | 'tests/api-test.js' 51 | ], 52 | 53 | // preprocess matching files before serving them to the browser 54 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 55 | preprocessors: { 56 | 'lib/test-service-provider.js': ['coverage'] 57 | }, 58 | 59 | // web server port 60 | port: 9876, 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS'], 65 | 66 | // enable / disable watching file and executing tests whenever any file changes 67 | autoWatch: false, 68 | 69 | // Continuous Integration mode 70 | // if true, Karma captures browsers, runs the tests and exits 71 | singleRun: true, 72 | 73 | // test results reporter to use 74 | // possible values: 'dots', 'progress' 75 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 76 | reporters: ['mocha', 'coverage', 'threshold'], 77 | 78 | // output HTML report of code coverage 79 | coverageReporter: { 80 | type: 'html', 81 | dir: 'coverage-client' 82 | }, 83 | 84 | // set coverage limits 85 | thresholdReporter: { 86 | statements: 94, 87 | branches: 80, 88 | functions: 88, 89 | lines: 94 90 | }, 91 | 92 | // output console.log calls to the console (default is false) 93 | captureConsole: true, 94 | 95 | // enable / disable colors in the output (reporters and logs) 96 | colors: true, 97 | 98 | // level of logging 99 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 100 | logLevel: config.LOG_INFO 101 | }); 102 | 103 | }; 104 | -------------------------------------------------------------------------------- /examples/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | T3 • TodoNotMVC 7 | 8 | 9 | 10 | 11 |
12 | 18 |
19 | 20 | 21 | 23 | 33 |
34 | 49 |
50 | 55 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/event-target.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Definition of a custom event type. This is used as a utility 3 | * throughout the framework whenever custom events are used. It is intended to 4 | * be inherited from, either through the prototype or via mixin. 5 | * @author Box 6 | */ 7 | 8 | Box.EventTarget = (function() { 9 | 10 | 'use strict'; 11 | 12 | /** 13 | * An object that is capable of generating custom events and also 14 | * executing handlers for events when they occur. 15 | * @constructor 16 | */ 17 | function EventTarget() { 18 | 19 | /** 20 | * Map of events to handlers. The keys in the object are the event names. 21 | * The values in the object are arrays of event handler functions. 22 | * @type {Object} 23 | * @private 24 | */ 25 | this._handlers = {}; 26 | } 27 | 28 | EventTarget.prototype = { 29 | 30 | // restore constructor 31 | constructor: EventTarget, 32 | 33 | /** 34 | * Adds a new event handler for a particular type of event. 35 | * @param {string} type The name of the event to listen for. 36 | * @param {Function} handler The function to call when the event occurs. 37 | * @returns {void} 38 | */ 39 | on: function(type, handler) { 40 | 41 | var handlers = this._handlers[type], 42 | i, 43 | len; 44 | 45 | if (typeof handlers === 'undefined') { 46 | handlers = this._handlers[type] = []; 47 | } 48 | 49 | for (i = 0, len = handlers.length; i < len; i++) { 50 | if (handlers[i] === handler) { 51 | // prevent duplicate handlers 52 | return; 53 | } 54 | } 55 | 56 | handlers.push(handler); 57 | }, 58 | 59 | /** 60 | * Fires an event with the given name and data. 61 | * @param {string} type The type of event to fire. 62 | * @param {Object} [data] An object with properties that should end up on 63 | * the event object for the given event. 64 | * @returns {void} 65 | */ 66 | fire: function(type, data) { 67 | 68 | var handlers, 69 | i, 70 | len, 71 | event = { 72 | type: type, 73 | data: data 74 | }; 75 | 76 | // if there are handlers for the event, call them in order 77 | handlers = this._handlers[event.type]; 78 | if (handlers instanceof Array) { 79 | // @NOTE: do a concat() here to create a copy of the handlers array, 80 | // so that if another handler is removed of the same type, it doesn't 81 | // interfere with the handlers array during this loop 82 | handlers = handlers.concat(); 83 | for (i = 0, len = handlers.length; i < len; i++) { 84 | handlers[i].call(this, event); 85 | } 86 | } 87 | }, 88 | 89 | /** 90 | * Removes an event handler from a given event. 91 | * @param {string} type The name of the event to remove from. 92 | * @param {Function} handler The function to remove as a handler. 93 | * @returns {void} 94 | */ 95 | off: function(type, handler) { 96 | 97 | var handlers = this._handlers[type], 98 | i, 99 | len; 100 | 101 | if (handlers instanceof Array) { 102 | for (i = 0, len = handlers.length; i < len; i++) { 103 | if (handlers[i] === handler) { 104 | handlers.splice(i, 1); 105 | break; 106 | } 107 | } 108 | } 109 | } 110 | }; 111 | 112 | return EventTarget; 113 | 114 | }()); 115 | -------------------------------------------------------------------------------- /tests/dom-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for DOM abstraction layer 3 | * @author Box 4 | */ 5 | 6 | 7 | describe('Box.DOM', function() { 8 | 'use strict'; 9 | 10 | leche.withData({ 11 | native: [Box.NativeDOM], 12 | jquery: [Box.JQueryDOM] 13 | }, function(dom) { 14 | var sandbox = sinon.sandbox.create(); 15 | 16 | var testModule, 17 | nestedModule; 18 | 19 | before(function() { 20 | Box.DOM = dom; 21 | var fixture = document.createElement('div'); 22 | fixture.id = 'mocha-fixture'; 23 | document.body.appendChild(fixture); 24 | }); 25 | 26 | beforeEach(function() { 27 | testModule = $('
')[0]; 28 | nestedModule = $('
')[0]; 29 | $('#mocha-fixture').append(testModule, nestedModule); 30 | }); 31 | 32 | afterEach(function() { 33 | sandbox.verifyAndRestore(); 34 | 35 | $('#mocha-fixture').empty(); 36 | }); 37 | 38 | after(function() { 39 | $('#mocha-fixture').remove(); 40 | }); 41 | 42 | describe('type', function() { 43 | it('should match type value of passed in event type', function() { 44 | assert.equal(Box.DOM.type, dom.type); 45 | }); 46 | }); 47 | 48 | describe('query()', function() { 49 | it('should return first element when multiples exist', function() { 50 | var testEl = Box.DOM.query(document.getElementById('mocha-fixture'), 'div'); 51 | 52 | assert.equal(testEl, testModule); 53 | }); 54 | 55 | it('should return null when no matches exist', function() { 56 | var testEl = Box.DOM.query(document.getElementById('mocha-fixture'), 'article'); 57 | 58 | assert.isNull(testEl); 59 | }); 60 | 61 | it('should return the element when exactly one match exists', function() { 62 | var testEl = Box.DOM.query(document.getElementById('mocha-fixture'), 'button'); 63 | var testBtn = document.getElementById('module-target'); 64 | 65 | assert.equal(testEl, testBtn); 66 | }); 67 | }); 68 | 69 | describe('queryAll()', function() { 70 | it('should return all elements when multiples exist', function() { 71 | var testEl = Box.DOM.queryAll(document.getElementById('mocha-fixture'), 'div'); 72 | 73 | assert.equal(testEl.length, 3); 74 | }); 75 | 76 | it('should return an empty array when no matches exist', function() { 77 | var testEl = Box.DOM.queryAll(document.getElementById('mocha-fixture'), 'article'); 78 | 79 | assert.equal(testEl.length, 0); 80 | }); 81 | 82 | it('should return an array of one element when exactly one match exists', function() { 83 | var testEl = Box.DOM.queryAll(document.getElementById('mocha-fixture'), 'button'); 84 | var testBtn = document.getElementById('module-target'); 85 | 86 | assert.equal(testEl.length, 1); 87 | assert.equal(testEl[0].id, testBtn.id); 88 | }); 89 | }); 90 | 91 | describe('on()', function() { 92 | it('should call an attached function', function() { 93 | var testBtn = document.getElementById('module-target'); 94 | Box.DOM.on(testBtn, 'click', sandbox.mock()); 95 | 96 | testBtn.click(); 97 | }); 98 | }); 99 | 100 | describe('off()', function() { 101 | it('should not call a function when it\'s listener has been turned off', function() { 102 | var neverEver = sandbox.mock().never(); 103 | var testBtn = document.getElementById('module-target'); 104 | Box.DOM.on(testBtn, 'click', neverEver); 105 | Box.DOM.off(testBtn, 'click', neverEver); 106 | 107 | testBtn.click(); 108 | }); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /examples/todo/js/services/todos-db.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview A todo object manager with built-in functionality 3 | * @author Box 4 | */ 5 | 6 | /** 7 | * Todo Object 8 | * @typedef {Object} Todo 9 | * @property {number} id A unique identifier 10 | * @property {string} title A label for the todo 11 | * @property {boolean} completed Is the task complete? 12 | */ 13 | 14 | /* 15 | * A todo object manager with built-in functionality 16 | */ 17 | Application.addService('todos-db', function() { 18 | 19 | 'use strict'; 20 | 21 | //-------------------------------------------------------------------------- 22 | // Private 23 | //-------------------------------------------------------------------------- 24 | /** @type {Object} */ 25 | var todos = {}; 26 | 27 | /** @type {number} */ 28 | var counter = 0; 29 | 30 | //-------------------------------------------------------------------------- 31 | // Public 32 | //-------------------------------------------------------------------------- 33 | 34 | return { 35 | 36 | /** 37 | * Returns a Todo by id 38 | * @returns {Todo} 39 | */ 40 | get: function(id) { 41 | return todos[id] || null; 42 | }, 43 | 44 | /** 45 | * Returns list of all todos 46 | * @returns {Todo[]} List of todos 47 | */ 48 | getList: function() { 49 | var todoList = []; 50 | 51 | Object.keys(todos).forEach(function(id) { 52 | todoList.push(todos[id]); 53 | }); 54 | 55 | return todoList; 56 | }, 57 | 58 | /** 59 | * Marks all todos as complete 60 | * @returns {void} 61 | */ 62 | markAllAsComplete: function() { 63 | var me = this; 64 | Object.keys(todos).forEach(function(id) { 65 | me.markAsComplete(id); 66 | }); 67 | }, 68 | 69 | /** 70 | * Marks a todo as completed 71 | * @param {number} id The id of the todo 72 | * @returns {void} 73 | */ 74 | markAsComplete: function(id) { 75 | if (todos[id]) { 76 | todos[id].completed = true; 77 | } 78 | }, 79 | 80 | /** 81 | * Marks all todos as incomplete 82 | * @returns {void} 83 | */ 84 | markAllAsIncomplete: function() { 85 | var me = this; 86 | Object.keys(todos).forEach(function(id) { 87 | me.markAsIncomplete(id); 88 | }); 89 | }, 90 | 91 | /** 92 | * Marks a todo as incomplete 93 | * @param {number} id The id of the todo 94 | * @returns {void} 95 | */ 96 | markAsIncomplete: function(id) { 97 | if (todos[id]) { 98 | todos[id].completed = false; 99 | } 100 | }, 101 | 102 | /** 103 | * Removes all completed tasks 104 | * @returns {void} 105 | */ 106 | removeCompleted: function() { 107 | var me = this; 108 | Object.keys(todos).forEach(function(id) { 109 | if (todos[id].completed) { 110 | me.remove(id); 111 | } 112 | }); 113 | }, 114 | 115 | /** 116 | * Adds a todo 117 | * @param {string} title The label of the todo 118 | * @returns {number} The id of the new todo 119 | */ 120 | add: function(title) { 121 | var todoId = counter++; 122 | todos[todoId] = { 123 | id: todoId, 124 | title: title, 125 | completed: false 126 | }; 127 | return todoId; 128 | }, 129 | 130 | /** 131 | * Edits a todo label 132 | * @param {number} id The unique identifier of the todo 133 | * @param {string} title The new label of the todo 134 | * @returns {void} 135 | */ 136 | edit: function(id, title) { 137 | if (todos[id]) { 138 | todos[id].title = title; 139 | } 140 | }, 141 | 142 | /** 143 | * Removes a todo by id 144 | * @param {number} id identifier of Todo to remove 145 | * @returns {void} 146 | */ 147 | remove: function(id) { 148 | if (todos[id]) { 149 | delete todos[id]; 150 | } 151 | } 152 | }; 153 | 154 | }); 155 | -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Contains the Context type which is used by modules to interact 3 | * with the environment. 4 | * @author Box 5 | */ 6 | 7 | Box.Context = (function() { 8 | 9 | 'use strict'; 10 | 11 | /** 12 | * The object type that modules use to interact with the environment. Used 13 | * exclusively within Box.Application, but exposed publicly for easy testing. 14 | * @param {Box.Application} application The application object to wrap. 15 | * @param {HTMLElement} element Module's DOM element 16 | * @constructor 17 | */ 18 | function Context(application, element) { 19 | this.application = application; 20 | this.element = element; 21 | } 22 | 23 | //------------------------------------------------------------------------- 24 | // Passthrough Methods 25 | //------------------------------------------------------------------------- 26 | 27 | Context.prototype = { 28 | constructor: Context, 29 | 30 | /** 31 | * Passthrough method to application that broadcasts messages. 32 | * @param {string} name Name of the message event 33 | * @param {*} [data] Custom parameters for the message 34 | * @returns {void} 35 | */ 36 | broadcast: function(name, data) { 37 | this.application.broadcast(name, data); 38 | }, 39 | 40 | /** 41 | * Passthrough method to application that retrieves services. 42 | * @param {string} serviceName The name of the service to retrieve. 43 | * @returns {Object|null} An object if the service is found or null if not. 44 | */ 45 | getService: function(serviceName) { 46 | return this.application.getService(serviceName); 47 | }, 48 | 49 | /** 50 | * Checks if a service exists 51 | * @param {string} serviceName The name of the service to check. 52 | * @returns {boolean} True, if service exist. False, otherwise. 53 | */ 54 | hasService: function(serviceName) { 55 | return this.application.hasService(serviceName); 56 | }, 57 | 58 | /** 59 | * Returns any configuration information that was output into the page 60 | * for this instance of the module. 61 | * @param {string} [name] Specific config parameter 62 | * @returns {*} config value or the entire configuration JSON object 63 | * if no name is specified (null if either not found) 64 | */ 65 | getConfig: function(name) { 66 | return this.application.getModuleConfig(this.element, name); 67 | }, 68 | 69 | /** 70 | * Returns a global variable 71 | * @param {string} name Specific global var name 72 | * @returns {*} returns the window-scope variable matching the name, null otherwise 73 | */ 74 | getGlobal: function(name) { 75 | return this.application.getGlobal(name); 76 | }, 77 | 78 | /** 79 | * Returns global configuration data 80 | * @param {string} [name] Specific config parameter 81 | * @returns {*} config value or the entire configuration JSON object 82 | * if no name is specified (null if either not found) 83 | */ 84 | getGlobalConfig: function(name) { 85 | return this.application.getGlobalConfig(name); 86 | }, 87 | 88 | /** 89 | * Passthrough method that signals that an error has occurred. If in development mode, an error 90 | * is thrown. If in production mode, an event is fired. 91 | * @param {Error} [exception] The exception object to use. 92 | * @returns {void} 93 | */ 94 | reportError: function(exception) { 95 | this.application.reportError(exception); 96 | }, 97 | 98 | //------------------------------------------------------------------------- 99 | // Service Shortcuts 100 | //------------------------------------------------------------------------- 101 | 102 | /** 103 | * Returns the element that represents the module. 104 | * @returns {HTMLElement} The element representing the module. 105 | */ 106 | getElement: function() { 107 | return this.element; 108 | } 109 | 110 | }; 111 | 112 | return Context; 113 | 114 | }()); 115 | -------------------------------------------------------------------------------- /lib/application-stub.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Fake application to use during testing 3 | * @author Box 4 | */ 5 | 6 | (function() { 7 | 8 | 'use strict'; 9 | 10 | /* 11 | * When testing actual Application, it should be included after to overwrite this stub. 12 | */ 13 | Box.Application = (function() { 14 | 15 | var services = {}, 16 | modules = {}, 17 | behaviors = {}; 18 | 19 | return { 20 | 21 | /** 22 | * Resets the application stub back to a clean state. Will also remove pre-registered components. 23 | * @returns {Box.Application} The application object. 24 | */ 25 | reset: function() { 26 | services = {}; 27 | modules = {}; 28 | behaviors = {}; 29 | return this; 30 | }, 31 | 32 | /** 33 | * Registers a service to the application stub 34 | * @param {string} serviceName The name of the service 35 | * @param {Function} creator The service creator function 36 | * @returns {Box.Application} The application object. 37 | */ 38 | addService: function(serviceName, creator) { 39 | services[serviceName] = { 40 | creator: creator 41 | }; 42 | return this; 43 | }, 44 | 45 | /** 46 | * Registers a module to the application stub 47 | * @param {string} moduleName The name of the module 48 | * @param {Function} creator The behavior creator function 49 | * @returns {Box.Application} The application object. 50 | */ 51 | addModule: function(moduleName, creator) { 52 | modules[moduleName] = { 53 | creator: creator 54 | }; 55 | return this; 56 | }, 57 | 58 | /** 59 | * Registers a behavior to the application stub 60 | * @param {string} behaviorName The name of the behavior 61 | * @param {Function} creator The behavior creator function 62 | * @returns {Box.Application} The application object. 63 | */ 64 | addBehavior: function(behaviorName, creator) { 65 | behaviors[behaviorName] = { 66 | creator: creator 67 | }; 68 | return this; 69 | }, 70 | 71 | /** 72 | * Checks if a service exists 73 | * @param {string} serviceName The name of the service to check. 74 | * @returns {boolean} True, if service exist. False, otherwise. 75 | */ 76 | hasService: function(serviceName) { 77 | return services.hasOwnProperty(serviceName); 78 | }, 79 | 80 | /** 81 | * Will create a new instance of a service with the given application context 82 | * @param {string} serviceName The name of the service being created 83 | * @param {Object} application The application context object (usually a TestServiceProvider) 84 | * @returns {?Object} The service object 85 | */ 86 | getServiceForTest: function(serviceName, application) { 87 | var serviceData = services[serviceName]; 88 | if (serviceData) { 89 | return services[serviceName].creator(application); 90 | } 91 | return null; 92 | }, 93 | 94 | /** 95 | * Will create a new instance of a module with a given context 96 | * @param {string} moduleName The name of the module being created 97 | * @param {Object} context The context object (usually a TestServiceProvider) 98 | * @returns {?Object} The module object 99 | */ 100 | getModuleForTest: function(moduleName, context) { 101 | var module = modules[moduleName].creator(context); 102 | 103 | if (!context.getElement) { 104 | // Add in a default getElement function that matches the first module element 105 | // Developer should stub this out if there are more than one instance of this module 106 | context.getElement = function() { 107 | return document.querySelector('[data-module="' + moduleName + '"]'); 108 | }; 109 | } 110 | return module; 111 | }, 112 | 113 | /** 114 | * Will create a new instance of a behavior with a given context 115 | * @param {string} behaviorName The name of the behavior being created 116 | * @param {Object} context The context object (usually a TestServiceProvider) 117 | * @returns {?Object} The behavior object 118 | */ 119 | getBehaviorForTest: function(behaviorName, context) { 120 | var behaviorData = behaviors[behaviorName]; 121 | if (behaviorData) { 122 | // getElement on behaviors must be stubbed 123 | if (!context.getElement) { 124 | context.getElement = function() { 125 | throw new Error('You must stub `getElement` for behaviors.'); 126 | }; 127 | } 128 | return behaviors[behaviorName].creator(context); 129 | } 130 | return null; 131 | } 132 | 133 | }; 134 | 135 | }()); 136 | 137 | }()); 138 | -------------------------------------------------------------------------------- /examples/todo/js/modules/footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Footer Module 3 | * @author Box 4 | */ 5 | 6 | /* 7 | * Manages the footer module, including todo counts and filters 8 | */ 9 | Application.addModule('footer', function(context) { 10 | 11 | 'use strict'; 12 | 13 | //-------------------------------------------------------------------------- 14 | // Private 15 | //-------------------------------------------------------------------------- 16 | 17 | var todosDB, 18 | moduleEl; 19 | 20 | //-------------------------------------------------------------------------- 21 | // Public 22 | //-------------------------------------------------------------------------- 23 | 24 | return { 25 | 26 | messages: ['todoadded', 'todoremoved', 'todostatuschange', 'statechanged'], 27 | 28 | /** 29 | * Initializes the module. Caches a data store object to todos 30 | * @returns {void} 31 | */ 32 | init: function() { 33 | todosDB = context.getService('todos-db'); 34 | moduleEl = context.getElement(); 35 | }, 36 | 37 | /** 38 | * Destroys the module. 39 | * @returns {void} 40 | */ 41 | destroy: function() { 42 | moduleEl = null; 43 | todosDB = null; 44 | }, 45 | 46 | /** 47 | * Handles the click event for the module. 48 | * @param {Event} event A DOM-normalized event object. 49 | * @param {HTMLElement} element The nearest HTML element with a data-type 50 | * attribute specified or null if there is none. 51 | * @param {string} elementType The value of data-type for the nearest 52 | * element with that attribute specified or null if there is none. 53 | * @returns {void} 54 | */ 55 | onclick: function(event, element, elementType) { 56 | 57 | // code to be run when a click occurs 58 | if (elementType === 'clear-btn') { 59 | this.clearCompletedTodos(); 60 | } 61 | 62 | }, 63 | 64 | /** 65 | * Handles all messages received for the module. 66 | * @param {string} name The name of the message received. 67 | * @param {*} [data] Additional data sent along with the message. 68 | * @returns {void} 69 | */ 70 | onmessage: function(name, data) { 71 | 72 | switch(name) { 73 | case 'todoadded': 74 | case 'todoremoved': 75 | case 'todostatuschange': 76 | this.updateTodoCounts(); 77 | break; 78 | 79 | case 'statechanged': 80 | this.updateSelectedFilterByUrl(data.url); 81 | break; 82 | } 83 | }, 84 | 85 | /** 86 | * Updates the selected class on the filter links 87 | * @param {string} url The current url 88 | * @returns {void} 89 | */ 90 | updateSelectedFilterByUrl: function(url) { 91 | var linkEls = moduleEl.querySelectorAll('a'); 92 | 93 | for (var i = 0, len = linkEls.length; i < len; i++) { 94 | if (url === linkEls[i].pathname) { 95 | linkEls[i].classList.add('selected'); 96 | } else { 97 | linkEls[i].classList.remove('selected'); 98 | } 99 | } 100 | }, 101 | 102 | 103 | /** 104 | * Updates todo counts based on what is in the todo DB 105 | * @returns {void} 106 | */ 107 | updateTodoCounts: function() { 108 | var todos = todosDB.getList(); 109 | 110 | var completedCount = 0; 111 | for (var i = 0, len = todos.length; i < len; i++) { 112 | if (todos[i].completed) { 113 | completedCount++; 114 | } 115 | } 116 | 117 | var itemsLeft = todos.length - completedCount; 118 | 119 | this.updateItemsLeft(itemsLeft); 120 | this.updateCompletedButton(completedCount); 121 | 122 | }, 123 | 124 | /** 125 | * Updates the displayed count of incomplete tasks 126 | * @param {number} itemsLeft # of incomplete tasks 127 | * @returns {void} 128 | */ 129 | updateItemsLeft: function(itemsLeft) { 130 | var itemText = itemsLeft === 1 ? 'item' : 'items'; 131 | 132 | moduleEl.querySelector('.items-left-counter').textContent = itemsLeft; 133 | moduleEl.querySelector('.items-left-text').textContent = itemText + ' left'; 134 | }, 135 | 136 | /** 137 | * Updates the displayed count of completed tasks and hide/shows the button accordingly 138 | * @param {number} completedCount # of completed tasks 139 | * @returns {void} 140 | */ 141 | updateCompletedButton: function(completedCount) { 142 | if (completedCount > 0) { 143 | moduleEl.querySelector('.completed-count').textContent = completedCount; 144 | moduleEl.classList.add('has-completed-tasks'); 145 | } else { 146 | moduleEl.classList.remove('has-completed-tasks'); 147 | } 148 | }, 149 | 150 | /** 151 | * Removes any todos that have been completed 152 | * @returns {void} 153 | */ 154 | clearCompletedTodos: function() { 155 | todosDB.removeCompleted(); 156 | context.broadcast('todoremoved'); 157 | } 158 | 159 | }; 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /tests/context-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for module context 3 | * @author Box 4 | */ 5 | 6 | describe('Box.Context', function() { 7 | 8 | 'use strict'; 9 | 10 | var sandbox = sinon.sandbox.create(); 11 | var element = document.body; 12 | 13 | afterEach(function() { 14 | sandbox.verifyAndRestore(); 15 | }); 16 | 17 | describe('new Box.Context', function(){ 18 | it('should have Box.Context as its constructor', function(){ 19 | var context = new Box.Context(); 20 | 21 | assert.strictEqual(context.constructor, Box.Context); 22 | }); 23 | }); 24 | 25 | describe('broadcast()', function() { 26 | 27 | it('should pass through to application when called', function() { 28 | var context, 29 | message = 'foo', 30 | application = { 31 | broadcast: function() {} 32 | }; 33 | 34 | sandbox.mock(application).expects('broadcast').withArgs(message); 35 | context = new Box.Context(application, element); 36 | context.broadcast(message); 37 | }); 38 | 39 | it('should pass through to application when called with arguments', function() { 40 | var context, 41 | message = 'foo', 42 | data = {}, 43 | application = { 44 | broadcast: function() {} 45 | }; 46 | 47 | sandbox.mock(application).expects('broadcast').withArgs(message, data); 48 | context = new Box.Context(application, element); 49 | context.broadcast(message, data); 50 | }); 51 | 52 | }); 53 | 54 | describe('getService()', function() { 55 | 56 | it('should pass through to application when called with service name', function() { 57 | var context, 58 | serviceName = 'foo', 59 | service = {}, 60 | application = { 61 | getService: function() {} 62 | }; 63 | 64 | sandbox.mock(application).expects('getService').withArgs(serviceName).returns(service); 65 | context = new Box.Context(application, element); 66 | assert.equal(context.getService(serviceName), service, 'getService() should return correct service'); 67 | }); 68 | 69 | }); 70 | 71 | describe('hasService()', function() { 72 | 73 | it('should return true when Application has the service', function() { 74 | var context, 75 | serviceName = 'foo', 76 | application = { 77 | hasService: function() {} 78 | }; 79 | 80 | sandbox.mock(application).expects('hasService').withArgs(serviceName).returns(true); 81 | context = new Box.Context(application, element); 82 | assert.isTrue(context.hasService(serviceName), 'hasService() should return true'); 83 | }); 84 | 85 | it('should return false when Application has the service', function() { 86 | var context, 87 | serviceName = 'foo', 88 | application = { 89 | hasService: function() {} 90 | }; 91 | 92 | sandbox.mock(application).expects('hasService').withArgs(serviceName).returns(false); 93 | context = new Box.Context(application, element); 94 | assert.isFalse(context.hasService(serviceName), 'hasService() should return false'); 95 | }); 96 | 97 | }); 98 | 99 | describe('getConfig()', function() { 100 | 101 | it('should pass through module element and config name to application.getModuleConfig() when called', function() { 102 | var context, 103 | config = {}, 104 | application = { 105 | getModuleConfig: function() {} 106 | }; 107 | 108 | sandbox.mock(application).expects('getModuleConfig').withArgs(element, 'foo').returns(config); 109 | context = new Box.Context(application, element); 110 | context.getConfig('foo'); 111 | }); 112 | }); 113 | 114 | describe('getGlobal()', function() { 115 | 116 | it('should return the window-scope var when it exists', function () { 117 | var application = { 118 | getGlobal: function () {} 119 | }; 120 | 121 | sandbox.stub(application, 'getGlobal').withArgs('foo').returns('bar'); 122 | 123 | var context = new Box.Context(application, element); 124 | assert.strictEqual(context.getGlobal('foo'), 'bar', 'global var returned'); 125 | }); 126 | 127 | }); 128 | 129 | 130 | describe('getGlobalConfig()', function() { 131 | 132 | it('should pass through to application when called', function() { 133 | var context, 134 | application = { 135 | getGlobalConfig: function() {} 136 | }; 137 | 138 | sandbox.mock(application).expects('getGlobalConfig').withArgs('foo').returns('bar'); 139 | context = new Box.Context(application, element); 140 | assert.equal(context.getGlobalConfig('foo'), 'bar', 'correct config value returned'); 141 | }); 142 | 143 | }); 144 | 145 | describe('reportError()', function() { 146 | 147 | it('should pass through to application when called', function() { 148 | var context, 149 | application = { 150 | reportError: function() {} 151 | }, 152 | exception = new Error('test error'); 153 | 154 | sandbox.mock(application).expects('reportError').withArgs(exception); 155 | context = new Box.Context(application, element); 156 | 157 | context.reportError(exception); 158 | }); 159 | 160 | }); 161 | 162 | 163 | }); 164 | -------------------------------------------------------------------------------- /examples/todo/js/services/router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview A page routing service 3 | * @author Box 4 | */ 5 | 6 | /* 7 | * A router for our objects 8 | */ 9 | Application.addService('router', function(application) { 10 | 11 | 'use strict'; 12 | 13 | var history = application.getGlobal('history'); 14 | var pageRoutes; 15 | 16 | var regexRoutes = [], 17 | prevUrl; 18 | 19 | /** 20 | * Parses the user-friendly declared routes at the top of the application, 21 | * these could be init'ed or passed in from another context to help extract 22 | * navigation for T3 from the framework. Populates the regexRoutes variable 23 | * that is local to the service. 24 | * Regexs and parsing borrowed from Backbone's router since they did it right 25 | * and not inclined to make a new router syntax unless we have a reason to. 26 | * @returns {void} 27 | */ 28 | function parseRoutes() { 29 | // Regexs to convert a route (/file/:fileId) into a regex 30 | var optionalParam = /\((.*?)\)/g, 31 | namedParam = /(\(\?)?:\w+/g, 32 | splatParam = /\*\w+/g, 33 | escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g, 34 | namedParamCallback = function(match, optional) { 35 | return optional ? match : '([^\/]+)'; 36 | }, 37 | route, 38 | regexRoute; 39 | 40 | for (var i = 0, len = pageRoutes.length; i < len; i++) { 41 | route = pageRoutes[i]; 42 | regexRoute = route.replace(escapeRegExp, '\\$&') 43 | .replace(optionalParam, '(?:$1)?') 44 | .replace(namedParam, namedParamCallback) 45 | .replace(splatParam, '(.*?)'); 46 | regexRoutes.push(new RegExp('^' + regexRoute + '$')); 47 | } 48 | } 49 | 50 | /** 51 | * Finds the regex route that matches the current fragment and returns 52 | * the matched route. This could eventually map back to the route it was 53 | * created from to return a smarter object (ie /file/:fileId returning {fileId: 42}) 54 | * @param {string} fragment represents the current "URL" the user is getting to 55 | * @return {Object} 56 | */ 57 | function matchGlob(fragment) { 58 | var regexRoute, 59 | globMatches; 60 | 61 | for (var idx in regexRoutes) { 62 | regexRoute = regexRoutes[idx]; 63 | 64 | if (regexRoute.test(fragment)) { 65 | globMatches = fragment.match(regexRoute); 66 | } 67 | } 68 | 69 | return globMatches; 70 | } 71 | 72 | /** 73 | * Gets the fragment and broadcasts the previous URL and the current URL of 74 | * the page, ideally we wouldn't have a dependency on the previous URL... 75 | * @param {string} fragment the current "URL" the user is getting to 76 | * @returns {void} 77 | */ 78 | function broadcastStateChanged(fragment) { 79 | var globMatches = matchGlob(fragment); 80 | 81 | if (!!globMatches) { 82 | application.broadcast('statechanged', { 83 | url: fragment, 84 | prevUrl: prevUrl, 85 | params: globMatches.splice(1) // Get everything after the URL 86 | }); 87 | } 88 | } 89 | 90 | return { 91 | /** 92 | * The magical method the application code will call to navigate around, 93 | * high level it pushes the state, gets the templates from server, puts 94 | * them on the page and broadcasts the statechanged. 95 | * @param {Object} state the state associated with the URL 96 | * @param {string} title the title of the page 97 | * @param {string} fragment the current "URL" the user is getting to 98 | * @returns {void} 99 | */ 100 | route: function(state, title, fragment) { 101 | prevUrl = history.state.hash; 102 | 103 | // First push the state 104 | history.pushState(state, title, fragment); 105 | 106 | // Then make the AJAX request 107 | broadcastStateChanged(fragment); 108 | }, 109 | 110 | /** 111 | * Initialization that has to be done when the navigation service is required 112 | * by application code, sets up routes (which could be done later) and binds 113 | * to the popstate/hashchange events (which have to happen now). 114 | * @private 115 | * @param {string[]} routes An array of special route strings 116 | * @returns {void} 117 | */ 118 | init: function(inputRoutes) { 119 | pageRoutes = inputRoutes || {}; 120 | 121 | // Turn the routes globs into regular expressions... 122 | parseRoutes(); 123 | 124 | var me = this; 125 | 126 | // Bind the document click listener to all anchor tags 127 | $(document).on('click.router', 'a', function(e) { 128 | // Append the query string to the end of the pathname 129 | var url = e.target.pathname + e.target.search; 130 | 131 | // Stop the event here and we can handle it 132 | e.preventDefault(); 133 | e.stopPropagation(); 134 | 135 | // Check the hash and the URL and make sure they aren't the same 136 | // and then navigate (in the case of an anchors href='#') 137 | if (url !== history.state.hash) { 138 | me.route({}, '', url); 139 | } 140 | }); 141 | 142 | window.onpopupstate = function() { 143 | var state = history.state, 144 | url = state.hash; 145 | 146 | me.route({}, '', url); 147 | }; 148 | 149 | history.pushState({}, '', application.getGlobal('location').pathname); 150 | }, 151 | 152 | /** 153 | * Unbinds the event handlers on the DOM 154 | * @returns {void} 155 | */ 156 | destroy: function() { 157 | $(document).off('click.router', 'a'); 158 | } 159 | }; 160 | }); 161 | 162 | -------------------------------------------------------------------------------- /tests/dom-event-delegate-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for DOMEventDelegate 3 | * @author Box 4 | */ 5 | 6 | 7 | describe('Box.DOMEventDelegate', function() { 8 | 9 | 'use strict'; 10 | 11 | leche.withData({ 12 | native: [Box.NativeDOM], 13 | jquery: [Box.JQueryDOM] 14 | }, function(dom) { 15 | 16 | var sandbox = sinon.sandbox.create(), 17 | delegate; 18 | 19 | before(function() { 20 | Box.DOM = dom; 21 | var fixture = document.createElement('div'); 22 | fixture.id = 'mocha-fixture'; 23 | document.body.appendChild(fixture); 24 | }); 25 | 26 | afterEach(function () { 27 | sandbox.verifyAndRestore(); 28 | 29 | delegate = null; 30 | $('#mocha-fixture').empty(); 31 | }); 32 | 33 | after(function() { 34 | $('#mocha-fixture').remove(); 35 | }); 36 | 37 | /** 38 | * Use jQuery to click an element. Need to do this because PhantomJS 39 | * doesn't properly handle clicks otherwise. 40 | * @param {HTMLElement} element The element to click. 41 | * @returns {void} 42 | * @private 43 | */ 44 | function click(element) { 45 | $(element).click(); 46 | } 47 | 48 | describe('attachEvents()', function() { 49 | 50 | var testElement; 51 | 52 | beforeEach(function() { 53 | testElement = $('
')[0]; 54 | $('#mocha-fixture').append(testElement); 55 | }); 56 | 57 | it('should respond to click when called with a click handler', function() { 58 | 59 | delegate = new Box.DOMEventDelegate(testElement, { 60 | onclick: sandbox.mock() 61 | }); 62 | 63 | delegate.attachEvents(); 64 | 65 | click(testElement.firstChild); 66 | }); 67 | 68 | it('should pass three arguments to onclick when a data-type element is clicked', function() { 69 | 70 | delegate = new Box.DOMEventDelegate(testElement, { 71 | onclick: sandbox.mock().withExactArgs( 72 | sinon.match({ type: 'click' }), 73 | testElement.firstChild, 74 | 'submit' 75 | ) 76 | }); 77 | 78 | delegate.attachEvents(); 79 | 80 | click(testElement.firstChild); 81 | }); 82 | 83 | it('should pass three arguments to onclick when element with both data-type and data-module is clicked', function() { 84 | 85 | delegate = new Box.DOMEventDelegate(testElement, { 86 | onclick: sandbox.mock().withExactArgs( 87 | sinon.match({ type: 'click' }), 88 | testElement, 89 | 'form-type' 90 | ) 91 | }); 92 | 93 | delegate.attachEvents(); 94 | 95 | testElement.setAttribute('data-type', 'form-type'); 96 | click(document.querySelector('#non-typed-button')); 97 | }); 98 | 99 | it('should pass three arguments to onclick when a non-data-type element is clicked', function() { 100 | 101 | delegate = new Box.DOMEventDelegate(testElement, { 102 | onclick: sandbox.mock().withExactArgs( 103 | sinon.match({ type: 'click' }), 104 | null, 105 | '' 106 | ) 107 | }); 108 | 109 | delegate.attachEvents(); 110 | 111 | testElement.firstChild.removeAttribute('data-type'); 112 | click(testElement.firstChild); 113 | }); 114 | 115 | it('should pass three arguments to onclick when element has no ancestor with a data-type or data-module', function() { 116 | 117 | delegate = new Box.DOMEventDelegate(testElement, { 118 | onclick: sandbox.mock().withExactArgs( 119 | sinon.match({ type: 'click' }), 120 | null, 121 | '' 122 | ) 123 | }); 124 | 125 | delegate.attachEvents(); 126 | 127 | testElement.removeAttribute('data-module'); 128 | testElement.firstChild.removeAttribute('data-type'); 129 | click(testElement.firstChild); 130 | }); 131 | 132 | it('should respond to click only once when called twice', function() { 133 | 134 | delegate = new Box.DOMEventDelegate(testElement, { 135 | onclick: sandbox.mock() // enforces one call 136 | }); 137 | 138 | delegate.attachEvents(); 139 | delegate.attachEvents(); 140 | 141 | click(testElement.firstChild); 142 | }); 143 | 144 | it('should respond to custom events when a custom event type list is specified', function() { 145 | 146 | delegate = new Box.DOMEventDelegate(testElement, { 147 | ontouchstart: sandbox.mock() 148 | }, ['touchstart']); 149 | 150 | delegate.attachEvents(); 151 | 152 | var event = document.createEvent('Event'); 153 | event.initEvent('touchstart', true, true); 154 | 155 | testElement.firstChild.dispatchEvent(event); 156 | }); 157 | 158 | it('should respond only to custom events when a custom event type list is specified', function() { 159 | 160 | delegate = new Box.DOMEventDelegate(testElement, { 161 | onclick: sandbox.mock().never() 162 | }, ['onsubmit']); 163 | 164 | delegate.attachEvents(); 165 | 166 | click(testElement.firstChild); 167 | }); 168 | 169 | 170 | }); 171 | 172 | describe('detachEvents()', function() { 173 | 174 | it('should not respond to click when called with a click handler', function() { 175 | var testElement = $('
')[0]; 176 | $('#mocha-fixture').append(testElement); 177 | 178 | delegate = new Box.DOMEventDelegate(testElement, { 179 | onclick: sandbox.mock().never() 180 | }); 181 | 182 | delegate.attachEvents(); 183 | delegate.detachEvents(); 184 | click(testElement.firstChild); 185 | }); 186 | 187 | }); 188 | 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /lib/test-service-provider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview A service provider that also contains a few pre-stubbed functions 3 | * @author Box 4 | */ 5 | 6 | (function() { 7 | 8 | 'use strict'; 9 | 10 | // We should use a reference directly the original application-stub object in case Box.Application gets stubbed out 11 | var application = Box.Application; 12 | 13 | // function stubs that are automatically included on a TestServiceProvider 14 | var APPLICATION_CONTEXT_STUBS = [ 15 | // Shared between Application and Context 16 | 'broadcast', 'getGlobalConfig', 'reportError', 'reportWarning', 'reportInfo', 17 | 18 | // Application (only ones that should be called from a service) 19 | 'start', 'stop', 'startAll', 'stopAll', 'isStarted', 20 | 21 | // Context (module/behavior only) - getElement done separately 22 | 'getConfig' 23 | ]; 24 | 25 | /** 26 | * Return a function stub that will throw an error if the test code does not properly mock out dependencies. 27 | * @param {string} method The name of the method being invoked 28 | * @returns {Function} A function stub 29 | */ 30 | function functionStub(method) { 31 | /* eslint-disable no-extra-parens */ 32 | return (function(methodKey) { 33 | return function() { 34 | throw new Error('Unexpected call to method "' + methodKey + '". You must stub this method out.'); 35 | }; 36 | }(method)); 37 | /* eslint-enable no-extra-parens */ 38 | } 39 | 40 | /** 41 | * Check if service is allowed. This should be replaced by Array.prototype.indexOf when we drop IE 8 support 42 | * @param {string} serviceName The name of the service being checked 43 | * @param {string[]} allowedServicesList The list of services allowed by the user 44 | * @returns {boolean} Returns true if service is in the allowed list 45 | * @private 46 | */ 47 | function isServiceAllowed(serviceName, allowedServicesList) { 48 | for (var i = 0, len = allowedServicesList.length; i < len; i++) { 49 | if (allowedServicesList[i] === serviceName) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | /** 57 | * This object is used as a stub for application/context that is normally passed into services/modules/behaviors at create time. 58 | * It exposes the stubbed services passed in through the getService() method. Also allows the use of pre-registered services. 59 | * @param {Object} serviceStubs A map of service stubs 60 | * @param {string[]} [allowedServicesList] List of real services to allow access to 61 | * @constructor 62 | */ 63 | Box.TestServiceProvider = function(serviceStubs, allowedServicesList) { 64 | this.stubs = serviceStubs || {}; 65 | this.allowedServicesList = allowedServicesList || []; 66 | this.serviceInstances = {}; // Stores the instances of pre-registered services 67 | }; 68 | 69 | Box.TestServiceProvider.prototype = { 70 | constructor: Box.TestServiceProvider, 71 | 72 | /** 73 | * Will retrieve either a service stub (prioritized) or the real service. Returns null if neither exists. 74 | * @param {string} serviceName The name of the service being retrieved 75 | * @returns {?Object} A service object or null if none exists 76 | * @throws {Error} Will throw an error if service does not exist 77 | */ 78 | getService: function(serviceName) { 79 | var service = this.stubs[serviceName]; 80 | 81 | // Return a service stub if found 82 | if (service) { 83 | return service; 84 | } 85 | 86 | // Check if this service is allowed to be pre-registered 87 | if (isServiceAllowed(serviceName, this.allowedServicesList)) { 88 | // Now check if we've already created the service instance 89 | if (this.serviceInstances.hasOwnProperty(serviceName)) { 90 | return this.serviceInstances[serviceName]; 91 | } else { 92 | var preRegisteredService = application.getServiceForTest(serviceName, this); 93 | if (preRegisteredService) { 94 | // Save the instance for the next call to getService() 95 | this.serviceInstances[serviceName] = preRegisteredService; 96 | return preRegisteredService; 97 | } else { 98 | throw new Error('Service "' + serviceName + '" does not exist.'); 99 | } 100 | } 101 | 102 | } else { 103 | throw new Error('Service "' + serviceName + '" is not on the `allowedServiceList`. Use "new Box.TestServiceProvider({ ...stubs... }, [\'' + serviceName + '\']);" or stub the service out.'); 104 | } 105 | }, 106 | 107 | /** 108 | * Checks if a service exists 109 | * @param {string} serviceName The name of the service to check. 110 | * @returns {boolean} True, if service exist. False, otherwise. 111 | */ 112 | hasService: function(serviceName) { 113 | return this.stubs.hasOwnProperty(serviceName) || isServiceAllowed(serviceName, this.allowedServicesList) && application.hasService(serviceName); 114 | }, 115 | 116 | /** 117 | * Retrieves a global var (this is the actual implementation for convenience in testing) 118 | * @param {string} name The name of the global 119 | * @returns {?*} The global object referenced or null if it does not exist 120 | */ 121 | getGlobal: function(name) { 122 | if (name in window) { 123 | return window[name]; 124 | } else { 125 | return null; 126 | } 127 | } 128 | }; 129 | 130 | // Add stubbed functions onto prototype for testing convenience 131 | (function() { 132 | var stubName; 133 | for (var i = 0, len = APPLICATION_CONTEXT_STUBS.length; i < len; i++) { 134 | stubName = APPLICATION_CONTEXT_STUBS[i]; 135 | Box.TestServiceProvider.prototype[stubName] = functionStub(stubName); 136 | } 137 | }()); 138 | 139 | }()); 140 | -------------------------------------------------------------------------------- /tests/test-service-provider-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for TestServiceProvider 3 | * @author Box 4 | */ 5 | 6 | describe('Box.TestServiceProvider', function() { 7 | 8 | 'use strict'; 9 | 10 | var sandbox = sinon.sandbox.create(); 11 | var testServiceProvider; 12 | 13 | beforeEach(function() { 14 | Box.Application.reset(); 15 | }); 16 | 17 | afterEach(function() { 18 | sandbox.verifyAndRestore(); 19 | 20 | Box.Application.reset(); 21 | }); 22 | 23 | describe('new Box.TestServiceProvider', function() { 24 | 25 | it('should have Box.TestServiceProvider as its constructor', function(){ 26 | testServiceProvider = new Box.TestServiceProvider(); 27 | 28 | assert.strictEqual(testServiceProvider.constructor, Box.TestServiceProvider); 29 | }); 30 | 31 | it('should set this.stubs to serviceStubs when called', function() { 32 | var serviceStub = { 33 | bar: 'baz' 34 | }; 35 | testServiceProvider = new Box.TestServiceProvider({ 36 | foo: serviceStub 37 | }); 38 | 39 | assert.propertyVal(testServiceProvider.stubs, 'foo', serviceStub); 40 | }); 41 | 42 | it('should set allowedServicesList to this.allowedServicesList when called', function() { 43 | testServiceProvider = new Box.TestServiceProvider({}, ['example-service']); 44 | 45 | assert.deepEqual(testServiceProvider.allowedServicesList, ['example-service']); 46 | assert.deepEqual(testServiceProvider.serviceInstances, {}); 47 | }); 48 | 49 | }); 50 | 51 | describe('getService()', function() { 52 | 53 | var stubbedService1 = { service: '1'}, 54 | stubbedService2 = { service: '2'}, 55 | preRegisteredService2 = { service: 'pre-2'}, 56 | preRegisteredService3 = { service: 'pre-3' }; 57 | 58 | beforeEach(function() { 59 | // Pre-register services 60 | var getServiceForTestStub = sandbox.stub(Box.Application, 'getServiceForTest'); 61 | getServiceForTestStub.withArgs('service2').returns(preRegisteredService2); 62 | getServiceForTestStub.withArgs('service3').returns(preRegisteredService3); 63 | getServiceForTestStub.returns(null); 64 | 65 | testServiceProvider = new Box.TestServiceProvider({ 66 | service1: stubbedService1, 67 | service2: stubbedService2 68 | }, ['service2', 'service3', 'service4']); 69 | }); 70 | 71 | it('should return the stubbed version of a service when a stub is available', function() { 72 | assert.deepEqual(testServiceProvider.getService('service1'), stubbedService1); 73 | }); 74 | 75 | it('should return the stubbed version of a service when a stub and a pre-registered service are available', function() { 76 | assert.deepEqual(testServiceProvider.getService('service2'), stubbedService2); 77 | }); 78 | 79 | it('should return the pre-registered version of a service when a pre-registered service is available', function() { 80 | assert.deepEqual(testServiceProvider.getService('service3'), preRegisteredService3); 81 | }); 82 | 83 | it('should return the same pre-registered service instance when called multiple times', function() { 84 | assert.deepEqual(testServiceProvider.getService('service3'), preRegisteredService3); 85 | assert.strictEqual(testServiceProvider.getService('service3'), testServiceProvider.getService('service3')); 86 | }); 87 | 88 | it('should throw an error when service is not available and service is on the allowedServiceList', function() { 89 | assert.throws(function() { 90 | testServiceProvider.getService('service4'); 91 | }, 'Service "not-available-service" does not exist.'); 92 | }); 93 | 94 | it('should throw an error when service is not available and service is not on the allowedServiceList', function() { 95 | assert.throws(function() { 96 | testServiceProvider.getService('not-available-service'); 97 | }, 'Service "not-available-service" is not on the `allowedServiceList`. Use "new Box.TestServiceProvider({ ...stubs... }, [\'not-available-service\']);" or stub the service out.'); 98 | }); 99 | 100 | }); 101 | 102 | describe('hasService()', function() { 103 | 104 | beforeEach(function() { 105 | // Setup Application 106 | var hasServiceStub = sandbox.stub(Box.Application, 'hasService'); 107 | hasServiceStub.withArgs('service2').returns(true); 108 | hasServiceStub.withArgs('service4').returns(true); 109 | hasServiceStub.returns(false); 110 | 111 | testServiceProvider = new Box.TestServiceProvider({ 112 | service1: {} 113 | }, ['service2', 'service3']); 114 | }); 115 | 116 | it('should return false when service does not exist', function() { 117 | assert.isFalse(testServiceProvider.hasService('non-existent-service')); 118 | }); 119 | 120 | it('should return true when service is stubbed', function() { 121 | assert.isTrue(testServiceProvider.hasService('service1')); 122 | }); 123 | 124 | it('should return true when service is allowed and available', function() { 125 | assert.isTrue(testServiceProvider.hasService('service2')); 126 | }); 127 | 128 | it('should return false when service is allowed but not available', function() { 129 | assert.isFalse(testServiceProvider.hasService('service3')); 130 | }); 131 | 132 | it('should return false when service is available but not allowed', function() { 133 | assert.isFalse(testServiceProvider.hasService('service4')); 134 | }); 135 | 136 | }); 137 | 138 | describe('getGlobal()', function() { 139 | 140 | it('should return window var when requesting an existing property', function() { 141 | window.foo = 'bar'; 142 | assert.strictEqual(testServiceProvider.getGlobal('foo'), window.foo); 143 | delete window.foo; 144 | }); 145 | 146 | it('should return null when requesting a non-existent property', function() { 147 | assert.isNull(testServiceProvider.getGlobal('foobar')); 148 | }); 149 | 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /lib/dom-event-delegate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview An object that encapsulates event delegation wireup for a 3 | * DOM element. 4 | * @author Box 5 | */ 6 | 7 | Box.DOMEventDelegate = (function() { 8 | 9 | 'use strict'; 10 | 11 | // Supported events for modules. Only events that bubble properly can be used in T3. 12 | var DEFAULT_EVENT_TYPES = ['click', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 13 | 'mouseenter', 'mouseleave', 'mousemove', 'keydown', 'keyup', 'submit', 'change', 14 | 'contextmenu', 'dblclick', 'input', 'focusin', 'focusout']; 15 | 16 | 17 | /** 18 | * Determines if a given element represents a module. 19 | * @param {HTMLElement} element The element to check. 20 | * @returns {boolean} True if the element represents a module, false if not. 21 | * @private 22 | */ 23 | function isModuleElement(element) { 24 | return element && element.hasAttribute('data-module'); 25 | } 26 | 27 | /** 28 | * Determines if a given element represents a T3 type. 29 | * @param {HTMLElement} element The element to check. 30 | * @returns {boolean} True if the element represents a T3 type, false if not. 31 | * @private 32 | */ 33 | function isTypeElement(element) { 34 | return element && element.hasAttribute('data-type'); 35 | } 36 | 37 | /** 38 | * Finds the closest ancestor that of an element that has a data-type 39 | * attribute. 40 | * @param {HTMLElement} element The element to start searching from. 41 | * @returns {HTMLElement} The matching element or null if not found. 42 | */ 43 | function getNearestTypeElement(element) { 44 | var found = false; 45 | var moduleBoundaryReached = false; 46 | 47 | // We need to check for the existence of 'element' since occasionally we call this on a detached element node. 48 | // For example: 49 | // 1. event handlers like mouseout may sometimes detach nodes from the DOM 50 | // 2. event handlers like mouseleave will still fire on the detached node 51 | // Checking existence of element.parentNode ensures the element is a valid HTML Element 52 | while (!found && element && element.parentNode && !moduleBoundaryReached) { 53 | found = isTypeElement(element); 54 | moduleBoundaryReached = isModuleElement(element); 55 | 56 | if (!found) { 57 | element = element.parentNode; 58 | } 59 | 60 | } 61 | 62 | return found ? element : null; 63 | } 64 | 65 | /** 66 | * Iterates over each supported event type that is also in the handler, applying 67 | * a callback function. This is used to more easily attach/detach all events. 68 | * @param {string[]} eventTypes A list of event types to iterate over 69 | * @param {Object} handler An object with onclick, onmouseover, etc. methods. 70 | * @param {Function} callback The function to call on each event type. 71 | * @param {Object} [thisValue] The value of "this" inside the callback. 72 | * @returns {void} 73 | * @private 74 | */ 75 | function forEachEventType(eventTypes, handler, callback, thisValue) { 76 | 77 | var i, 78 | type; 79 | 80 | for (i = 0; i < eventTypes.length; i++) { 81 | type = eventTypes[i]; 82 | 83 | // only call the callback if the event is on the handler 84 | if (handler['on' + type]) { 85 | callback.call(thisValue, type); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * An object that manages events within a single DOM element. 92 | * @param {HTMLElement} element The DOM element to handle events for. 93 | * @param {Object} handler An object containing event handlers such as "onclick". 94 | * @param {string[]} [eventTypes] A list of event types to handle (events must bubble). Defaults to a common set of events. 95 | * @constructor 96 | */ 97 | function DOMEventDelegate(element, handler, eventTypes) { 98 | 99 | /** 100 | * The DOM element that this object is handling events for. 101 | * @type {HTMLElement} 102 | */ 103 | this.element = element; 104 | 105 | /** 106 | * Object on which event handlers are available. 107 | * @type {Object} 108 | * @private 109 | */ 110 | this._handler = handler; 111 | 112 | /** 113 | * List of event types to handle (make sure these events bubble!) 114 | * @type {string[]} 115 | * @private 116 | */ 117 | this._eventTypes = eventTypes || DEFAULT_EVENT_TYPES; 118 | 119 | /** 120 | * Tracks event handlers whose this-value is bound to the correct 121 | * object. 122 | * @type {Object} 123 | * @private 124 | */ 125 | this._boundHandler = {}; 126 | 127 | /** 128 | * Indicates if events have been attached. 129 | * @type {boolean} 130 | * @private 131 | */ 132 | this._attached = false; 133 | } 134 | 135 | 136 | DOMEventDelegate.prototype = { 137 | 138 | // restore constructor 139 | constructor: DOMEventDelegate, 140 | 141 | _handleEvent: function(event) { 142 | var targetElement = getNearestTypeElement(event.target), 143 | elementType = targetElement ? targetElement.getAttribute('data-type') : ''; 144 | 145 | this._handler['on' + event.type](event, targetElement, elementType); 146 | }, 147 | 148 | /** 149 | * Attaches all event handlers for the DOM element. 150 | * @returns {void} 151 | */ 152 | attachEvents: function() { 153 | if (!this._attached) { 154 | 155 | forEachEventType(this._eventTypes, this._handler, function(eventType) { 156 | var that = this; 157 | 158 | function handleEvent() { 159 | that._handleEvent.apply(that, arguments); 160 | } 161 | 162 | Box.DOM.on(this.element, eventType, handleEvent); 163 | 164 | this._boundHandler[eventType] = handleEvent; 165 | }, this); 166 | 167 | this._attached = true; 168 | } 169 | }, 170 | 171 | /** 172 | * Detaches all event handlers for the DOM element. 173 | * @returns {void} 174 | */ 175 | detachEvents: function() { 176 | forEachEventType(this._eventTypes, this._handler, function(eventType) { 177 | Box.DOM.off(this.element, eventType, this._boundHandler[eventType]); 178 | }, this); 179 | } 180 | }; 181 | 182 | return DOMEventDelegate; 183 | }()); 184 | 185 | -------------------------------------------------------------------------------- /examples/todo/js/modules/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Todo List 3 | * @author Box 4 | */ 5 | 6 | /* 7 | * Manages the todo list which includes adding/removing/checking items 8 | */ 9 | Application.addModule('list', function(context) { 10 | 11 | 'use strict'; 12 | 13 | //-------------------------------------------------------------------------- 14 | // Private 15 | //-------------------------------------------------------------------------- 16 | var todosDB, 17 | moduleEl, 18 | listEl, 19 | filter; 20 | 21 | /** 22 | * Returns true if all todos in the list are complete 23 | * @returns {boolean} 24 | * @private 25 | */ 26 | function isListCompleted() { 27 | var todos = todosDB.getList(); 28 | var len = todos.length; 29 | var isComplete = len > 0; 30 | for (var i = 0; i < len; i++) { 31 | if (!todos[i].completed) { 32 | isComplete = false; 33 | break; 34 | } 35 | } 36 | return isComplete; 37 | } 38 | 39 | /** 40 | * Sets the todo filter based on URL 41 | * @returns {void} 42 | * @private 43 | */ 44 | function setFilterByUrl(url) { 45 | if (url.indexOf('/active') > -1) { 46 | filter = 'incomplete'; 47 | } else if (url.indexOf('/completed') > -1) { 48 | filter = 'complete'; 49 | } else { 50 | filter = ''; 51 | } 52 | } 53 | 54 | //-------------------------------------------------------------------------- 55 | // Public 56 | //-------------------------------------------------------------------------- 57 | 58 | return { 59 | /** 60 | * The behaviors that this module uses. 61 | * @type String[] 62 | */ 63 | behaviors: ['todo'], 64 | 65 | /** 66 | * The messages that this modules listens for. 67 | * @type String[] 68 | */ 69 | messages: ['todoadded', 'todoremoved', 'todostatuschange', 'statechanged'], 70 | 71 | /** 72 | * Initializes the module. Caches a data store object to todos 73 | * @returns {void} 74 | */ 75 | init: function() { 76 | todosDB = context.getService('todos-db'); 77 | 78 | moduleEl = context.getElement(); 79 | listEl = moduleEl.querySelector('#todo-list'); 80 | }, 81 | 82 | /** 83 | * Destroys the module. 84 | * @returns {void} 85 | */ 86 | destroy: function() { 87 | listEl = null; 88 | moduleEl = null; 89 | todosDB = null; 90 | }, 91 | 92 | /** 93 | * Handles all click events for the module. 94 | * @param {Event} event A DOM-normalized event object. 95 | * @param {HTMLElement} element The nearest HTML element with a data-type 96 | * attribute specified or null if there is none. 97 | * @param {string} elementType The value of data-type for the nearest 98 | * element with that attribute specified or null if there is none. 99 | * @returns {void} 100 | */ 101 | onchange: function(event, element, elementType) { 102 | 103 | if (elementType === 'select-all-checkbox') { 104 | var shouldMarkAsComplete = element.checked; 105 | 106 | if (shouldMarkAsComplete) { 107 | todosDB.markAllAsComplete(); 108 | } else { 109 | todosDB.markAllAsIncomplete(); 110 | } 111 | 112 | this.renderList(); 113 | 114 | context.broadcast('todostatuschange'); 115 | } 116 | 117 | }, 118 | 119 | /** 120 | * Handles all messages received for the module. 121 | * @param {string} name The name of the message received. 122 | * @param {*} [data] Additional data sent along with the message. 123 | * @returns {void} 124 | */ 125 | onmessage: function(name, data) { 126 | 127 | switch(name) { 128 | case 'todoadded': 129 | case 'todoremoved': 130 | this.renderList(); 131 | this.updateSelectAllCheckbox(); 132 | break; 133 | 134 | case 'todostatuschange': 135 | this.updateSelectAllCheckbox(); 136 | break; 137 | 138 | case 'statechanged': 139 | setFilterByUrl(data.url); 140 | this.renderList(); 141 | break; 142 | } 143 | 144 | }, 145 | 146 | /** 147 | * Creates a list item for a todo based off of a template 148 | * @param {number} id The id of the todo 149 | * @param {string} title The todo label 150 | * @param {boolean} isCompleted Is todo complete 151 | * @returns {Node} 152 | */ 153 | createTodo: function(id, title, isCompleted) { 154 | var todoTemplateEl = moduleEl.querySelector('.todo-template-container li'), 155 | newTodoEl = todoTemplateEl.cloneNode(true); 156 | 157 | // Set the label of the todo 158 | newTodoEl.querySelector('label').textContent = title; 159 | newTodoEl.setAttribute('data-todo-id', id); 160 | if (isCompleted) { 161 | newTodoEl.classList.add('completed'); 162 | newTodoEl.querySelector('input[type="checkbox"]').checked = true; 163 | } 164 | 165 | return newTodoEl; 166 | }, 167 | 168 | /** 169 | * Appends a new todo list element to the todo-list 170 | * @param {number} id The id of the todo 171 | * @param {string} title The todo label 172 | * @param {boolean} isCompleted Is todo complete 173 | * @returns {void} 174 | */ 175 | addTodoItem: function(id, title, isCompleted) { 176 | listEl.appendChild(this.createTodo(id, title, isCompleted)); 177 | }, 178 | 179 | /** 180 | * Updates the 'checked' status of the select-all checkbox based on the status of the list 181 | * @returns {void} 182 | */ 183 | updateSelectAllCheckbox: function() { 184 | var selectAllCheckboxEl = moduleEl.querySelector('#toggle-all'); 185 | selectAllCheckboxEl.checked = isListCompleted(); 186 | }, 187 | 188 | /** 189 | * Removes all todo elements from the list (not necessarily from the db) 190 | * @returns {void} 191 | */ 192 | clearList: function() { 193 | var todoListEl = moduleEl.querySelector('#todo-list'); 194 | while (todoListEl.hasChildNodes()) { 195 | todoListEl.removeChild(todoListEl.lastChild); 196 | } 197 | }, 198 | 199 | /** 200 | * Renders all todos in the todos db 201 | * @returns {void} 202 | */ 203 | renderList: function() { 204 | // Clear the todo list first 205 | this.clearList(); 206 | 207 | var todos = todosDB.getList(), 208 | todo; 209 | 210 | // Render todos - factor in the current filter as well 211 | for (var i = 0, len = todos.length; i < len; i++) { 212 | todo = todos[i]; 213 | 214 | if (!filter 215 | || (filter === 'incomplete' && !todo.completed) 216 | || (filter === 'complete' && todo.completed)) { 217 | this.addTodoItem(todo.id, todo.title, todo.completed); 218 | 219 | } 220 | } 221 | } 222 | }; 223 | 224 | }); 225 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v2.7.0 - August 26, 2016 2 | 3 | * 2.7.0 (Jeff Tan) 4 | * Issue #159: Added setErrorHandler to override default error handling (Rey) 5 | 6 | v2.6.0 - July 13, 2016 7 | 8 | * 2.6.0 (Jeff Tan) 9 | * Add test for reportInfo() method (Mike) 10 | * Added new method to report console info messages in debug mode (#175) (Mike) 11 | * Update README.md (Jeff Tan) 12 | 13 | v2.5.0 - May 20, 2016 14 | 15 | * 2.5.0 (Jeff Tan) 16 | * Fixes constructor reference in new instances of Box.Context and Box.TestServiceProvider (fixes #172) (Adam Platti) 17 | 18 | v2.4.0 - March 23, 2016 19 | 20 | * 2.4.0 (Jeff Tan) 21 | * Add reportWarning to test service provider (Jeff Tan) 22 | * Add custom event types (Jeff Tan) 23 | * Add reportWarning to Box.Application (Jeff Tan) 24 | * Include the data-module element when finding event targets (Jeff Tan) 25 | * Update Copyright year (Jeff Tan) 26 | 27 | v2.3.0 - March 16, 2016 28 | 29 | * 2.3.0 (Jeff Tan) 30 | * Set the handler context to the actual behavior or module instance (Justin Bennett) 31 | 32 | v2.2.0 - March 14, 2016 33 | 34 | * 2.2.0 (Jeff Tan) 35 | * Use recommended wording (Justin Bennett) 36 | * Remove bind, reword test (Justin Bennett) 37 | * Remove unnecessary `bind` call. (Zephraph) 38 | * Support cleaner onmessage handler (Zephraph) 39 | * Add malformed JSON error message (Zephraph) 40 | 41 | v2.1.0 - January 31, 2016 42 | 43 | * 2.1.0 (Jeff Tan) 44 | * Add basic element check to DOMEventDelegate (Jeff Tan) 45 | * Allow getModuleConfig() to execute before module instantiation (Jeff Tan) 46 | 47 | v2.0.2 - November 18, 2015 48 | 49 | * 2.0.2 (Jeff Tan) 50 | * Remove package.json caching in the dist function (Jeff Tan) 51 | 52 | v2.0.1 - November 18, 2015 53 | 54 | * 2.0.1 (Jeff Tan) 55 | * Fix release script to work with multiple dist calls (Jeff Tan) 56 | * Add debug output to release script (Jeff Tan) 57 | 58 | v2.0.0 - November 18, 2015 59 | 60 | * 2.0.0 (Jeff Tan) 61 | * Add separate file header for testing package (Jeff Tan) 62 | * Fixing spaces in build script (Jeff Tan) 63 | * Return singleton service instance when getService is called on pre-registered services (Jeff Tan) 64 | * Add mousemove to allowed event types (Jeff Tan) 65 | * Update README.md (Jeff Tan) 66 | * Remove service exports from T3 (Jeff Tan) 67 | * Update Readme for 2.0.0 release and add auto-version updating (Jeff Tan) 68 | * Add hasService() to context object (Jeff Tan) 69 | * [Breaking] Add allowedServiceList to TestServiceProvider (Jeff Tan) 70 | * Use jQuery instead of $ for dom event delegation (Jeff Tan) 71 | * Add linting to test directory (Jeff Tan) 72 | * Breaking: Initialize behaviors before module (Jeff Tan) 73 | * Change getService() to throw error when requesting non-existent service. Add hasService() method. (Jeff Tan) 74 | * Bind event handlers after init() is called (Jeff Tan) 75 | * Throw error when duplicate behaviors are included (Jeff Tan) 76 | * Revert "Check for circular dependencies only during instantiation of service" (Denis Rodin) 77 | * Check for circular dependencies only during instantiation of service (Denis Rodin) 78 | * Check for circular dependencies only during instantiation of service (Denis Rodin) 79 | * Breaking: Use NativeDOM by default (fixes #76) (Nicholas C. Zakas) 80 | * Build: Upgrade ESLint (fixes #90) (Nicholas C. Zakas) 81 | 82 | v1.5.1 - August 10, 2015 83 | 84 | * 1.5.1 (Nicholas C. Zakas) 85 | * Fix: Ensure DOMEventDelegate is in T3 release (Nicholas C. Zakas) 86 | 87 | v1.5.0 - August 5, 2015 88 | 89 | * 1.5.0 (Nicholas C. Zakas) 90 | * Update: Make Box.Application chainable (fixes #65) (Nicholas C. Zakas) 91 | * New: Add Box.DOMEventDelegate (fixes #47, fixes #63) (Nicholas C. Zakas) 92 | * Update email address for support (Nicholas C. Zakas) 93 | 94 | v1.4.1 - June 24, 2015 95 | 96 | * 1.4.1 (Jeff Tan) 97 | * Ammended existing test to cover new fields (Jason Divock) 98 | * Making errors a bit more reportable (Jason Divock) 99 | 100 | v1.4.0 - June 11, 2015 101 | 102 | * 1.4.0 (Jeff Tan) 103 | * Add missing commonJS and AMD wrappers (Jeff Tan) 104 | 105 | v1.3.0 - June 9, 2015 106 | 107 | * 1.3.0 (Jeff Tan) 108 | * Breaking out dom events into an abstraction layer, building different versions of t3 accordingly (Jason Divock) 109 | * Add AMD support. Fixes #50 (Priyajeet Hora) 110 | * Fix release script (Nicholas C. Zakas) 111 | 112 | v1.2.0 - April 23, 2015 113 | 114 | * 1.2.0 (Nicholas C. Zakas) 115 | * Change karma to use mocha reporter (Jeff Tan) 116 | * Fix embedded sourcemap URL (fixes #31) (Nicholas C. Zakas) 117 | * Add wrapper to T3 for CommonJS (Jeff Tan) 118 | * Remove event delegation on detached nodes (Jeff Tan) 119 | * Update: Fire event when broadcast() is called (fixes #43) (Nicholas C. Zakas) 120 | * Reverting dist changes (Priyajeet Hora) 121 | * Prevent event-target.js duplicate handlers. Fixes #35. (Priyajeet Hora) 122 | * Clean up duplicated assign code (fixes #29) (azu) 123 | * Todo example fails to update completed items. Fixes #25 (Priyajeet Hora) 124 | * Firefox innerText fixes as well as switching the select all checkbox logic to use the model data instead of view elements Fixes #21 (Priyajeet Hora) 125 | * readme grammar (Matthew Hadley) 126 | * Apply or remove completed class when select all is clicked. Fixes #18. (Priyajeet Hora) 127 | * Adding missing doctype Fixes #14 (Priyajeet Hora) 128 | 129 | v1.1.1 - April 14, 2015 130 | 131 | * 1.1.1 (Nicholas C. Zakas) 132 | * Update build script to publish to npm and update site (Nicholas C. Zakas) 133 | * Add Travis configuration (Nicholas C. Zakas) 134 | * Add sourcemap generation (Nicholas C. Zakas) 135 | * Update README with badges and more info (Nicholas C. Zakas) 136 | * Missing semicolon in application-stub.js Fixes #4 (Priyajeet Hora) 137 | * Create t3-testing bundle (Jeff Tan) 138 | * Updating Karam coverage dir and adding it to gitignore (Tarrence van As) 139 | * Add ESLint to package.json (Jeff Tan) 140 | * Add license and contribution guide (Nicholas C. Zakas) 141 | * Rename t3 to t3js (Jeff Tan) 142 | * Adding items to .npmignore. Fixes #60. (Priyajeet Hora) 143 | * Adding items to .npmignore. Fixes #60. (Priyajeet Hora) 144 | * Adding npmignore. Fixes #60 (Priyajeet Hora) 145 | 146 | v1.1.0 - March 30, 2015 147 | 148 | * 1.1.0 (Nicholas C. Zakas) 149 | * Fix release task (Nicholas C. Zakas) 150 | * Add changelog generation (Nicholas C. Zakas) 151 | * Update README for open sourcing (Nicholas C. Zakas) 152 | * Remove most jQuery references (Nicholas C. Zakas) 153 | * Switch to ShellJS and create dist files (Nicholas C. Zakas) 154 | * Add focusin and focusout events (Jeff Tan) 155 | 156 | 1.0.2 - February 20, 2015 157 | 158 | * v1.0.2 (Jeff Tan) 159 | * Detect circular service dependencies (fixes #51) (Nicholas C. Zakas) 160 | -------------------------------------------------------------------------------- /examples/todo/js/behaviors/todo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview The TODO item behavior 3 | * @author Box 4 | */ 5 | 6 | /*global Box*/ 7 | 8 | /* 9 | * Handles behavior of todo items 10 | */ 11 | Application.addBehavior('todo', function(context) { 12 | 13 | 'use strict'; 14 | 15 | //-------------------------------------------------------------------------- 16 | // Private 17 | //-------------------------------------------------------------------------- 18 | 19 | var ENTER_KEY = 13, 20 | ESCAPE_KEY = 27; 21 | 22 | var todosDB, 23 | moduleEl; 24 | 25 | /** 26 | * Returns the nearest todo element 27 | * @param {HTMLElement} element A root/child node of a todo element to search from 28 | * @returns {HTMLElement} 29 | * @private 30 | */ 31 | function getClosestTodoElement(element) { 32 | 33 | var matchesSelector = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.msMatchesSelector; 34 | 35 | while (element) { 36 | if (matchesSelector.bind(element)('li')) { 37 | return element; 38 | } else { 39 | element = element.parentNode; 40 | } 41 | } 42 | return false; 43 | 44 | } 45 | 46 | /** 47 | * Returns the id of the nearest todo element 48 | * @param {HTMLElement} element A root/child node of a todo element 49 | * @returns {string} 50 | * @private 51 | */ 52 | function getClosestTodoId(element) { 53 | var todoEl = getClosestTodoElement(element); 54 | return todoEl.getAttribute('data-todo-id'); 55 | } 56 | 57 | //-------------------------------------------------------------------------- 58 | // Public 59 | //-------------------------------------------------------------------------- 60 | 61 | return { 62 | 63 | /** 64 | * Initializes the module. Caches a data store object to todos 65 | * @returns {void} 66 | */ 67 | init: function() { 68 | todosDB = context.getService('todos-db'); 69 | moduleEl = context.getElement(); 70 | }, 71 | 72 | /** 73 | * Destroys the module. 74 | * @returns {void} 75 | */ 76 | destroy: function() { 77 | todosDB = null; 78 | moduleEl = null; 79 | }, 80 | 81 | /** 82 | * Handles all click events for the behavior. 83 | * @param {Event} event A DOM-normalized event object. 84 | * @param {HTMLElement} element The nearest HTML element with a data-type 85 | * attribute specified or null if there is none. 86 | * @param {string} elementType The value of data-type for the nearest 87 | * element with that attribute specified or null if there is none. 88 | * @returns {void} 89 | */ 90 | onclick: function(event, element, elementType) { 91 | 92 | if (elementType === 'delete-btn') { 93 | var todoEl = getClosestTodoElement(element), 94 | todoId = getClosestTodoId(element); 95 | 96 | moduleEl.querySelector('#todo-list').removeChild(todoEl); 97 | todosDB.remove(todoId); 98 | 99 | context.broadcast('todoremoved', { 100 | id: todoId 101 | }); 102 | 103 | } 104 | 105 | }, 106 | 107 | /** 108 | * Handles change events for the behavior. 109 | * @param {Event} event A DOM-normalized event object. 110 | * @param {HTMLElement} element The nearest HTML element with a data-type 111 | * attribute specified or null if there is none. 112 | * @param {string} elementType The value of data-type for the nearest 113 | * element with that attribute specified or null if there is none. 114 | * @returns {void} 115 | */ 116 | onchange: function(event, element, elementType) { 117 | 118 | if (elementType === 'mark-as-complete-checkbox') { 119 | var todoEl = getClosestTodoElement(element), 120 | todoId = getClosestTodoId(element); 121 | 122 | if (element.checked) { 123 | todoEl.classList.add('completed'); 124 | todosDB.markAsComplete(todoId); 125 | } else { 126 | todoEl.classList.remove('completed'); 127 | todosDB.markAsIncomplete(todoId); 128 | } 129 | 130 | context.broadcast('todostatuschange'); 131 | 132 | } 133 | 134 | }, 135 | 136 | /** 137 | * Handles double click events for the behavior. 138 | * @param {Event} event A DOM-normalized event object. 139 | * @param {HTMLElement} element The nearest HTML element with a data-type 140 | * attribute specified or null if there is none. 141 | * @param {string} elementType The value of data-type for the nearest 142 | * element with that attribute specified or null if there is none. 143 | * @returns {void} 144 | */ 145 | ondblclick: function(event, element, elementType) { 146 | 147 | if (elementType === 'todo-label') { 148 | var todoEl = getClosestTodoElement(element); 149 | 150 | event.preventDefault(); 151 | event.stopPropagation(); 152 | 153 | this.showEditor(todoEl); 154 | } 155 | 156 | }, 157 | 158 | /** 159 | * Handles keydown events for the behavior. 160 | * @param {Event} event A DOM-normalized event object. 161 | * @param {HTMLElement} element The nearest HTML element with a data-type 162 | * attribute specified or null if there is none. 163 | * @param {string} elementType The value of data-type for the nearest 164 | * element with that attribute specified or null if there is none. 165 | * @returns {void} 166 | */ 167 | onkeydown: function(event, element, elementType) { 168 | 169 | if (elementType === 'edit-input') { 170 | var todoEl = getClosestTodoElement(element); 171 | 172 | if (event.keyCode === ENTER_KEY) { 173 | this.saveLabel(todoEl); 174 | this.hideEditor(todoEl); 175 | } else if (event.keyCode === ESCAPE_KEY) { 176 | this.hideEditor(todoEl); 177 | } 178 | 179 | } 180 | 181 | }, 182 | 183 | /** 184 | * Displays a input box for the user to edit the label with 185 | * @param {HTMLElement} todoEl The todo element to edit 186 | * @returns {void} 187 | */ 188 | showEditor: function(todoEl) { 189 | var todoId = getClosestTodoId(todoEl), 190 | editInputEl = todoEl.querySelector('.edit'), 191 | title = todosDB.get(todoId).title; // Grab current label 192 | 193 | // Set the edit input value to current label 194 | editInputEl.value = title; 195 | 196 | todoEl.classList.add('editing'); 197 | 198 | // Place user cursor in the input 199 | editInputEl.focus(); 200 | }, 201 | 202 | /** 203 | * Hides the edit input for a given todo 204 | * @param {HTMLElement} todoEl The todo element 205 | * @returns {void} 206 | */ 207 | hideEditor: function(todoEl) { 208 | todoEl.classList.remove('editing'); 209 | }, 210 | 211 | /** 212 | * Saves the value of the edit input and saves it to the db 213 | * @param {HTMLElement} todoEl The todo element to edit 214 | * @returns {void} 215 | */ 216 | saveLabel: function(todoEl) { 217 | var todoId = getClosestTodoId(todoEl), 218 | editInputEl = todoEl.querySelector('.edit'), 219 | newTitle = (editInputEl.value).trim(); 220 | 221 | todoEl.querySelector('label').textContent = newTitle; 222 | todosDB.edit(todoId, newTitle); 223 | } 224 | }; 225 | 226 | }); 227 | -------------------------------------------------------------------------------- /dist/t3.min.js: -------------------------------------------------------------------------------- 1 | /*! t3 v2.7.0 */ 2 | /*! 3 | Copyright 2016 Box, Inc. All rights reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 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 | !function(e){var t={};if(t.EventTarget=function(){"use strict";function e(){this._handlers={}}return e.prototype={constructor:e,on:function(e,t){var n,r,i=this._handlers[e];for("undefined"==typeof i&&(i=this._handlers[e]=[]),n=0,r=i.length;r>n;n++)if(i[n]===t)return;i.push(t)},fire:function(e,t){var n,r,i,o={type:e,data:t};if(n=this._handlers[o.type],n instanceof Array)for(n=n.concat(),r=0,i=n.length;i>r;r++)n[r].call(this,o)},off:function(e,t){var n,r,i=this._handlers[e];if(i instanceof Array)for(n=0,r=i.length;r>n;n++)if(i[n]===t){i.splice(n,1);break}}},e}(),t.NativeDOM=function(){"use strict";return{type:"native",query:function(e,t){return e.querySelector(t)},queryAll:function(e,t){return e.querySelectorAll(t)},on:function(e,t,n){e.addEventListener(t,n,!1)},off:function(e,t,n){e.removeEventListener(t,n,!1)}}}(),t.DOM=t.NativeDOM,t.DOMEventDelegate=function(){"use strict";function e(e){return e&&e.hasAttribute("data-module")}function n(e){return e&&e.hasAttribute("data-type")}function r(t){for(var r=!1,i=!1;!r&&t&&t.parentNode&&!i;)r=n(t),i=e(t),r||(t=t.parentNode);return r?t:null}function i(e,t,n,r){var i,o;for(i=0;in;n++)if(e[n]===t)return n;return-1}function i(){y={},b={},E={},w=[],_={},D={},A=!1}function o(e){for(var t=0,n=w.length;n>t;t++)if(w[t]===e)return!0;return!1}function a(e){if("function"==typeof M)return void M(e);if(y.debug)throw e;O.fire("error",{exception:e})}function u(e,t){var n,r;for(n in e)r=e[n],"function"==typeof r&&(e[n]=function(e,n){return function(){var r=t+"."+e+"() - ";try{return n.apply(this,arguments)}catch(i){i.methodName=e,i.objectName=t,i.name=r+i.name,i.message=r+i.message,a(i)}}}(n,r))}function s(e){var t=e.getAttribute("data-module");return t?t.split(" ")[0]:""}function c(e,t){"function"==typeof e[t]&&e[t].apply(e,Array.prototype.slice.call(arguments,2))}function f(e){var t=E[e];if(t){if(!t.instance){if(o(e))return a(new ReferenceError("Circular service dependency: "+w.join(" -> ")+" -> "+e)),null;w.push(e),t.instance=t.creator(O),w.pop()}return t.instance}return a(new Error('Service "'+e+'" not found')),null}function l(e){var t,n,r,i,o,u=[],s={};for(n=e.instance.behaviors||[],t=0;tv;v++)h=p[v],c(h,"init");c(n.instance,"init"),d(n)}return this},stop:function(e){var t=v(e);if(t){p(t);for(var n,r=l(t),i=r.length-1;i>=0;i--)n=r[i],c(n,"destroy");c(t.instance,"destroy"),delete D[e.id]}else if(y.debug)return a(new Error("Unable to stop module associated with element: "+e.id)),this;return this},startAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.start(n[r]);return this},stopAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.stop(n[r]);return this},addModule:function(e,t){return"undefined"!=typeof b[e]?(a(new Error("Module "+e+" has already been added.")),this):(b[e]={creator:t,counter:1},this)},getModuleConfig:function(e,n){var r=v(e),i=null;if(r&&r.config)i=r.config;else{var o=t.DOM.query(e,'script[type="text/x-config"]');if(o)try{i=JSON.parse(o.text)}catch(u){a(new Error("Module with id "+e.id+" has a malformed config."))}r&&(r.config=i)}return i?"undefined"==typeof n?i:n in i?i[n]:null:null},addService:function(e,t){return"undefined"!=typeof E[e]?(a(new Error("Service "+e+" has already been added.")),this):(E[e]={creator:t,instance:null},this)},getService:f,hasService:function(e){return E.hasOwnProperty(e)},addBehavior:function(e,t){return"undefined"!=typeof _[e]?(a(new Error("Behavior "+e+" has already been added.")),this):(_[e]={creator:t,instance:null},this)},broadcast:function(e,t){var n,r,i,o,a;for(r in D)if(D.hasOwnProperty(r))for(i=D[r],g(i.instance,e,t),a=l(i),n=0;nn;n++)if(i[n]===t)return;i.push(t)},fire:function(e,t){var n,r,i,o={type:e,data:t};if(n=this._handlers[o.type],n instanceof Array)for(n=n.concat(),r=0,i=n.length;i>r;r++)n[r].call(this,o)},off:function(e,t){var n,r,i=this._handlers[e];if(i instanceof Array)for(n=0,r=i.length;r>n;n++)if(i[n]===t){i.splice(n,1);break}}},e}(),t.NativeDOM=function(){"use strict";return{type:"native",query:function(e,t){return e.querySelector(t)},queryAll:function(e,t){return e.querySelectorAll(t)},on:function(e,t,n){e.addEventListener(t,n,!1)},off:function(e,t,n){e.removeEventListener(t,n,!1)}}}(),t.DOM=t.NativeDOM,t.DOMEventDelegate=function(){"use strict";function e(e){return e&&e.hasAttribute("data-module")}function n(e){return e&&e.hasAttribute("data-type")}function r(t){for(var r=!1,i=!1;!r&&t&&t.parentNode&&!i;)r=n(t),i=e(t),r||(t=t.parentNode);return r?t:null}function i(e,t,n,r){var i,o;for(i=0;in;n++)if(e[n]===t)return n;return-1}function i(){y={},b={},E={},w=[],_={},D={},A=!1}function o(e){for(var t=0,n=w.length;n>t;t++)if(w[t]===e)return!0;return!1}function a(e){if("function"==typeof M)return void M(e);if(y.debug)throw e;O.fire("error",{exception:e})}function u(e,t){var n,r;for(n in e)r=e[n],"function"==typeof r&&(e[n]=function(e,n){return function(){var r=t+"."+e+"() - ";try{return n.apply(this,arguments)}catch(i){i.methodName=e,i.objectName=t,i.name=r+i.name,i.message=r+i.message,a(i)}}}(n,r))}function s(e){var t=e.getAttribute("data-module");return t?t.split(" ")[0]:""}function c(e,t){"function"==typeof e[t]&&e[t].apply(e,Array.prototype.slice.call(arguments,2))}function f(e){var t=E[e];if(t){if(!t.instance){if(o(e))return a(new ReferenceError("Circular service dependency: "+w.join(" -> ")+" -> "+e)),null;w.push(e),t.instance=t.creator(O),w.pop()}return t.instance}return a(new Error('Service "'+e+'" not found')),null}function l(e){var t,n,r,i,o,u=[],s={};for(n=e.instance.behaviors||[],t=0;tv;v++)h=p[v],c(h,"init");c(n.instance,"init"),d(n)}return this},stop:function(e){var t=v(e);if(t){p(t);for(var n,r=l(t),i=r.length-1;i>=0;i--)n=r[i],c(n,"destroy");c(t.instance,"destroy"),delete D[e.id]}else if(y.debug)return a(new Error("Unable to stop module associated with element: "+e.id)),this;return this},startAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.start(n[r]);return this},stopAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.stop(n[r]);return this},addModule:function(e,t){return"undefined"!=typeof b[e]?(a(new Error("Module "+e+" has already been added.")),this):(b[e]={creator:t,counter:1},this)},getModuleConfig:function(e,n){var r=v(e),i=null;if(r&&r.config)i=r.config;else{var o=t.DOM.query(e,'script[type="text/x-config"]');if(o)try{i=JSON.parse(o.text)}catch(u){a(new Error("Module with id "+e.id+" has a malformed config."))}r&&(r.config=i)}return i?"undefined"==typeof n?i:n in i?i[n]:null:null},addService:function(e,t){return"undefined"!=typeof E[e]?(a(new Error("Service "+e+" has already been added.")),this):(E[e]={creator:t,instance:null},this)},getService:f,hasService:function(e){return E.hasOwnProperty(e)},addBehavior:function(e,t){return"undefined"!=typeof _[e]?(a(new Error("Behavior "+e+" has already been added.")),this):(_[e]={creator:t,instance:null},this)},broadcast:function(e,t){var n,r,i,o,a;for(r in D)if(D.hasOwnProperty(r))for(i=D[r],g(i.instance,e,t),a=l(i),n=0;nn;n++)if(i[n]===t)return;i.push(t)},fire:function(e,t){var n,r,i,o={type:e,data:t};if(n=this._handlers[o.type],n instanceof Array)for(n=n.concat(),r=0,i=n.length;i>r;r++)n[r].call(this,o)},off:function(e,t){var n,r,i=this._handlers[e];if(i instanceof Array)for(n=0,r=i.length;r>n;n++)if(i[n]===t){i.splice(n,1);break}}},e}(),t.JQueryDOM=function(){"use strict";return{type:"jquery",query:function(e,t){return jQuery(e).find(t)[0]||null},queryAll:function(e,t){return jQuery.makeArray(jQuery(e).find(t))},on:function(e,t,n){jQuery(e).on(t,n)},off:function(e,t,n){jQuery(e).off(t,n)}}}(),t.DOM=t.JQueryDOM,t.DOMEventDelegate=function(){"use strict";function e(e){return e&&e.hasAttribute("data-module")}function n(e){return e&&e.hasAttribute("data-type")}function r(t){for(var r=!1,i=!1;!r&&t&&t.parentNode&&!i;)r=n(t),i=e(t),r||(t=t.parentNode);return r?t:null}function i(e,t,n,r){var i,o;for(i=0;in;n++)if(e[n]===t)return n;return-1}function i(){m={},b={},E={},w=[],_={},D={},A=!1}function o(e){for(var t=0,n=w.length;n>t;t++)if(w[t]===e)return!0;return!1}function a(e){if("function"==typeof M)return void M(e);if(m.debug)throw e;O.fire("error",{exception:e})}function u(e,t){var n,r;for(n in e)r=e[n],"function"==typeof r&&(e[n]=function(e,n){return function(){var r=t+"."+e+"() - ";try{return n.apply(this,arguments)}catch(i){i.methodName=e,i.objectName=t,i.name=r+i.name,i.message=r+i.message,a(i)}}}(n,r))}function s(e){var t=e.getAttribute("data-module");return t?t.split(" ")[0]:""}function c(e,t){"function"==typeof e[t]&&e[t].apply(e,Array.prototype.slice.call(arguments,2))}function f(e){var t=E[e];if(t){if(!t.instance){if(o(e))return a(new ReferenceError("Circular service dependency: "+w.join(" -> ")+" -> "+e)),null;w.push(e),t.instance=t.creator(O),w.pop()}return t.instance}return a(new Error('Service "'+e+'" not found')),null}function l(e){var t,n,r,i,o,u=[],s={};for(n=e.instance.behaviors||[],t=0;tg;g++)h=p[g],c(h,"init");c(n.instance,"init"),d(n)}return this},stop:function(e){var t=g(e);if(t){p(t);for(var n,r=l(t),i=r.length-1;i>=0;i--)n=r[i],c(n,"destroy");c(t.instance,"destroy"),delete D[e.id]}else if(m.debug)return a(new Error("Unable to stop module associated with element: "+e.id)),this;return this},startAll:function(e){for(var n=t.DOM.queryAll(e,y),r=0,i=n.length;i>r;r++)this.start(n[r]);return this},stopAll:function(e){for(var n=t.DOM.queryAll(e,y),r=0,i=n.length;i>r;r++)this.stop(n[r]);return this},addModule:function(e,t){return"undefined"!=typeof b[e]?(a(new Error("Module "+e+" has already been added.")),this):(b[e]={creator:t,counter:1},this)},getModuleConfig:function(e,n){var r=g(e),i=null;if(r&&r.config)i=r.config;else{var o=t.DOM.query(e,'script[type="text/x-config"]');if(o)try{i=JSON.parse(o.text)}catch(u){a(new Error("Module with id "+e.id+" has a malformed config."))}r&&(r.config=i)}return i?"undefined"==typeof n?i:n in i?i[n]:null:null},addService:function(e,t){return"undefined"!=typeof E[e]?(a(new Error("Service "+e+" has already been added.")),this):(E[e]={creator:t,instance:null},this)},getService:f,hasService:function(e){return E.hasOwnProperty(e)},addBehavior:function(e,t){return"undefined"!=typeof _[e]?(a(new Error("Behavior "+e+" has already been added.")),this):(_[e]={creator:t,instance:null},this)},broadcast:function(e,t){var n,r,i,o,a;for(r in D)if(D.hasOwnProperty(r))for(i=D[r],v(i.instance,e,t),a=l(i),n=0;nn;n++)if(i[n]===t)return;i.push(t)},fire:function(e,t){var n,r,i,o={type:e,data:t};if(n=this._handlers[o.type],n instanceof Array)for(n=n.concat(),r=0,i=n.length;i>r;r++)n[r].call(this,o)},off:function(e,t){var n,r,i=this._handlers[e];if(i instanceof Array)for(n=0,r=i.length;r>n;n++)if(i[n]===t){i.splice(n,1);break}}},e}(),t.NativeDOM=function(){"use strict";return{type:"native",query:function(e,t){return e.querySelector(t)},queryAll:function(e,t){return e.querySelectorAll(t)},on:function(e,t,n){e.addEventListener(t,n,!1)},off:function(e,t,n){e.removeEventListener(t,n,!1)}}}(),t.DOM=t.NativeDOM,t.DOMEventDelegate=function(){"use strict";function e(e){return e&&e.hasAttribute("data-module")}function n(e){return e&&e.hasAttribute("data-type")}function r(t){for(var r=!1,i=!1;!r&&t&&t.parentNode&&!i;)r=n(t),i=e(t),r||(t=t.parentNode);return r?t:null}function i(e,t,n,r){var i,o;for(i=0;in;n++)if(e[n]===t)return n;return-1}function i(){y={},b={},E={},w=[],_={},D={},A=!1}function o(e){for(var t=0,n=w.length;n>t;t++)if(w[t]===e)return!0;return!1}function a(e){if("function"==typeof M)return void M(e);if(y.debug)throw e;O.fire("error",{exception:e})}function u(e,t){var n,r;for(n in e)r=e[n],"function"==typeof r&&(e[n]=function(e,n){return function(){var r=t+"."+e+"() - ";try{return n.apply(this,arguments)}catch(i){i.methodName=e,i.objectName=t,i.name=r+i.name,i.message=r+i.message,a(i)}}}(n,r))}function s(e){var t=e.getAttribute("data-module");return t?t.split(" ")[0]:""}function c(e,t){"function"==typeof e[t]&&e[t].apply(e,Array.prototype.slice.call(arguments,2))}function f(e){var t=E[e];if(t){if(!t.instance){if(o(e))return a(new ReferenceError("Circular service dependency: "+w.join(" -> ")+" -> "+e)),null;w.push(e),t.instance=t.creator(O),w.pop()}return t.instance}return a(new Error('Service "'+e+'" not found')),null}function l(e){var t,n,r,i,o,u=[],s={};for(n=e.instance.behaviors||[],t=0;tv;v++)h=p[v],c(h,"init");c(n.instance,"init"),d(n)}return this},stop:function(e){var t=v(e);if(t){p(t);for(var n,r=l(t),i=r.length-1;i>=0;i--)n=r[i],c(n,"destroy");c(t.instance,"destroy"),delete D[e.id]}else if(y.debug)return a(new Error("Unable to stop module associated with element: "+e.id)),this;return this},startAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.start(n[r]);return this},stopAll:function(e){for(var n=t.DOM.queryAll(e,m),r=0,i=n.length;i>r;r++)this.stop(n[r]);return this},addModule:function(e,t){return"undefined"!=typeof b[e]?(a(new Error("Module "+e+" has already been added.")),this):(b[e]={creator:t,counter:1},this)},getModuleConfig:function(e,n){var r=v(e),i=null;if(r&&r.config)i=r.config;else{var o=t.DOM.query(e,'script[type="text/x-config"]');if(o)try{i=JSON.parse(o.text)}catch(u){a(new Error("Module with id "+e.id+" has a malformed config."))}r&&(r.config=i)}return i?"undefined"==typeof n?i:n in i?i[n]:null:null},addService:function(e,t){return"undefined"!=typeof E[e]?(a(new Error("Service "+e+" has already been added.")),this):(E[e]={creator:t,instance:null},this)},getService:f,hasService:function(e){return E.hasOwnProperty(e)},addBehavior:function(e,t){return"undefined"!=typeof _[e]?(a(new Error("Behavior "+e+" has already been added.")),this):(_[e]={creator:t,instance:null},this)},broadcast:function(e,t){var n,r,i,o,a;for(r in D)if(D.hasOwnProperty(r))for(i=D[r],g(i.instance,e,t),a=l(i),n=0;nn;n++)if(i[n]===t)return;i.push(t)},fire:function(e,t){var n,r,i,o={type:e,data:t};if(n=this._handlers[o.type],n instanceof Array)for(n=n.concat(),r=0,i=n.length;i>r;r++)n[r].call(this,o)},off:function(e,t){var n,r,i=this._handlers[e];if(i instanceof Array)for(n=0,r=i.length;r>n;n++)if(i[n]===t){i.splice(n,1);break}}},e}(),t.JQueryDOM=function(){"use strict";return{type:"jquery",query:function(e,t){return jQuery(e).find(t)[0]||null},queryAll:function(e,t){return jQuery.makeArray(jQuery(e).find(t))},on:function(e,t,n){jQuery(e).on(t,n)},off:function(e,t,n){jQuery(e).off(t,n)}}}(),t.DOM=t.JQueryDOM,t.DOMEventDelegate=function(){"use strict";function e(e){return e&&e.hasAttribute("data-module")}function n(e){return e&&e.hasAttribute("data-type")}function r(t){for(var r=!1,i=!1;!r&&t&&t.parentNode&&!i;)r=n(t),i=e(t),r||(t=t.parentNode);return r?t:null}function i(e,t,n,r){var i,o;for(i=0;in;n++)if(e[n]===t)return n;return-1}function i(){m={},b={},E={},w=[],_={},D={},A=!1}function o(e){for(var t=0,n=w.length;n>t;t++)if(w[t]===e)return!0;return!1}function a(e){if("function"==typeof M)return void M(e);if(m.debug)throw e;O.fire("error",{exception:e})}function u(e,t){var n,r;for(n in e)r=e[n],"function"==typeof r&&(e[n]=function(e,n){return function(){var r=t+"."+e+"() - ";try{return n.apply(this,arguments)}catch(i){i.methodName=e,i.objectName=t,i.name=r+i.name,i.message=r+i.message,a(i)}}}(n,r))}function s(e){var t=e.getAttribute("data-module");return t?t.split(" ")[0]:""}function c(e,t){"function"==typeof e[t]&&e[t].apply(e,Array.prototype.slice.call(arguments,2))}function f(e){var t=E[e];if(t){if(!t.instance){if(o(e))return a(new ReferenceError("Circular service dependency: "+w.join(" -> ")+" -> "+e)),null;w.push(e),t.instance=t.creator(O),w.pop()}return t.instance}return a(new Error('Service "'+e+'" not found')),null}function l(e){var t,n,r,i,o,u=[],s={};for(n=e.instance.behaviors||[],t=0;tg;g++)h=p[g],c(h,"init");c(n.instance,"init"),d(n)}return this},stop:function(e){var t=g(e);if(t){p(t);for(var n,r=l(t),i=r.length-1;i>=0;i--)n=r[i],c(n,"destroy");c(t.instance,"destroy"),delete D[e.id]}else if(m.debug)return a(new Error("Unable to stop module associated with element: "+e.id)),this;return this},startAll:function(e){for(var n=t.DOM.queryAll(e,y),r=0,i=n.length;i>r;r++)this.start(n[r]);return this},stopAll:function(e){for(var n=t.DOM.queryAll(e,y),r=0,i=n.length;i>r;r++)this.stop(n[r]);return this},addModule:function(e,t){return"undefined"!=typeof b[e]?(a(new Error("Module "+e+" has already been added.")),this):(b[e]={creator:t,counter:1},this)},getModuleConfig:function(e,n){var r=g(e),i=null;if(r&&r.config)i=r.config;else{var o=t.DOM.query(e,'script[type="text/x-config"]');if(o)try{i=JSON.parse(o.text)}catch(u){a(new Error("Module with id "+e.id+" has a malformed config."))}r&&(r.config=i)}return i?"undefined"==typeof n?i:n in i?i[n]:null:null},addService:function(e,t){return"undefined"!=typeof E[e]?(a(new Error("Service "+e+" has already been added.")),this):(E[e]={creator:t,instance:null},this)},getService:f,hasService:function(e){return E.hasOwnProperty(e)},addBehavior:function(e,t){return"undefined"!=typeof _[e]?(a(new Error("Behavior "+e+" has already been added.")),this):(_[e]={creator:t,instance:null},this)},broadcast:function(e,t){var n,r,i,o,a;for(r in D)if(D.hasOwnProperty(r))for(i=D[r],v(i.instance,e,t),a=l(i),n=0;n 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ``` 61 | 62 | You may also use `bower` to install t3js: 63 | 64 | ``` 65 | bower install t3js 66 | ``` 67 | 68 | ## Upgrade from 1.5.1 to 2.0.0 69 | 70 | T3 2.0.0 was released on November 18th, 2015 with some major changes. To upgrade, please see [these instructions](http://t3js.org/docs/getting-started/migrating-to-2-0-0). 71 | 72 | ## Getting Started 73 | 74 | Your T3 front-end is made up of modules, so the first step is to indicate which modules are responsible for which parts of the page. You can do that by using the `data-module` attribute and specifying the module ID, such as: 75 | 76 | ```html 77 |
78 |

Box

79 | 80 |
81 | ``` 82 | 83 | This example specifies the module `header` should manage this particular part of the page. The module `header` is then defined as: 84 | 85 | ```js 86 | Box.Application.addModule('header', function(context) { 87 | 88 | return { 89 | 90 | onclick: function(event, element, elementType) { 91 | if (elementType === 'welcome-btn') { 92 | alert('Welcome, T3 user!'); 93 | } else { 94 | alert('You clicked outside the button.'); 95 | } 96 | } 97 | 98 | }; 99 | 100 | }); 101 | ``` 102 | 103 | This is a very simple module that has an `onclick` handler. T3 automatically wires up specified event handlers so you don't have to worry about using event delegation or removing event handlers when they are no longer needed. The `onclick` handler receives a DOM-normalized event object that can be used to get event details. When the button is clicked, a message is displayed. Additionally, clicking anywhere inside the module will display a different message. Event handlers are tied to the entire module area, so there's no need to attach multiple handlers of the same type. 104 | 105 | The last step is to initialize the T3 application: 106 | 107 | ```js 108 | Box.Application.init(); 109 | ``` 110 | 111 | This call starts all modules on the page (be sure to include both the T3 library and your module code before calling `init()`). We recommend calling `init()` as soon as possible after your JavaScript is loaded. Whether you do that `onload`, earlier, or later, is completely up to you. 112 | 113 | There are more extensive tutorials and examples on our [website](http://t3js.org). 114 | 115 | ## Browser Support 116 | 117 | T3 is tested and known to work in the following browsers: 118 | 119 | * Internet Explorer 9 and higher 120 | * Firefox (latest version) 121 | * Chrome (latest version) 122 | * Safari (latest version) 123 | 124 | With the exception of Internet Explorer, T3 will continue to support the current and previous one version of all major browsers. 125 | 126 | ## Contributing 127 | 128 | The main purpose of sharing T3 is to continue its development, making it faster, more efficient, and easier to use. 129 | 130 | ### Directory Structure 131 | 132 | * `config` - configuration files for the project 133 | * `dist` - browser bundles (this directory is updated automatically with each release) 134 | * `lib` - the source code as individual files 135 | * `tests` - the test code 136 | 137 | ### Prerequisites 138 | 139 | In order to get started contributing to T3, you'll need to be familiar and have installed: 140 | 141 | 1. [Git](http://git-scm.com/) 142 | 1. [npm](https://npmjs.org) 143 | 1. [Node.js](https://nodejs.org) or [IO.js](https://iojs.org) 144 | 145 | ### Setup 146 | 147 | Following the instructions in the [contributor guidelines](CONTRIBUTING.md) to setup a local copy of the T3 repository. 148 | 149 | Once you clone the T3 git repository, run the following inside the `t3js` directory: 150 | 151 | ``` 152 | $ npm i 153 | ``` 154 | 155 | This sets up all the dependencies that the T3 build system needs to function. 156 | 157 | **Note:** You'll need to do this periodically when pulling the latest code from our repository as dependencies might change. If you find unexpected errors, be sure to run `npm i` again to ensure your dependencies are up-to-date. 158 | 159 | ### Running Tests 160 | 161 | After that, you can run all tests by running: 162 | 163 | ``` 164 | $ npm test 165 | ``` 166 | 167 | This will start by linting the code and then running all unit tests. 168 | 169 | ### Build Commands 170 | 171 | The following build commands are available: 172 | 173 | 1. `npm test` - runs all linting and uni tests 174 | 1. `npm run lint` - runs all linting 175 | 1. `npm run dist` - creates the browser bundles and places them in `/dist` 176 | 177 | ## Frequently Asked Questions 178 | 179 | ### Can I use this with older browsers? 180 | 181 | We provide a custom version of T3 built with jQuery that is compatible with older browsers such as IE8. 182 | 183 | ### Why support IE8? 184 | 185 | The Box web application currently supports IE8 with a [planned end-of-life of December 31, 2015](https://support.box.com/hc/en-us/articles/200519838-What-Is-the-Box-Policy-for-Browser-and-OS-Support-). We will be dropping T3 support for IE8 at the same time. 186 | 187 | ## Support 188 | 189 | Need to contact us directly? Email [t3js@box.com](mailto:t3js@box.com) with your questions or comments. 190 | 191 | ## Copyright and License 192 | 193 | Copyright 2015 Box, Inc. All rights reserved. 194 | 195 | Licensed under the Apache License, Version 2.0 (the "License"); 196 | you may not use this file except in compliance with the License. 197 | You may obtain a copy of the License at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 204 | See the License for the specific language governing permissions and 205 | limitations under the License. 206 | 207 | [npm-image]: https://img.shields.io/npm/v/t3js.svg?style=flat-square 208 | [npm-url]: https://npmjs.org/package/t3js 209 | [travis-image]: https://img.shields.io/travis/box/t3js/master.svg?style=flat-square 210 | [travis-url]: https://travis-ci.org/box/t3js 211 | -------------------------------------------------------------------------------- /dist/t3.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"t3.min.js.map","sources":["./dist/t3.js"],"names":["window","Box","EventTarget","this","_handlers","prototype","constructor","on","type","handler","i","len","handlers","length","push","fire","data","event","Array","concat","call","off","splice","NativeDOM","query","root","selector","querySelector","queryAll","querySelectorAll","element","listener","addEventListener","removeEventListener","DOM","DOMEventDelegate","isModuleElement","hasAttribute","isTypeElement","getNearestTypeElement","found","moduleBoundaryReached","parentNode","forEachEventType","eventTypes","callback","thisValue","_handler","_eventTypes","DEFAULT_EVENT_TYPES","_boundHandler","_attached","_handleEvent","targetElement","target","elementType","getAttribute","attachEvents","eventType","handleEvent","that","apply","arguments","detachEvents","Context","application","broadcast","name","getService","serviceName","hasService","getConfig","getModuleConfig","getGlobal","getGlobalConfig","reportError","exception","getElement","Application","assign","receiver","supplier","prop","hasOwnProperty","indexOf","items","item","reset","globalConfig","modules","services","serviceStack","behaviors","instances","initialized","isServiceBeingInstantiated","error","customErrorHandler","debug","captureObjectErrors","object","objectName","propertyName","propertyValue","methodName","method","errorPrefix","ex","message","getModuleName","moduleAttribute","split","callModuleMethod","instance","slice","serviceData","ReferenceError","join","creator","pop","Error","getBehaviors","instanceData","behaviorNames","behaviorData","moduleBehaviorInstances","behaviorName","behaviorInstances","includedBehaviors","context","createAndBindEventDelegate","eventDelegates","delegate","bindEventListeners","moduleBehaviors","unbindEventListeners","getInstanceDataByElement","id","callMessageHandler","onmessage","messages","MODULE_SELECTOR","init","params","startAll","document","documentElement","destroy","stopAll","isStarted","start","module","moduleName","moduleData","counter","behaviorInstance","stop","moduleElements","addModule","moduleConfig","config","configElement","JSON","parse","text","addService","addBehavior","messageData","setGlobalConfig","setErrorHandler","exceptionHandler","reportWarning","globalConsole","warn","reportInfo","info","define","amd","exports","key"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/t3-jquery.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"t3-jquery.min.js.map","sources":["./dist/t3-jquery.js"],"names":["window","Box","EventTarget","this","_handlers","prototype","constructor","on","type","handler","i","len","handlers","length","push","fire","data","event","Array","concat","call","off","splice","JQueryDOM","query","root","selector","jQuery","find","queryAll","makeArray","element","listener","DOM","DOMEventDelegate","isModuleElement","hasAttribute","isTypeElement","getNearestTypeElement","found","moduleBoundaryReached","parentNode","forEachEventType","eventTypes","callback","thisValue","_handler","_eventTypes","DEFAULT_EVENT_TYPES","_boundHandler","_attached","_handleEvent","targetElement","target","elementType","getAttribute","attachEvents","eventType","handleEvent","that","apply","arguments","detachEvents","Context","application","broadcast","name","getService","serviceName","hasService","getConfig","getModuleConfig","getGlobal","getGlobalConfig","reportError","exception","getElement","Application","assign","receiver","supplier","prop","hasOwnProperty","indexOf","items","item","reset","globalConfig","modules","services","serviceStack","behaviors","instances","initialized","isServiceBeingInstantiated","error","customErrorHandler","debug","captureObjectErrors","object","objectName","propertyName","propertyValue","methodName","method","errorPrefix","ex","message","getModuleName","moduleAttribute","split","callModuleMethod","instance","slice","serviceData","ReferenceError","join","creator","pop","Error","getBehaviors","instanceData","behaviorNames","behaviorData","moduleBehaviorInstances","behaviorName","behaviorInstances","includedBehaviors","context","createAndBindEventDelegate","eventDelegates","delegate","bindEventListeners","moduleBehaviors","unbindEventListeners","getInstanceDataByElement","id","callMessageHandler","onmessage","messages","MODULE_SELECTOR","init","params","startAll","document","documentElement","destroy","stopAll","isStarted","start","module","moduleName","moduleData","counter","behaviorInstance","stop","moduleElements","addModule","moduleConfig","config","configElement","JSON","parse","text","addService","addBehavior","messageData","setGlobalConfig","setErrorHandler","exceptionHandler","reportWarning","globalConsole","warn","reportInfo","info","define","amd","exports","key"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/t3-native.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"t3-native.min.js.map","sources":["./dist/t3-native.js"],"names":["window","Box","EventTarget","this","_handlers","prototype","constructor","on","type","handler","i","len","handlers","length","push","fire","data","event","Array","concat","call","off","splice","NativeDOM","query","root","selector","querySelector","queryAll","querySelectorAll","element","listener","addEventListener","removeEventListener","DOM","DOMEventDelegate","isModuleElement","hasAttribute","isTypeElement","getNearestTypeElement","found","moduleBoundaryReached","parentNode","forEachEventType","eventTypes","callback","thisValue","_handler","_eventTypes","DEFAULT_EVENT_TYPES","_boundHandler","_attached","_handleEvent","targetElement","target","elementType","getAttribute","attachEvents","eventType","handleEvent","that","apply","arguments","detachEvents","Context","application","broadcast","name","getService","serviceName","hasService","getConfig","getModuleConfig","getGlobal","getGlobalConfig","reportError","exception","getElement","Application","assign","receiver","supplier","prop","hasOwnProperty","indexOf","items","item","reset","globalConfig","modules","services","serviceStack","behaviors","instances","initialized","isServiceBeingInstantiated","error","customErrorHandler","debug","captureObjectErrors","object","objectName","propertyName","propertyValue","methodName","method","errorPrefix","ex","message","getModuleName","moduleAttribute","split","callModuleMethod","instance","slice","serviceData","ReferenceError","join","creator","pop","Error","getBehaviors","instanceData","behaviorNames","behaviorData","moduleBehaviorInstances","behaviorName","behaviorInstances","includedBehaviors","context","createAndBindEventDelegate","eventDelegates","delegate","bindEventListeners","moduleBehaviors","unbindEventListeners","getInstanceDataByElement","id","callMessageHandler","onmessage","messages","MODULE_SELECTOR","init","params","startAll","document","documentElement","destroy","stopAll","isStarted","start","module","moduleName","moduleData","counter","behaviorInstance","stop","moduleElements","addModule","moduleConfig","config","configElement","JSON","parse","text","addService","addBehavior","messageData","setGlobalConfig","setErrorHandler","exceptionHandler","reportWarning","globalConsole","warn","reportInfo","info","define","amd","exports","key"],"mappings":"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"} -------------------------------------------------------------------------------- /Makefile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Build file 3 | * @author nzakas 4 | */ 5 | /*global config, target, exec, echo, find, which, test, exit, mkdir*/ 6 | 7 | 'use strict'; 8 | 9 | //------------------------------------------------------------------------------ 10 | // Requirements 11 | //------------------------------------------------------------------------------ 12 | 13 | require('shelljs/make'); 14 | 15 | var util = require('util'), 16 | path = require('path'), 17 | nodeCLI = require('shelljs-nodecli'), 18 | semver = require('semver'), 19 | dateformat = require('dateformat'), 20 | uglifyjs = require('uglify-js'); 21 | 22 | //------------------------------------------------------------------------------ 23 | // Data 24 | //------------------------------------------------------------------------------ 25 | 26 | var NODE = 'node ', // intentional extra space 27 | NODE_MODULES = './node_modules/', 28 | DIST_DIR = './dist/', 29 | 30 | // Utilities - intentional extra space at the end of each string 31 | JSDOC = NODE + NODE_MODULES + 'jsdoc/jsdoc.js ', 32 | 33 | // Since our npm package name is actually 't3js' 34 | DIST_NAME = 't3', 35 | DIST_JQUERY_NAME = DIST_NAME + '-jquery', 36 | DIST_NATIVE_NAME = DIST_NAME + '-native', 37 | 38 | // Directories 39 | JS_DIRS = getSourceDirectories(), 40 | 41 | // Files 42 | 43 | SRC_JQUERY_FILES = ['lib/wrap-start.partial', 'lib/box.js', 'lib/event-target.js', 'lib/dom-jquery.js', 'lib/dom-event-delegate.js', 'lib/context.js', 'lib/application.js', 'lib/wrap-end.partial'], 44 | SRC_NATIVE_FILES = ['lib/wrap-start.partial', 'lib/box.js', 'lib/event-target.js', 'lib/dom-native.js', 'lib/dom-event-delegate.js', 'lib/context.js', 'lib/application.js', 'lib/wrap-end.partial'], 45 | TESTING_JQUERY_FILES = ['lib/wrap-start.partial', 'lib/box.js', 'lib/event-target.js', 'lib/dom-jquery.js', 'lib/dom-event-delegate.js', 'lib/application-stub.js', 'lib/test-service-provider.js', 'lib/wrap-end.partial'], 46 | TESTING_NATIVE_FILES = ['lib/wrap-start.partial', 'lib/box.js', 'lib/event-target.js', 'lib/dom-native.js', 'lib/dom-event-delegate.js', 'lib/application-stub.js', 'lib/test-service-provider.js', 'lib/wrap-end.partial'], 47 | JS_FILES = find(JS_DIRS).filter(fileType('js')).join(' '), 48 | TEST_FILES = find('tests/').filter(fileType('js')).join(' '); 49 | 50 | //------------------------------------------------------------------------------ 51 | // Helpers 52 | //------------------------------------------------------------------------------ 53 | 54 | /** 55 | * Checks if current repository has any uncommitted changes 56 | * @return {boolean} 57 | */ 58 | function isDirectoryClean() { 59 | var fatalState = config.fatal; // save current fatal state 60 | config.fatal = false; 61 | var isUnstagedChanges = exec('git diff --exit-code', {silent:true}).code; 62 | var isStagedChanged = exec('git diff --cached --exit-code', {silent:true}).code; 63 | config.fatal = fatalState; // restore fatal state 64 | return !(isUnstagedChanges || isStagedChanged); 65 | } 66 | 67 | /** 68 | * Executes a Node CLI and exits with a non-zero exit code if the 69 | * CLI execution returns a non-zero exit code. Otherwise, it does 70 | * not exit. 71 | * @param {...string} [args] Arguments to pass to the Node CLI utility. 72 | * @returns {void} 73 | * @private 74 | */ 75 | function nodeExec(args) { 76 | args = arguments; // make linting happy 77 | var code = nodeCLI.exec.apply(nodeCLI, args).code; 78 | if (code !== 0) { 79 | exit(code); 80 | } 81 | } 82 | 83 | /** 84 | * Runs exec() but exits if the exit code is non-zero. 85 | * @param {string} cmd The command to execute. 86 | * @returns {void} 87 | * @private 88 | */ 89 | function execOrExit(cmd) { 90 | var code = exec(cmd).code; 91 | if (code !== 0) { 92 | exit(code); 93 | } 94 | } 95 | 96 | /** 97 | * Generates a function that matches files with a particular extension. 98 | * @param {string} extension The file extension (i.e. 'js') 99 | * @returns {Function} The function to pass into a filter method. 100 | * @private 101 | */ 102 | function fileType(extension) { 103 | return function(filename) { 104 | return filename.substring(filename.lastIndexOf('.') + 1) === extension; 105 | }; 106 | } 107 | 108 | /** 109 | * Determines which directories are present that might have JavaScript files. 110 | * @returns {string[]} An array of directories that exist. 111 | * @private 112 | */ 113 | function getSourceDirectories() { 114 | var dirs = [ 'lib', 'src', 'app' ], 115 | result = []; 116 | 117 | dirs.forEach(function(dir) { 118 | if (test('-d', dir)) { 119 | result.push(dir); 120 | } 121 | }); 122 | 123 | return result; 124 | } 125 | 126 | /** 127 | * Gets the git tags that represent versions. 128 | * @returns {string[]} An array of tags in the git repo. 129 | * @private 130 | */ 131 | function getVersionTags() { 132 | var tags = exec('git tag', { silent: true }).output.trim().split(/\n/g); 133 | 134 | return tags.reduce(function(list, tag) { 135 | if (semver.valid(tag)) { 136 | list.push(tag); 137 | } 138 | return list; 139 | }, []).sort(semver.compare); 140 | } 141 | 142 | /** 143 | * Verifies that common module loaders can be used with dist files 144 | * @returns {void} 145 | * @private 146 | */ 147 | function validateModuleLoading() { 148 | var t3js = require(DIST_DIR + DIST_NAME + '.js'); 149 | 150 | // Validate CommonJS 151 | if (!t3js || !('Application' in t3js)) { 152 | echo('ERROR: The dist file is not wrapped correctly for CommonJS'); 153 | exit(1); 154 | } 155 | } 156 | 157 | /** 158 | * Updates the README links with the latest version 159 | * @param string version The latest version string 160 | * @returns {void} 161 | * @private 162 | */ 163 | function updateReadme(version) { 164 | // Copy to temp file 165 | cat('README.md').to('README.tmp'); 166 | 167 | // Replace Version String 168 | sed('-i', /\/box\/t3js\/v([^/])+/g, '/box/t3js/' + version, 'README.tmp'); 169 | 170 | // Replace README 171 | rm('README.md'); 172 | mv('README.tmp', 'README.md'); 173 | } 174 | 175 | /** 176 | * Generate distribution files for a single package 177 | * @param Object config The distribution configuration 178 | * @returns {void} 179 | * @private 180 | */ 181 | function generateDistFiles(config) { 182 | // Delete package.json from the cache since it can get updated by npm version 183 | delete require.cache[require.resolve('./package.json')]; 184 | 185 | var pkg = require('./package.json'), 186 | distFilename = DIST_DIR + config.name + '.js', 187 | minDistFilename = distFilename.replace(/\.js$/, '.min.js'), 188 | minDistSourcemapFilename = minDistFilename + '.map', 189 | distTestingFilename = DIST_DIR + config.name + '-testing' + '.js'; 190 | 191 | // Add copyrights and version info 192 | var versionComment = '/*! ' + config.name + ' v' + pkg.version + ' */\n', 193 | testingVersionComment = '/*! ' + config.name + '-testing v' + pkg.version + ' */\n', 194 | copyrightComment = cat('./config/copyright.txt'); 195 | 196 | // concatenate files together and add version/copyright notices 197 | (versionComment + copyrightComment + cat(config.files)).to(distFilename); 198 | (testingVersionComment + copyrightComment + cat(config.testingFiles)).to(distTestingFilename); 199 | 200 | // create minified version with source maps 201 | var result = uglifyjs.minify(distFilename, { 202 | output: { 203 | comments: /^!/ 204 | }, 205 | outSourceMap: path.basename(minDistSourcemapFilename) 206 | }); 207 | result.code.to(minDistFilename); 208 | result.map.to(minDistSourcemapFilename); 209 | 210 | // create filenames with version in them 211 | cp(distFilename, distFilename.replace('.js', '-' + pkg.version + '.js')); 212 | cp(minDistFilename, minDistFilename.replace('.min.js', '-' + pkg.version + '.min.js')); 213 | cp(distTestingFilename, distTestingFilename.replace('.js', '-' + pkg.version + '.js')); 214 | } 215 | 216 | /** 217 | * Generate all distribution files 218 | * @private 219 | */ 220 | function dist() { 221 | if (test('-d', DIST_DIR)) { 222 | rm('-r', DIST_DIR + '*'); 223 | } else { 224 | mkdir(DIST_DIR); 225 | } 226 | 227 | [{ 228 | name: DIST_NATIVE_NAME, 229 | files: SRC_NATIVE_FILES, 230 | testingFiles: TESTING_NATIVE_FILES 231 | }, { 232 | name: DIST_JQUERY_NAME, 233 | files: SRC_JQUERY_FILES, 234 | testingFiles: TESTING_JQUERY_FILES 235 | }, { 236 | name: DIST_NAME, 237 | files: SRC_NATIVE_FILES, 238 | testingFiles: TESTING_NATIVE_FILES 239 | }].forEach(function(config){ 240 | generateDistFiles(config); 241 | }); 242 | } 243 | 244 | /** 245 | * Creates a release version tag and pushes to origin. 246 | * @param {string} type The type of release to do (patch, minor, major) 247 | * @returns {void} 248 | */ 249 | function release(type) { 250 | 251 | // 'npm version' needs a clean repository to run 252 | if (!isDirectoryClean()) { 253 | echo('RELEASE ERROR: Working directory must be clean to push release!'); 254 | exit(1); 255 | } 256 | 257 | echo('Running tests'); 258 | target.test(); 259 | 260 | // Step 1: Create the new version 261 | echo('Creating new version'); 262 | var newVersion = exec('npm version ' + type).output.trim(); 263 | 264 | // Step 2: Generate files 265 | echo('Generating dist files'); 266 | dist(); 267 | 268 | echo('Generating changelog'); 269 | target.changelog(); 270 | 271 | echo('Updating README'); 272 | updateReadme(newVersion); 273 | 274 | // Step 3: Validate CommonJS wrapping 275 | echo('Validating module loading'); 276 | validateModuleLoading(); 277 | 278 | // Step 4: Add files to current commit 279 | execOrExit('git add -A'); 280 | execOrExit('git commit --amend --no-edit'); 281 | 282 | // Step 5: reset the git tag to the latest commit 283 | execOrExit('git tag -f ' + newVersion); 284 | 285 | // Step 6: publish to git 286 | echo('Pushing to github'); 287 | execOrExit('git push origin master --tags'); 288 | 289 | // Step 7: publish to npm 290 | echo('Publishing to NPM'); 291 | execOrExit('npm publish'); 292 | 293 | // Step 8: Update version number in docs site 294 | echo('Updating documentation site'); 295 | execOrExit('git checkout gh-pages'); 296 | ('version: ' + newVersion).to('_data/t3.yml'); 297 | execOrExit('git commit -am "Update version number to ' + newVersion + '"'); 298 | execOrExit('git fetch origin && git rebase origin/gh-pages && git push origin gh-pages'); 299 | 300 | // Step 9: Switch back to master 301 | execOrExit('git checkout master'); 302 | 303 | // Step 10: Party time 304 | } 305 | 306 | 307 | //------------------------------------------------------------------------------ 308 | // Tasks 309 | //------------------------------------------------------------------------------ 310 | 311 | target.all = function() { 312 | target.test(); 313 | }; 314 | 315 | target.lint = function() { 316 | echo('Validating JavaScript files'); 317 | nodeExec('eslint', [JS_FILES, TEST_FILES].join(' ')); 318 | }; 319 | 320 | target.test = function() { 321 | target.lint(); 322 | 323 | echo('Running browser tests'); 324 | var code = exec('node ./node_modules/karma/bin/karma start config/karma-conf.js').code; 325 | if (code !== 0) { 326 | exit(code); 327 | } 328 | 329 | echo('Running Utilities tests'); 330 | target['utils-test'](); 331 | 332 | echo('Running API tests'); 333 | target['api-test'](); 334 | }; 335 | 336 | target['utils-test'] = function() { 337 | var code = exec('node ./node_modules/karma/bin/karma start config/testing-utils-karma-conf.js').code; 338 | if (code !== 0) { 339 | exit(code); 340 | } 341 | }; 342 | 343 | target['api-test'] = function() { 344 | // generate dist files that are used by api-test 345 | dist(); 346 | 347 | nodeExec('mocha', './tests/api-test.js'); 348 | 349 | // revert generated files 350 | execOrExit('git checkout dist'); 351 | }; 352 | 353 | target['test-watch'] = function() { 354 | echo('Watching files to run browser tests. Press Ctrl+C to exit.'); 355 | var code = exec('node ./node_modules/karma/bin/karma start config/karma-conf.js --single-run=false --autoWatch').code; 356 | if (code !== 0) { 357 | exit(code); 358 | } 359 | }; 360 | 361 | target.docs = function() { 362 | echo('Generating documentation'); 363 | exec(JSDOC + '-d jsdoc ' + JS_DIRS.join(' ')); 364 | echo('Documentation has been output to /jsdoc'); 365 | }; 366 | 367 | // Don't assign directly to dist since shelljs wraps this function 368 | target.dist = function() { 369 | dist(); 370 | }; 371 | 372 | target.changelog = function() { 373 | 374 | // get most recent two tags 375 | var tags = getVersionTags(), 376 | rangeTags = tags.slice(tags.length - 2), 377 | now = new Date(), 378 | timestamp = dateformat(now, 'mmmm d, yyyy'); 379 | 380 | // output header 381 | (rangeTags[1] + ' - ' + timestamp + '\n').to('CHANGELOG.tmp'); 382 | 383 | // get log statements 384 | var logs = exec('git log --pretty=format:"* %s (%an)" ' + rangeTags.join('..'), {silent: true}).output.split(/\n/g); 385 | logs = logs.filter(function(line) { 386 | return line.indexOf('Merge pull request') === -1 && line.indexOf('Merge branch') === -1; 387 | }); 388 | logs.push(''); // to create empty lines 389 | logs.unshift(''); 390 | 391 | // output log statements 392 | logs.join('\n').toEnd('CHANGELOG.tmp'); 393 | 394 | // switch-o change-o 395 | cat('CHANGELOG.tmp', 'CHANGELOG.md').to('CHANGELOG.md.tmp'); 396 | rm('CHANGELOG.tmp'); 397 | rm('CHANGELOG.md'); 398 | mv('CHANGELOG.md.tmp', 'CHANGELOG.md'); 399 | }; 400 | 401 | target.patch = function() { 402 | release('patch'); 403 | }; 404 | 405 | target.minor = function() { 406 | release('minor'); 407 | }; 408 | 409 | target.major = function() { 410 | release('major'); 411 | }; 412 | --------------------------------------------------------------------------------