├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile.js
├── README.md
├── config
├── copyright.txt
├── karma-conf.js
└── testing-utils-karma-conf.js
├── dist
├── t3-jquery-2.7.0.js
├── t3-jquery-2.7.0.min.js
├── t3-jquery-testing-2.7.0.js
├── t3-jquery-testing.js
├── t3-jquery.js
├── t3-jquery.min.js
├── t3-jquery.min.js.map
├── t3-native-2.7.0.js
├── t3-native-2.7.0.min.js
├── t3-native-testing-2.7.0.js
├── t3-native-testing.js
├── t3-native.js
├── t3-native.min.js
├── t3-native.min.js.map
├── t3-testing.js
├── t3.js
├── t3.min.js
└── t3.min.js.map
├── examples
└── todo
│ ├── bower_components
│ ├── jquery-1.11.1.min
│ │ ├── .bower.json
│ │ └── index.js
│ └── todomvc-common
│ │ ├── base.css
│ │ ├── base.js
│ │ └── bg.png
│ ├── css
│ └── app.css
│ ├── index.html
│ └── js
│ ├── app.js
│ ├── behaviors
│ └── todo.js
│ ├── modules
│ ├── footer.js
│ ├── header.js
│ ├── list.js
│ └── page.js
│ ├── services
│ ├── router.js
│ └── todos-db.js
│ └── t3-dev-build.js
├── lib
├── .eslintrc
├── application-stub.js
├── application.js
├── box.js
├── context.js
├── dom-event-delegate.js
├── dom-jquery.js
├── dom-native.js
├── event-target.js
├── test-service-provider.js
├── wrap-end.partial
└── wrap-start.partial
├── package.json
└── tests
├── .eslintrc
├── api-test.js
├── application-test.js
├── context-test.js
├── dom-event-delegate-test.js
├── dom-test.js
├── event-target-test.js
├── test-service-provider-test.js
└── utils
├── assert.js
└── common-setup.js
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 | "License" shall mean the terms and conditions for use, reproduction,
9 | and distribution as defined by Sections 1 through 9 of this document.
10 | "Licensor" shall mean the copyright owner or entity authorized by
11 | the copyright owner that is granting the License.
12 | "Legal Entity" shall mean the union of the acting entity and all
13 | other entities that control, are controlled by, or are under common
14 | control with that entity. For the purposes of this definition,
15 | "control" means (i) the power, direct or indirect, to cause the
16 | direction or management of such entity, whether by contract or
17 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
18 | outstanding shares, or (iii) beneficial ownership of such entity.
19 | "You" (or "Your") shall mean an individual or Legal Entity
20 | exercising permissions granted by this License.
21 | "Source" form shall mean the preferred form for making modifications,
22 | including but not limited to software source code, documentation
23 | source, and configuration files.
24 | "Object" form shall mean any form resulting from mechanical
25 | transformation or translation of a Source form, including but
26 | not limited to compiled object code, generated documentation,
27 | and conversions to other media types.
28 | "Work" shall mean the work of authorship, whether in Source or
29 | Object form, made available under the License, as indicated by a
30 | copyright notice that is included in or attached to the work
31 | (an example is provided in the Appendix below).
32 | "Derivative Works" shall mean any work, whether in Source or Object
33 | form, that is based on (or derived from) the Work and for which the
34 | editorial revisions, annotations, elaborations, or other modifications
35 | represent, as a whole, an original work of authorship. For the purposes
36 | of this License, Derivative Works shall not include works that remain
37 | separable from, or merely link (or bind by name) to the interfaces of,
38 | the Work and Derivative Works thereof.
39 | "Contribution" shall mean any work of authorship, including
40 | the original version of the Work and any modifications or additions
41 | to that Work or Derivative Works thereof, that is intentionally
42 | submitted to Licensor for inclusion in the Work by the copyright owner
43 | or by an individual or Legal Entity authorized to submit on behalf of
44 | the copyright owner. For the purposes of this definition, "submitted"
45 | means any form of electronic, verbal, or written communication sent
46 | to the Licensor or its representatives, including but not limited to
47 | communication on electronic mailing lists, source code control systems,
48 | and issue tracking systems that are managed by, or on behalf of, the
49 | Licensor for the purpose of discussing and improving the Work, but
50 | excluding communication that is conspicuously marked or otherwise
51 | designated in writing by the copyright owner as "Not a Contribution."
52 | "Contributor" shall mean Licensor and any individual or Legal Entity
53 | on behalf of whom a Contribution has been received by Licensor and
54 | subsequently incorporated within the Work.
55 |
56 | 2. Grant of Copyright License. Subject to the terms and conditions of
57 | this License, each Contributor hereby grants to You a perpetual,
58 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
59 | copyright license to reproduce, prepare Derivative Works of,
60 | publicly display, publicly perform, sublicense, and distribute the
61 | Work and such Derivative Works in Source or Object form.
62 |
63 | 3. Grant of Patent License. Subject to the terms and conditions of
64 | this License, each Contributor hereby grants to You a perpetual,
65 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66 | (except as stated in this section) patent license to make, have made,
67 | use, offer to sell, sell, import, and otherwise transfer the Work,
68 | where such license applies only to those patent claims licensable
69 | by such Contributor that are necessarily infringed by their
70 | Contribution(s) alone or by combination of their Contribution(s)
71 | with the Work to which such Contribution(s) was submitted. If You
72 | institute patent litigation against any entity (including a
73 | cross-claim or counterclaim in a lawsuit) alleging that the Work
74 | or a Contribution incorporated within the Work constitutes direct
75 | or contributory patent infringement, then any patent licenses
76 | granted to You under this License for that Work shall terminate
77 | as of the date such litigation is filed.
78 |
79 | 4. Redistribution. You may reproduce and distribute copies of the
80 | Work or Derivative Works thereof in any medium, with or without
81 | modifications, and in Source or Object form, provided that You
82 | meet the following conditions:
83 |
84 | (a) You must give any other recipients of the Work or
85 | Derivative Works a copy of this License; and
86 |
87 | (b) You must cause any modified files to carry prominent notices
88 | stating that You changed the files; and
89 |
90 | (c) You must retain, in the Source form of any Derivative Works
91 | that You distribute, all copyright, patent, trademark, and
92 | attribution notices from the Source form of the Work,
93 | excluding those notices that do not pertain to any part of
94 | the Derivative Works; and
95 |
96 | (d) If the Work includes a "NOTICE" text file as part of its
97 | distribution, then any Derivative Works that You distribute must
98 | include a readable copy of the attribution notices contained
99 | within such NOTICE file, excluding those notices that do not
100 | pertain to any part of the Derivative Works, in at least one
101 | of the following places: within a NOTICE text file distributed
102 | as part of the Derivative Works; within the Source form or
103 | documentation, if provided along with the Derivative Works; or,
104 | within a display generated by the Derivative Works, if and
105 | wherever such third-party notices normally appear. The contents
106 | of the NOTICE file are for informational purposes only and
107 | do not modify the License. You may add Your own attribution
108 | notices within Derivative Works that You distribute, alongside
109 | or as an addendum to the NOTICE text from the Work, provided
110 | that such additional attribution notices cannot be construed
111 | as modifying the License.
112 |
113 | You may add Your own copyright statement to Your modifications and
114 | may provide additional or different license terms and conditions
115 | for use, reproduction, or distribution of Your modifications, or
116 | for any such Derivative Works as a whole, provided Your use,
117 | reproduction, and distribution of the Work otherwise complies with
118 | the conditions stated in this License.
119 |
120 | 5. Submission of Contributions. Unless You explicitly state otherwise,
121 | any Contribution intentionally submitted for inclusion in the Work
122 | by You to the Licensor shall be under the terms and conditions of
123 | this License, without any additional terms or conditions.
124 | Notwithstanding the above, nothing herein shall supersede or modify
125 | the terms of any separate license agreement you may have executed
126 | with Licensor regarding such Contributions.
127 |
128 | 6. Trademarks. This License does not grant permission to use the trade
129 | names, trademarks, service marks, or product names of the Licensor,
130 | except as required for reasonable and customary use in describing the
131 | origin of the Work and reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty. Unless required by applicable law or
134 | agreed to in writing, Licensor provides the Work (and each
135 | Contributor provides its Contributions) on an "AS IS" BASIS,
136 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
137 | implied, including, without limitation, any warranties or conditions
138 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
139 | PARTICULAR PURPOSE. You are solely responsible for determining the
140 | appropriateness of using or redistributing the Work and assume any
141 | risks associated with Your exercise of permissions under this License.
142 |
143 | 8. Limitation of Liability. In no event and under no legal theory,
144 | whether in tort (including negligence), contract, or otherwise,
145 | unless required by applicable law (such as deliberate and grossly
146 | negligent acts) or agreed to in writing, shall any Contributor be
147 | liable to You for damages, including any direct, indirect, special,
148 | incidental, or consequential damages of any character arising as a
149 | result of this License or out of the use or inability to use the
150 | Work (including but not limited to damages for loss of goodwill,
151 | work stoppage, computer failure or malfunction, or any and all
152 | other commercial damages or losses), even if such Contributor
153 | has been advised of the possibility of such damages.
154 |
155 | 9. Accepting Warranty or Additional Liability. While redistributing
156 | the Work or Derivative Works thereof, You may choose to offer,
157 | and charge a fee for, acceptance of support, warranty, indemnity,
158 | or other liability obligations and/or rights consistent with this
159 | License. However, in accepting such obligations, You may act only
160 | on Your own behalf and on Your sole responsibility, not on behalf
161 | of any other Contributor, and only if You agree to indemnify,
162 | defend, and hold each Contributor harmless for any liability
163 | incurred by, or claims asserted against, such Contributor by reason
164 | of your accepting any such warranty or additional liability.
165 |
166 | END OF TERMS AND CONDITIONS
167 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Build Status][travis-image]][travis-url]
2 | [](https://opensource.box.com/badges)
3 |
4 | # DEPRECATED
5 |
6 | Box has migrated using `react`, `webpack`, and the latest version of ECMAScript for our frontend projects as of 2018. We no longer support changes, pull requests, or upgrades to this package. We appreciate all of the user contributions that you have given us over the past few years.
7 |
8 | # T3 JavaScript Framework
9 |
10 | T3 is a client-side JavaScript framework for building large-scale web applications. Its design is based on the principles of [Scalable JavaScript Application Architecture](https://www.youtube.com/watch?v=mKouqShWI4o), specifically:
11 |
12 | * Enforcing loose coupling between components
13 | * Making dependencies explicit
14 | * Providing extension points to allow for unforeseen requirements
15 | * Abstracting away common pain points
16 | * Encouraging progressive enhancement
17 |
18 | The approaches taken in T3 have been battle-hardened through continuous production use at [Box](https://box.com) since 2013, where we use T3 along with jQuery, jQuery UI, and several other third-party libraries and frameworks.
19 |
20 | ## Framework Design
21 |
22 | T3 is different from most JavaScript frameworks. It's meant to be a small piece of an overall architecture that allows you to build scalable client-side code.
23 |
24 | ### No MVC Here
25 |
26 | T3 is explicitly *not* an MVC framework. It's a framework that allows the creation of loosely-coupled components while letting you decide what other pieces you need for your web application. You can use T3 with other frameworks like [Backbone](http://backbonejs.org/) or [React](http://facebook.github.io/react/), or you can use T3 by itself. If you decide you want model and views, in whatever form, you can still use them with T3.
27 |
28 | ### Unopinionated by Design
29 |
30 | T3 is made to be unopinionated while prescribing how some problems might be solved. Our goal here is not to create a single framework that can do everything for you, but rather, to provide some structure to your client-side code that allows you to make good choices. Then, you can add in other libraries and frameworks to suit your needs.
31 |
32 | ### Three Component Types
33 |
34 | T3 allows you to define functionality using just three component types:
35 |
36 | 1. **Services** are utility libraries that provide additional capabilities to your application. You can think of services as tools in a toolbox that you use to build an application. They intended to be reusable pieces of code such as cookie parsing, Ajax communication, string utilities, and so on.
37 | 2. **Modules** represent a particular DOM element on a page and manage the interaction inside of that element. It's a module's job to respond to user interaction within its boundaries. Your application is made up of a series of modules. Modules may not interact directly with other modules, but may do so indirectly.
38 | 3. **Behaviors** are mixins for modules and are used primarily to allow shared declarative event handling for modules without duplicating code. If, for instance, you use a particular attribute to indicate a link should use Ajax navigation instead of full-page navigation, you can share that functionality amongst multiple modules.
39 |
40 | We've found that by using a combination of these three component types, we're able to create compelling, progressively-enhanced user experiences.
41 |
42 | ## Installation
43 |
44 | To include T3 in a web page, you can use [RawGit](http://rawgit.com).
45 |
46 | The last published release:
47 |
48 | ```
49 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/dist/t3-jquery-2.7.0.min.js:
--------------------------------------------------------------------------------
1 | /*! t3-jquery 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.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.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.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;n 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 |
--------------------------------------------------------------------------------
/examples/todo/bower_components/todomvc-common/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/box/t3js/334be4358567eeb9092d89667ea01810857d75a9/examples/todo/bower_components/todomvc-common/bg.png
--------------------------------------------------------------------------------
/examples/todo/css/app.css:
--------------------------------------------------------------------------------
1 | #clear-completed {
2 | display: none;
3 | }
4 | .has-completed-tasks #clear-completed {
5 | display: block;
6 | }
--------------------------------------------------------------------------------
/examples/todo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | T3 • TodoNotMVC
7 |
8 |
9 |
10 |
11 |
12 |
13 |
todos
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/utils/common-setup.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: 0, no-unused-vars: 0 */
2 |
3 | var assert = chai.assert;
4 |
--------------------------------------------------------------------------------