├── .gitignore ├── example ├── rottentomatoes │ ├── demo │ │ ├── public │ │ │ ├── __id__ │ │ │ │ ├── Movie.css │ │ │ │ ├── Movie.html │ │ │ │ └── Movie.js │ │ │ ├── Home.css │ │ │ ├── Home_movie.html │ │ │ ├── Home.html │ │ │ └── Home.js │ │ └── library │ │ │ ├── BaseController.js │ │ │ └── BaseController.css │ ├── package.json │ └── main.js ├── skeleton │ ├── library │ │ ├── BaseController.html │ │ ├── BaseController.js │ │ └── BaseController.css │ ├── public │ │ ├── Home_item.html │ │ ├── Home.css │ │ ├── about │ │ │ ├── About.js │ │ │ └── About.html │ │ ├── Home.html │ │ └── Home.js │ └── __static__ │ │ └── favicon.ico ├── helloworld │ ├── library │ │ ├── BaseController.html │ │ ├── BaseController.css │ │ ├── BaseController.js │ │ └── GitHubAPIClient.js │ └── public │ │ ├── Home.html │ │ ├── Home_item.html │ │ ├── gist │ │ ├── Gist.html │ │ └── Gist.js │ │ └── Home.js └── skitjs.com │ ├── __static__ │ ├── favicon.ico │ ├── skitrequest.png │ └── viewlayer.png │ ├── public │ ├── getting-started │ │ └── GettingStarted.js │ ├── Home.js │ ├── Home.css │ └── Home.html │ └── library │ ├── BaseController.js │ ├── BaseController_style.css │ ├── BaseController_buttons.css │ └── BaseController_layout.css ├── lib ├── skit │ ├── platform │ │ ├── env_browser.js │ │ ├── env_server.js │ │ ├── cookies_browser.js │ │ ├── object.js │ │ ├── env.js │ │ ├── navigation_browser.js │ │ ├── netproxy_server.js │ │ ├── netproxy_browser.js │ │ ├── netproxy.js │ │ ├── json.js │ │ ├── net_browser.js │ │ ├── net_server.js │ │ ├── cookies_server.js │ │ ├── net_SendOptions.js │ │ ├── cookies.js │ │ ├── net_Response.js │ │ ├── navigation_server.js │ │ ├── net.js │ │ ├── string.js │ │ ├── navigation.js │ │ ├── PubSub.js │ │ ├── util.js │ │ ├── urls.js │ │ ├── iter.js │ │ └── Controller.js │ ├── browser │ │ ├── reset.css │ │ ├── dom.js │ │ ├── Event.js │ │ ├── events.js │ │ ├── layout.js │ │ └── ElementWrapper.js │ └── thirdparty │ │ └── cookies.js ├── skit.js ├── loader │ ├── styleresource.js │ ├── NamedNode.js │ ├── loader.js │ ├── pooledmoduleloader.js │ ├── SkitModule.js │ ├── BundledLoader.js │ └── scriptresource.js ├── skitutil.js ├── error.html ├── errors.js ├── SkitProxy.js ├── bootstrap.html ├── ControllerRenderer.js └── optimizer.js ├── README.md ├── package.json ├── LICENSE ├── docs.md └── bin └── skit /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /example/rottentomatoes/demo/public/__id__/Movie.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/skeleton/library/BaseController.html: -------------------------------------------------------------------------------- 1 |
2 | Hello, world! I’m a template. 3 | 4 |
5 | 6 |{{ gist.description }}
7 | 8 | 9 | {{#each gist.files }} 10 |{{ size }} bytes, {{ type }} ({{ language }})
12 |{{ content }}
13 | {{/each}}
14 |
--------------------------------------------------------------------------------
/example/rottentomatoes/demo/library/BaseController.js:
--------------------------------------------------------------------------------
1 |
2 | var Controller = skit.platform.Controller;
3 | var string = skit.platform.string;
4 | var Handlebars = skit.thirdparty.handlebars;
5 |
6 | Handlebars.registerHelper('slugify', function(arg) {
7 | return string.trim(arg).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
8 | });
9 |
10 | return Controller.create({});
11 |
--------------------------------------------------------------------------------
/example/skitjs.com/public/getting-started/GettingStarted.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Controller = skit.platform.Controller;
4 |
5 | var BaseController = library.BaseController;
6 |
7 | var html = __module__.html;
8 |
9 |
10 | return Controller.create(BaseController, {
11 | __title__: function() {
12 | return 'Getting Started';
13 | },
14 |
15 | __body__: function() {
16 | return html();
17 | }
18 | });
--------------------------------------------------------------------------------
/example/skitjs.com/public/Home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Controller = skit.platform.Controller;
4 |
5 | var BaseController = library.BaseController;
6 |
7 | var html = __module__.html;
8 |
9 |
10 | return Controller.create(BaseController, {
11 | __title__: function() {
12 | return 'JavaScript web application environment for first-class web clients';
13 | },
14 |
15 | __body__: function() {
16 | return html();
17 | }
18 | });
--------------------------------------------------------------------------------
/lib/skit/browser/reset.css:
--------------------------------------------------------------------------------
1 |
2 | body, h1, h2, h3, h4, h5, h6, p, ol, ul, form, blockquote {
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | h1, h2, h3, h4, h5, h6 {
8 | font-size: 1em;
9 | font-weight: normal;
10 | }
11 |
12 | a {
13 | text-decoration: none;
14 | cursor: pointer;
15 | }
16 |
17 | a img {
18 | border: none;
19 | }
20 |
21 | ol,
22 | ul,
23 | li {
24 | list-style-type: none;
25 | }
26 |
27 | * {
28 | outline: none;
29 | }
30 |
--------------------------------------------------------------------------------
/example/skeleton/public/about/About.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Controller = skit.platform.Controller;
4 |
5 | var BaseController = library.BaseController;
6 |
7 | // This loads About.html from this directory.
8 | var html = __module__.html;
9 |
10 |
11 | return Controller.create(BaseController, {
12 | // This controller doesn't preload anything.
13 | __title__: function() {
14 | return 'About';
15 | },
16 |
17 | __body__: function() {
18 | return html();
19 | }
20 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | skit
2 | ====
3 | A pure JavaScript frontend for building better web clients.
4 |
5 | ### Upcoming features
6 |
7 | * Client-side navigation/rendering and history management for subsequent pageloads.
8 | * Performance improvements, including the ability to cache content from backends.
9 | * Better integration points for backend proxies.
10 |
11 | ### Changelog
12 |
13 | Check out the Releases for a list of changes with every version.
14 |
--------------------------------------------------------------------------------
/lib/skit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @license
5 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 |
10 | var SkitServer = require('./SkitServer');
11 | var optimizer = require('./optimizer');
12 | var scriptresource = require('./loader/scriptresource');
13 | var styleresource = require('./loader/styleresource');
14 |
15 | module.exports = {
16 | 'SkitServer': SkitServer,
17 | 'optimizeServer': optimizer.optimizeServer,
18 |
19 | 'styleresource': styleresource,
20 | 'scriptresource': scriptresource,
21 | };
22 |
--------------------------------------------------------------------------------
/lib/skit/platform/object.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @module
5 | * @license
6 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
7 | * License: MIT
8 | */
9 |
10 |
11 | /**
12 | * Shallow copy an Object's keys to a new object.
13 | *
14 | * @param {Object} obj The object to copy, eg. {'a': 'b'}.
15 | * @return {Object} A new object containing the same keys, eg. {'a': 'b'}.
16 | */
17 | module.exports.copy = function copy(obj) {
18 | var newObj = {};
19 | for (var k in obj) {
20 | newObj[k] = obj[k];
21 | }
22 | return newObj;
23 | };
24 |
--------------------------------------------------------------------------------
/example/helloworld/library/BaseController.js:
--------------------------------------------------------------------------------
1 | var Controller = skit.platform.Controller;
2 | var template = __module__.html;
3 | module.exports = Controller.create({
4 | __title__: function(childTitle) {
5 | // Parents get the title generated by children as an argument.
6 | return childTitle + ' | Hello World';
7 | },
8 | __body__: function(childHtml) {
9 | // Parents get the HTML generated by children as an argument.
10 | return template({childHtml: childHtml});
11 | },
12 | __ready__: function() {
13 | // Parents also get __preload__, __load__ and __ready__ calls.
14 | }
15 | });
--------------------------------------------------------------------------------
/example/rottentomatoes/demo/library/BaseController.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue", hevetica, arial, sans-serif;
8 | font-size: 16px;
9 | line-height: 1.4em;
10 |
11 | width: 640px;
12 | margin: 20px auto;
13 | }
14 |
15 | h1, h2, p, hr, ul, ol {
16 | margin-bottom: 20px;
17 | }
18 |
19 | li {
20 | list-style-type: square;
21 | }
22 |
23 | a {
24 | color: blue;
25 | text-decoration: none;
26 | cursor: pointer;
27 | }
28 |
29 | .poster {
30 | float: left;
31 | width: 80px;
32 | margin: 10px;
33 | margin-top: 0;
34 | margin-left: 0;
35 | }
--------------------------------------------------------------------------------
/example/skeleton/library/BaseController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var reset = skit.browser.reset;
4 | var Controller = skit.platform.Controller;
5 |
6 | // This loads Base.html from this directory.
7 | var html = __module__.html;
8 |
9 |
10 | return Controller.create({
11 | __title__: function(childTitle) {
12 | // Parent controllers can frame the title content of child controllers.
13 | return childTitle + ' | Skit';
14 | },
15 |
16 | __body__: function(childHtml) {
17 | // Parent controllers can frame the body content of child controllers.
18 | return html({
19 | childHtml: childHtml
20 | });
21 | }
22 | });
--------------------------------------------------------------------------------
/lib/skit/platform/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @module
5 | * @license
6 | * (c) 2016 Cluster Labs, Inc. https://cluster.co/
7 | * License: MIT
8 | */
9 |
10 | /** @ignore */
11 | var browser = __module__.browser;
12 | /** @ignore */
13 | var server = __module__.server;
14 |
15 |
16 |
17 | /**
18 | * @param {string} name Environment key name.
19 | * @return {object?} The env value, if present, or null.
20 | */
21 | module.exports.get = function(name) {
22 | // Dummy function filled in by env_browser.js or env_server.js.
23 | };
24 |
25 |
26 | /* JSDoc, plz to ignore this. */
27 | module.exports = browser || server;
28 |
--------------------------------------------------------------------------------
/example/skeleton/public/about/About.html:
--------------------------------------------------------------------------------
1 | 5 | Navigation is based on the directory structure of “public”. 6 | Pretty wacky, but there’s no routes file to worry about. 7 |
8 |Nam commodo dignissim mauris in mattis. Aenean congue justo id odio volutpat porttitor. Curabitur sed laoreet risus, eu sollicitudin arcu.
11 | 12 |Proin tincidunt metus non eros euismod, eget porta lectus aliquam. Duis eu nunc ac orci interdum mollis. Phasellus sed mollis risus, eu tempor quam. Morbi at interdum mi, vel porta lacus.
13 | 14 | -------------------------------------------------------------------------------- /example/skeleton/public/Home.html: -------------------------------------------------------------------------------- 1 |Visit a subpage: About
5 |10 | This is dynamic content loaded from a remote location. 11 | This is a trivial example because the remote location is a hosted JSON file, 12 | but in a real application this would be data from an API of some kind. 13 |
14 |24 | Check out the skit project on Github 25 | to see the full documentation. 26 |
27 |4 | Regex paths are supported by directories with special names. 5 | In main.js we register regular expressions for these paths. 6 |
7 | 8 |13 | Tomatometer: {{ ratings.critics_score }}% (critics); {{ ratings.audience_score }}% (audience) 14 | — 15 | Rating: {{ mpaa_rating }} 16 |
17 | 18 |
20 |
21 | {{ synopsis }}
22 |
This is a template. It is rendered using Handlebars.
3 | 4 |5 | Navigation in skit (for now) is dictated by the 6 | directory structure of the “public” package. WTF? I know, it’s crazy. 7 |
8 | 9 |12 | We fetched and rendered the movies data (below) on the server side. 13 | Clicking another list ("Change list") will make a client-side request 14 | in the same re-instantiated class, and could re-render the list or something 15 | with the same exact templates. 16 |
17 | 18 |23 | Change list: 24 |
{{ fileName }}:{{ lineNumber }}
{{{ excerptHtml }}}
87 | {{/if}}
88 | {{/if}}
89 |
90 | {{#if error.stack }}
91 | {{error.stack}}
94 | {{/if}}
95 | Foo.html
31 | - Foo.js
32 | - Foo_Bar.js
33 | - Foo_bazbuz.js
34 | - Foo.css
35 |
36 | Provide a single module, "Foo", with several internal modules. In Foo.js, you might import some of them at the top of your file:
37 |
38 | // This is a global skit library.
39 | var net = skit.platform.net;
40 |
41 | // This is a class from another skit module we wrote.
42 | var MyLibraryClass = library.things.MyLibraryClass;
43 |
44 | var Bar = __module__.Bar;
45 | // This is a Handlebars compiled template.
46 | var html = __module__.html;
47 |
48 | CSS modules are not accessible this way:
49 |
50 | // this will not work:
51 | var css = __module__.css;
52 |
53 |
54 | Module conventions
55 | ------------------
56 |
57 | 1. Files whose exports are a class are CapitalizedLikeAClass, eg. Controller.js
58 | 2. Files whose exports are a module arelikethis -- no spaces
59 | 3. Internal modules, eg. SomeModule_someinternalthing.js follow the same convention -- "someinternalthing" in this case is not a class, whereas SomeModule_InternalThing.js is a class
60 | 4. \_\_things\_like\_this\_\_ are generally _special_ skit API things.
61 | 5. Imports are grouped: first global, then project, then internal imports.
62 | 6. Imports can only be at the top of the file -- imports below the first non-import are ignored.
63 |
64 |
65 | skit.browser
66 | ------------
67 |
68 | The browser module contains things that depend on "window", eg. client-side event listeners, DOM lookups and DOM layout measuring functionality.
69 |
70 | skit.platform
71 | -------------
72 |
73 | The platform module is intended for use in both places: server and client-side. It contains several modules that work transparently on the server and in the browser:
74 |
75 | - *cookies* - Wraps cookie setting/reading.
76 | - *net* - Wraps XHR on the client and _request_ on the server to provide the ability to call HTTP endpoints from either place transparently.
77 | - *navigation* - Provides information about the current URL globally, and allows the server side to perform redirects using navigation.navigate() and issue 404s with navigation.notFound().
78 |
79 |
--------------------------------------------------------------------------------
/lib/loader/NamedNode.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @license
5 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 |
10 | function NamedNode(name) {
11 | this.name = name;
12 | this.parent = null;
13 | this.children_ = {};
14 | }
15 |
16 |
17 | NamedNode.prototype.root = function() {
18 | var current = this;
19 | while (current.parent) {
20 | current = current.parent;
21 | if (current === this) {
22 | throw new Error('Cyclical tree.');
23 | }
24 | }
25 | return current;
26 | };
27 |
28 |
29 | NamedNode.prototype.findNodeWithPath = function(string, opt_separator) {
30 | var separator = opt_separator || '.';
31 | var components = string.split(separator).filter(function(s) { return !!s; });
32 |
33 | return this.findNodeWithPathComponents(components);
34 | };
35 |
36 |
37 | NamedNode.prototype.findNodeWithPathComponents = function(components) {
38 | var current = this;
39 | while (current && components.length) {
40 | current = current.getChildWithName(components[0]);
41 | components = components.slice(1);
42 | }
43 | return current;
44 | };
45 |
46 |
47 | NamedNode.prototype.contains = function(node) {
48 | var current = node;
49 | while (current.parent) {
50 | current = current.parent;
51 | if (current === this) {
52 | return true;
53 | }
54 | if (current === node) {
55 | throw new Error('Cyclical tree.');
56 | }
57 | }
58 | return false;
59 | };
60 |
61 |
62 | NamedNode.prototype.order = function() {
63 | var i = 0;
64 | var current = this;
65 | while (current.parent) {
66 | current = current.parent;
67 | i++;
68 | if (current === this) {
69 | throw new Error('Cyclical tree.');
70 | }
71 | }
72 | return i;
73 | };
74 |
75 |
76 | NamedNode.prototype.addChildNode = function(node) {
77 | if (node.parent) {
78 | node.parent.removeChildNode(node);
79 | }
80 | node.parent = this;
81 | this.children_[node.name] = node;
82 | };
83 |
84 |
85 | NamedNode.prototype.removeChildNode = function(node) {
86 | if (node.parent === this) {
87 | delete this.children_[node.name];
88 | node.parent = null;
89 | }
90 | };
91 |
92 |
93 | NamedNode.prototype.getChildWithName = function(name) {
94 | return this.children_[name] || null;
95 | };
96 |
97 |
98 | NamedNode.prototype.children = function() {
99 | var children = [];
100 | for (var n in this.children_) {
101 | children.push(this.children_[n]);
102 | }
103 | return children;
104 | };
105 |
106 |
107 | NamedNode.prototype.eachChild = function(fn, opt_context) {
108 | for (var n in this.children_) {
109 | fn.call(opt_context, this.children_[n]);
110 | }
111 | };
112 |
113 |
114 | NamedNode.prototype.childNames = function() {
115 | return Object.keys(this.children_);
116 | };
117 |
118 |
119 | NamedNode.prototype.descendants = function() {
120 | if (this.__handling) {
121 | throw new Error('Cyclical tree.');
122 | }
123 | this.__handling = true;
124 |
125 | var list = [];
126 | for (var n in this.children_) {
127 | var child = this.children_[n];
128 | child.descendants().forEach(function(child) {
129 | list.push(child);
130 | });
131 | list.push(child);
132 | }
133 |
134 | delete this.__handling;
135 | return list;
136 | };
137 |
138 |
139 | NamedNode.prototype.toJSON = function() {
140 | var result = {'__name__': this.name};
141 | for (var sub in this.children_) {
142 | result[sub] = this.children_[sub];
143 | }
144 | return result;
145 | };
146 |
147 |
148 | NamedNode.prototype.nodePath = function() {
149 | var parts = [];
150 | var current = this;
151 | while (current && current.name) {
152 | parts.unshift(current.name);
153 | current = current.parent;
154 | }
155 | return parts;
156 | };
157 |
158 |
159 | module.exports = NamedNode;
160 |
--------------------------------------------------------------------------------
/lib/skit/platform/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @module
5 | * @license
6 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
7 | * License: MIT
8 | */
9 |
10 |
11 |
12 | /**
13 | * Setup a class to inherit from another class.
14 | *
15 | * @param {Function} childCtor The child constructor function.
16 | * @param {Function} parentCtor The parent constructor function.
17 | */
18 | module.exports.inherits = function inherits(childCtor, parentCtor) {
19 | function tempCtor() {};
20 | tempCtor.prototype = parentCtor.prototype;
21 | childCtor.superClass_ = parentCtor.prototype;
22 | childCtor.prototype = new tempCtor();
23 | childCtor.prototype.constructor = childCtor;
24 | };
25 |
26 |
27 | /**
28 | * Create a constructor function from the given object keys. If the first
29 | * parameter is a Function, it will become the parent constructor for this
30 | * class. If the __init__ member exists in the given object, it will be called
31 | * when the object is created.
32 | *
33 | * @param {Function} parent The parent class constructor. If the first
34 | * parameter is not a function, it will be used as {object}.
35 | * @param {Object} object The object whose member properties will become
36 | * the prototype members of the resulting class.
37 | */
38 | module.exports.createClass = function createClass() {
39 | var parent = null;
40 | var object;
41 | var parentOrObject = arguments[0];
42 | if (typeof parentOrObject == 'function') {
43 | parent = parentOrObject;
44 | object = arguments[1];
45 | } else {
46 | object = parentOrObject;
47 | }
48 |
49 | if (!object) {
50 | throw new Error('Supply an object that optionally defines __init__.');
51 | }
52 |
53 | var f = function() {
54 | if (this.__init__) {
55 | this.__init__.apply(this, arguments);
56 | }
57 | };
58 | if (parent) {
59 | module.exports.inherits(f, parent);
60 | }
61 | for (var k in object) {
62 | f.prototype[k] = object[k];
63 | }
64 | return f;
65 | };
66 |
67 |
68 | /**
69 | * Bind {this} to the given context inside {fn}.
70 | *
71 | * @param {Function} fn The function to bind.
72 | * @param {Object} context The object to bind as {this} inside {fn}.
73 | * @param {...Object} var_args The arguments to bind after {this}.
74 | */
75 | module.exports.bind = function bind(fn, context, var_args) {
76 | var args = Array.prototype.slice.call(arguments, 2);
77 | return function() {
78 | var moreArgs = Array.prototype.slice.call(arguments);
79 | return fn.apply(context, args.concat(moreArgs));
80 | };
81 | };
82 |
83 |
84 | var hasConsoleLog = false;
85 | try {
86 | hasConsoleLog = !!(typeof console != 'undefined' && console.log && console.log.apply);
87 | } catch (e) {}
88 |
89 |
90 | /**
91 | * In environments that support console.log(), call it with the given
92 | * arguments. Otherwise, do nothing.
93 | *
94 | * @param {...Object} var_args The arguments to pass to console.log().
95 | */
96 | module.exports.log = function log(var_args) {
97 | if (hasConsoleLog) {
98 | console.log.apply(console, arguments);
99 | }
100 | };
101 |
102 |
103 | /**
104 | * Call setTimeout with an optional opt_context.
105 | *
106 | * @param {Function} fn The function to call after the timeout.
107 | * @param {number} time The time in milliseconds to wait before calling {fn}.
108 | * @param {Object=} opt_context The context for {this} inside {fn}.
109 | */
110 | module.exports.setTimeout = function(fn, time, opt_context) {
111 | return setTimeout(function() {
112 | fn.apply(opt_context, arguments);
113 | }, time);
114 | };
115 |
116 |
117 | /**
118 | * Call a function as soon as possible in the given opt_context.
119 | *
120 | * @param {Function} fn The function to call after the timeout.
121 | * @param {Object=} opt_context The context for {this} inside {fn}.
122 | */
123 | module.exports.nextTick = function nextTick(fn, opt_context) {
124 | return module.exports.setTimeout(fn, 0, opt_context);
125 | };
126 |
--------------------------------------------------------------------------------
/lib/skit/browser/events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 'browser-only';
3 |
4 | /**
5 | * Add and remove event listeners on DOM elements.
6 | *
7 | * @module
8 | * @license
9 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
10 | * License: MIT
11 | */
12 |
13 |
14 | /** @ignore */
15 | var Event = skit.browser.Event;
16 | /** @ignore */
17 | var iter = skit.platform.iter;
18 | /** @ignore */
19 | var ElementWrapper = skit.browser.ElementWrapper;
20 |
21 |
22 | /** @ignore */
23 | var boundHandlerId = 0;
24 | /** @ignore */
25 | var boundHandlers = {};
26 | /** @ignore */
27 | var globalListeners_ = [];
28 |
29 |
30 | /**
31 | * Listen for a named DOM event on a given DOM node or {ElementWrapper}.
32 | *
33 | * @param {Element|ElementWrapper} maybeWrappedElement The DOM element or
34 | * ElementWrapper to listen on.
35 | * @param {string} evtName The event name, eg. 'click'.
36 | * @param {Function} callback The callback function.
37 | * @param {Object=} opt_context The object that should be {this} inside
38 | * the callback.
39 | * @return {number} The listener ID, which can be passed to unbind().
40 | */
41 | module.exports.bind = function(maybeWrappedElement, evtName, callback, opt_context) {
42 | var element = maybeWrappedElement.element ? maybeWrappedElement.element : maybeWrappedElement;
43 | var wrappedCallback = function(evt) {
44 | var wrapped = new Event(evt);
45 | callback.call(opt_context, wrapped);
46 | };
47 | var listenerId = ++boundHandlerId + '';
48 | boundHandlers[listenerId] = {
49 | element: element,
50 | evtName: evtName,
51 | handler: wrappedCallback
52 | };
53 |
54 | if (element.addEventListener) {
55 | element.addEventListener(evtName, wrappedCallback);
56 | } else {
57 | element.attachEvent('on' + evtName, wrappedCallback);
58 | }
59 |
60 | if (element === window || element === document || element == document.body || element === document.documentElement) {
61 | globalListeners_.push(listenerId);
62 | }
63 |
64 | return listenerId;
65 | };
66 |
67 |
68 | /**
69 | * Unisten for an event given the listenerId returned by {bind}. Unattaches
70 | * the event listener from the original DOM element.
71 | *
72 | * @param {number} listenerId The listener ID returned by bind().
73 | */
74 | module.exports.unbind = function(listenerId) {
75 | var wrapper = boundHandlers[listenerId];
76 | if (!wrapper) {
77 | return;
78 | }
79 |
80 | delete boundHandlers[listenerId];
81 |
82 | if (wrapper.element.addEventListener) {
83 | wrapper.element.removeEventListener(wrapper.evtName, wrapper.handler);
84 | } else {
85 | wrapper.element.detachEvent(wrapper.evtName, wrapper.handler);
86 | }
87 | };
88 |
89 |
90 | /**
91 | * Listen for an event on a matching child element from a parent element.
92 | *
93 | * @param {Element|ElementWrapper} maybeWrappedElement The DOM element or
94 | * ElementWrapper to listen on.
95 | * @param {string} selector The selector used to determine {originalTarget}
96 | * of the resulting {Event} object passed to {callback}.
97 | * @param {string} evtName The event name, eg. 'click'.
98 | * @param {Function} callback The callback function.
99 | * @param {Object=} opt_context The object that should be {this} inside
100 | * the callback.
101 | * @return {number} The listener ID, which can be passed to unbind().
102 | */
103 | module.exports.delegate = function(element, selector, evtName, callback, opt_context) {
104 | return module.exports.bind(element, evtName, function(evt) {
105 | var currentTarget = evt.target.up(selector);
106 | if (currentTarget) {
107 | evt.currentTarget = currentTarget;
108 | callback.apply(opt_context, arguments);
109 | }
110 | });
111 | };
112 |
113 |
114 | /**
115 | * Remove all listeners added to the global window/document/body.
116 | */
117 | module.exports.removeGlobalListeners = function() {
118 | var listeners = globalListeners_;
119 | globalListeners_ = [];
120 | iter.forEach(listeners, function(listener) {
121 | module.exports.unbind(listener);
122 | });
123 | };
124 |
--------------------------------------------------------------------------------
/lib/loader/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @license
5 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 | var path = require('path');
10 | var fs = require('fs');
11 |
12 | var NamedNode = require('./NamedNode');
13 | var SkitModule = require('./SkitModule');
14 | var pooledmoduleloader = require('./pooledmoduleloader');
15 |
16 |
17 | function walkSync(dir) {
18 | var remaining = 1;
19 |
20 | var filePaths = [];
21 | var paths = fs.readdirSync(dir);
22 | paths.forEach(function(pathToTest) {
23 | pathToTest = dir + path.sep + pathToTest;
24 | var stat = fs.statSync(pathToTest);
25 | if (stat.isDirectory()) {
26 | var paths = walkSync(pathToTest);
27 | paths.forEach(function(file) {
28 | filePaths.push(file);
29 | });
30 | } else {
31 | filePaths.push(pathToTest);
32 | }
33 | });
34 | return filePaths;
35 | }
36 | module.exports.walkSync = walkSync;
37 |
38 |
39 | function mkdirPSync(dir) {
40 | // 'taylor' to '/home/taylor' to ['home', 'taylor']
41 | var parts = path.normalize(dir).split(path.sep);
42 |
43 | for (var i = 0; i < parts.length; i++) {
44 | var currentPath = parts.slice(0, i + 1).join(path.sep) || path.sep;
45 | currentPath = path.normalize(currentPath);
46 |
47 | try {
48 | fs.mkdirSync(currentPath);
49 | } catch (e) {
50 | if (e.code != 'EEXIST' && e.code != 'EISDIR') {
51 | throw e;
52 | }
53 | }
54 | }
55 | }
56 | module.exports.mkdirPSync = mkdirPSync;
57 |
58 |
59 | function buildModuleTree(rootPath, opt_rootName) {
60 | var root = new NamedNode(opt_rootName);
61 |
62 | var realPath = fs.realpathSync(rootPath);
63 | var files = walkSync(realPath);
64 |
65 | files.forEach(function(file) {
66 | var relativePath = file.replace(realPath + path.sep, '');
67 | if (relativePath.indexOf('__') == 0) {
68 | // continue
69 | return;
70 | }
71 |
72 | var basename = path.basename(relativePath);
73 | if (basename.substring(0, 1) == '.') {
74 | // continue
75 | return;
76 | }
77 |
78 | var dirname = path.dirname(relativePath);
79 | var parent = root;
80 | if (dirname != '.') {
81 | dirname.split(path.sep).forEach(function(component) {
82 | var child = parent.getChildWithName(component);
83 | if (!child) {
84 | var child = new NamedNode(component);
85 | parent.addChildNode(child);
86 | }
87 | parent = child;
88 | });
89 | }
90 |
91 | var moduleName = SkitModule.moduleName(file);
92 | var moduleNode = parent.getChildWithName(moduleName);
93 | if (!moduleNode) {
94 | var modulePath = parent.nodePath().concat([moduleName]).join('.');
95 | moduleNode = new SkitModule(moduleName, modulePath);
96 | parent.addChildNode(moduleNode);
97 | }
98 |
99 | try {
100 | moduleNode.addFile(file);
101 | } catch (e) {
102 | e.fileName = file;
103 | throw e;
104 | }
105 | });
106 |
107 | return root;
108 | }
109 | module.exports.buildModuleTree = buildModuleTree;
110 |
111 |
112 | var __skitTree__ = null;
113 | function globalScopedLoaderForModule(modulePath, cb) {
114 | if (!__skitTree__) {
115 | __skitTree__ = new NamedNode('root');
116 | __skitTree__.addChildNode(loadSkitTree());
117 |
118 | pooledmoduleloader.setPoolSize('global', 10);
119 | }
120 | var module = __skitTree__.findNodeWithPath(modulePath);
121 |
122 | pooledmoduleloader.borrowModuleScope('global', module, function(scope) {
123 | cb(scope);
124 | });
125 | }
126 | module.exports.globalScopedLoaderForModule = globalScopedLoaderForModule;
127 |
128 |
129 | function loadSkitTree() {
130 | // Note that this shouldn't be cached, since a tree in memory can only
131 | // belong to a single parent, and we take this tree and add it to
132 | // a different tree in load() below.
133 | var skitPath = path.resolve(__dirname, '..', 'skit');
134 | console.log('[skit] Loading skit in: ' + skitPath);
135 | return buildModuleTree(skitPath, 'skit');
136 | }
137 | module.exports.loadSkitTree = loadSkitTree;
138 |
--------------------------------------------------------------------------------
/lib/skit/platform/urls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @module
5 | * @license
6 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
7 | * License: MIT
8 | */
9 |
10 |
11 | /**
12 | * @param {string} str A querystring component, eg. "foo%20bar" or "foo+bar".
13 | * @return {string} An unescaped string, eg. "foo bar"
14 | */
15 | function decodeQuerystringComponent(str) {
16 | if (!str) {
17 | return str;
18 | }
19 |
20 | return decodeURIComponent(str.replace(/\+/g, ' '));
21 | }
22 |
23 |
24 | /**
25 | * Convert an object of keys/values to a form-encoded string, eg.
26 | * {'a': 'b=c', 'd': 'e'} => 'a=b%26c&d=e'.
27 | *
28 | * @param {Object} params The object of keys/values to encode. Nesting of
29 | * objects is not supported and will have unexpected results.
30 | * @return {string} The form-encoded string.
31 | */
32 | module.exports.toFormEncodedString = function toFormEncodedString(params) {
33 | var pairs = [];
34 | for (var key in params) {
35 | pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent('' + params[key]));
36 | }
37 | return pairs.join('&');
38 | };
39 |
40 |
41 | /**
42 | * The result returned by urls.parse().
43 | * @class
44 | * @name UriInformation
45 | * @property {string} scheme "http" for "http://www.ex.com:80/abc?d=e#f=g"
46 | * @property {string} host "www.ex.com" for "http://www.ex.com:80/abc?d=e#f=g"
47 | * @property {number?} port 80 for "http://www.ex.com:80/abc?d=e#f=g", null
48 | * if not specified.
49 | * @property {string} path "/abc" for "http://www.ex.com:80/abc?d=e#f=g"
50 | * @property {string?} hash "f=g" for "http://www.ex.com:80/abc?d=e#f=g"
51 | * @property {Object} params {d: 'e'} for "http://www.ex.com:80/abc?d=e#f=g"
52 | */
53 |
54 |
55 | /**
56 | * Parses a URL into its component parts.
57 | *
58 | * @param {string} url The URL to parse.
59 | * @return {UriInformation} The parsed result.
60 | */
61 | module.exports.parse = function parse(url) {
62 | var preAndPostHash = url.split('#');
63 | var hash = preAndPostHash[1] || null;
64 | var pathAndQuerystring = preAndPostHash[0].split('?');
65 | var querystring = pathAndQuerystring[1] || '';
66 | var path = pathAndQuerystring[0];
67 |
68 | var scheme = null;
69 | var host = null;
70 | var port = null;
71 | if (path.indexOf('/') > 0) {
72 | var schemeHostAndPath = path.match(/^([A-Za-z]+):\/\/([^\/:]+(?:\:(\d+))?)(\/.*)?$/);
73 | if (schemeHostAndPath) {
74 | scheme = schemeHostAndPath[1];
75 | host = schemeHostAndPath[2];
76 | port = schemeHostAndPath[3] || null;
77 | if (port) {
78 | port = parseInt(port, 10);
79 | }
80 | path = schemeHostAndPath[4];
81 | }
82 | }
83 |
84 | var existingPairs = querystring.split('&');
85 | var params = {};
86 | for (var i = 0; i < existingPairs.length; i++) {
87 | var split = existingPairs[i].split('=');
88 | if (split[0].length) {
89 | params[decodeQuerystringComponent(split[0])] = decodeQuerystringComponent(split[1]);
90 | }
91 | }
92 |
93 | return {scheme: scheme, host: host, port: port, path: path, hash: hash, params: params};
94 | };
95 |
96 |
97 | /**
98 | * Given a base URL, append the parameters to the end of the URL. Updates
99 | * existing params to the new values specified in {params}.
100 | *
101 | * @param {string} url A URL, eg. "/index.html?a=b"
102 | * @param {Object} params The params to append, eg. {'a': 'c'}.
103 | * @return {string} The URL with the params appended, eg. "/index.html?a=c"
104 | */
105 | module.exports.appendParams = function appendParams(url, params) {
106 | var parsed = module.exports.parse(url);
107 |
108 | var newParams = parsed.params;
109 | for (var key in params) {
110 | if (params[key] === null) {
111 | delete newParams[key];
112 | } else {
113 | newParams[key] = params[key];
114 | }
115 | }
116 |
117 | var newPairs = [];
118 | for (var key in newParams) {
119 | var value = newParams[key];
120 | newPairs.push(encodeURIComponent(key) + (typeof value != 'undefined' ? '=' + encodeURIComponent(value) : ''));
121 | }
122 | var newQuerystring = newPairs.join('&');
123 |
124 | var newUrl = parsed.path;
125 | if (newQuerystring.length) {
126 | newUrl += '?' + newQuerystring;
127 | }
128 | if (parsed.hash && parsed.hash.length) {
129 | newUrl += '#' + parsed.hash;
130 | }
131 | if (parsed.scheme && parsed.host) {
132 | newUrl = parsed.scheme + '://' + parsed.host + newUrl;
133 | }
134 |
135 | return newUrl;
136 | };
137 |
--------------------------------------------------------------------------------
/lib/loader/pooledmoduleloader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @license
5 | * (c) 2015 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 | var vm = require('vm');
10 |
11 | var scriptresource = require('./scriptresource');
12 | var TargetEnvironment = scriptresource.TargetEnvironment;
13 |
14 |
15 | function ModuleLoaderPool(size) {
16 | this.size = size;
17 | this.available = size;
18 | this.built = 0;
19 | this.pool = [];
20 | this.waiters = [];
21 | }
22 |
23 | ModuleLoaderPool.prototype.getLoader = function(cb) {
24 | if (this.available) {
25 | this.available--;
26 | var loader = this.pool.shift();
27 | if (!loader) {
28 | loader = new ProgressiveModuleLoader();
29 | this.built++;
30 | //console.log('built new module loader');
31 | }
32 | cb(loader);
33 | } else {
34 | this.waiters.push(cb);
35 | }
36 | };
37 |
38 | ModuleLoaderPool.prototype.release = function(loader) {
39 | this.pool.push(loader);
40 | this.available++;
41 |
42 | if (this.waiters.length) {
43 | var cb = this.waiters.shift();
44 | this.getLoader(cb);
45 | }
46 | };
47 |
48 |
49 |
50 | function ProgressiveModuleLoader() {
51 | // TODO(Taylor): Limit require() usage here to specific modules?
52 | // Or provide a few globally required things?
53 | this.context = vm.createContext({
54 | require: require,
55 | console: console,
56 | });
57 |
58 | this.objectsByResourcePath = {};
59 | this.objectsByModulePath = {};
60 | this.mainResourceByModulePath = {};
61 | }
62 |
63 |
64 | ProgressiveModuleLoader.prototype.loadModule = function(module) {
65 | var allResources = module.buildResourceList();
66 |
67 | for (var i = 0; i < allResources.length; i++) {
68 | var resource = allResources[i];
69 | if (resource.getCssString || resource.resourcePath in this.objectsByResourcePath) {
70 | continue;
71 | }
72 |
73 | if (!resource.includeInEnvironment(TargetEnvironment.SERVER)) {
74 | continue;
75 | }
76 |
77 | // console.log('loading resource:', resource.resourcePath);
78 |
79 | var script = resource.__script__;
80 | if (!script) {
81 | // Errors here bubble up to the try/catch around serveController().
82 | var functionString = resource.getFunctionString();
83 | script = resource.__script__ = vm.createScript(functionString, resource.filePath);
84 | }
85 |
86 | var evaluatedFunction = script.runInContext(this.context);
87 | var evaluatedDependencies = resource.getAbsoluteDependencyPaths().map(function(resourcePath) {
88 | return this.objectsByResourcePath[resourcePath];
89 | }, this);
90 |
91 | var modulePath = resource.resourcePath.split(':')[0];
92 |
93 | var evaluated = evaluatedFunction.apply({}, evaluatedDependencies);
94 | this.objectsByResourcePath[resource.resourcePath] = evaluated;
95 | // This might be set multiple times for multiple resources in a module,
96 | // but will eventually be correct.
97 | this.objectsByModulePath[modulePath] = evaluated;
98 | this.mainResourceByModulePath[modulePath] = resource;
99 | };
100 | };
101 |
102 |
103 |
104 | function LoadedModuleScope(module, pool, loader) {
105 | this.module = module;
106 |
107 | this.pool = pool;
108 | this.loader = loader;
109 | this.loader.loadModule(module);
110 |
111 | this.mainObject = this.loader.objectsByModulePath[module.modulePath];
112 | this.mainObjectResourcePath = this.loader.mainResourceByModulePath[module.modulePath].resourcePath;
113 | }
114 |
115 | LoadedModuleScope.prototype.getObjectByResourcePath = function(resourcePath) {
116 | return this.loader.objectsByResourcePath[resourcePath];
117 | };
118 |
119 | LoadedModuleScope.prototype.getObjectByModulePath = function(modulePath) {
120 | return this.loader.objectsByModulePath[modulePath];
121 | };
122 |
123 | LoadedModuleScope.prototype.release = function() {
124 | if (!this.pool) {
125 | console.log('[skit internal] A loaded module scope was released multiple times.');
126 | }
127 |
128 | this.pool.release(this.loader);
129 | delete this.loader;
130 | delete this.pool;
131 | };
132 |
133 |
134 |
135 | var pools_ = {};
136 |
137 | module.exports = {
138 | setPoolSize: function(name, size) {
139 | pools_[name] = new ModuleLoaderPool(size);
140 | },
141 |
142 | resetPool: function(name) {
143 | pools_[name] = new ModuleLoaderPool(pools_[name].size);
144 | },
145 |
146 | borrowModuleScope: function(name, module, cb, opt_context) {
147 | var myPool = pools_[name];
148 | myPool.getLoader(function(loader) {
149 | var scope = new LoadedModuleScope(module, myPool, loader);
150 | cb.call(opt_context, scope);
151 | });
152 | }
153 | };
154 |
--------------------------------------------------------------------------------
/lib/skit/browser/layout.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 'browser-only';
3 |
4 | /**
5 | * @module
6 | * @license
7 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
8 | * License: MIT
9 | */
10 |
11 |
12 | /** @ignore */
13 | function isGlobal(element) {
14 | return (element === window || element === document || element === document.body);
15 | }
16 |
17 |
18 | /**
19 | * @param {Element|ElementWrapper|Window|Document} A DOM element.
20 | * @return {number} The "scrollTop" value for the given element, ie. how far
21 | * the given element is scrolled. If window, document or body is passed,
22 | * use the browser-appropriate scrollTop measure.
23 | */
24 | function scrollTop(element) {
25 | if (isGlobal(element)) {
26 | return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
27 | }
28 |
29 | return element.scrollTop;
30 | }
31 | module.exports.scrollTop = scrollTop;
32 |
33 |
34 | /**
35 | * @param {Element|ElementWrapper|Window|Document} A DOM element.
36 | * @return {number} The "scrollLeft" value for the given element, ie. how far
37 | * the given element is scrolled. If window, document or body is passed,
38 | * use the browser-appropriate scrollLeft measure.
39 | */
40 | function scrollLeft(element) {
41 | if (isGlobal(element)) {
42 | return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
43 | }
44 |
45 | return element.scrollLeft;
46 | }
47 | module.exports.scrollLeft = scrollLeft;
48 |
49 |
50 | /**
51 | * @param {Element|ElementWrapper|Window|Document} A DOM element.
52 | * @return {number} The "scrollHeight" value for the given element, ie. how
53 | * tall the element is if it had no scroll bar. If window, document or
54 | * body is passed, use the browser-appropriate scrollHeight measure.
55 | */
56 | module.exports.scrollHeight = function(element) {
57 | if (isGlobal(element)) {
58 | if (typeof document.body.scrollHeight !== 'number') {
59 | return document.documentElement.scrollHeight;
60 | }
61 | return document.body.scrollHeight;
62 | }
63 |
64 | return element.scrollHeight;
65 | };
66 |
67 |
68 | /**
69 | * @param {Element|ElementWrapper} A DOM element.
70 | * @return {Element} The offset parent for this element, which is the first
71 | * ancestor element that is not statically positioned.
72 | */
73 | module.exports.offsetParent = function(element) {
74 | element = element.element || element;
75 | return element.offsetParent || element.ownerDocument.body;
76 | };
77 |
78 |
79 | /**
80 | * @param {Element|ElementWrapper} A DOM element.
81 | * @return {{left: number, top: number, width: number, height: number}} The
82 | * current position, width and height of the given element. If the element
83 | * is hidden, returns all zeroes.
84 | */
85 | function boundingRect(element) {
86 | element = element.element || element;
87 |
88 | var rect = null;
89 | if (element.getClientRects().length) {
90 | rect = element.getBoundingClientRect();
91 | }
92 |
93 | if (!(rect && (rect.width || rect.height))) {
94 | return {top: 0, left: 0, width: 0, height: 0};
95 | }
96 |
97 | var document = element.ownerDocument;
98 | var window = document.defaultView;
99 | var documentElement = document.documentElement;
100 |
101 | return {
102 | top: rect.top + window.pageYOffset - documentElement.clientTop,
103 | left: rect.left + window.pageXOffset - documentElement.clientLeft,
104 | height: rect.height,
105 | width: rect.width
106 | };
107 | }
108 | module.exports.boundingRect = boundingRect;
109 |
110 |
111 | /**
112 | * @param {Element|ElementWrapper} A DOM element.
113 | * @return {{left: number, top: number}} The current position of the given element.
114 | */
115 | module.exports.position = function(element) {
116 | var rect = boundingRect(element);
117 | return {top: rect.top, left: rect.left};
118 | };
119 |
120 |
121 | /**
122 | * @param {Element|ElementWrapper|Window|Document} A DOM element.
123 | * @return {number} The outer width of the given element, which includes
124 | * padding and borders.
125 | */
126 | module.exports.width = function(element) {
127 | element = element.element || element;
128 | if (isGlobal(element)) {
129 | return document.documentElement.clientWidth || window.innerWidth;
130 | }
131 | return boundingRect(element).width;
132 | };
133 |
134 |
135 | /**
136 | * @param {Element|ElementWrapper|Window|Document} A DOM element.
137 | * @return {number} The outer height of the given element, which includes
138 | * padding and borders.
139 | */
140 | module.exports.height = function(element) {
141 | element = element.element || element;
142 | if (isGlobal(element)) {
143 | return document.documentElement.clientHeight || window.innerHeight;
144 | }
145 | return boundingRect(element).height;
146 | };
147 |
--------------------------------------------------------------------------------
/lib/skit/thirdparty/cookies.js:
--------------------------------------------------------------------------------
1 | 'browser-only';
2 |
3 | /*!
4 | * Cookies.js - 0.3.1
5 | * Wednesday, April 24 2013 @ 2:28 AM EST
6 | *
7 | * Copyright (c) 2013, Scott Hamper
8 | * Licensed under the MIT license,
9 | * http://www.opensource.org/licenses/MIT
10 | */
11 | define([], function () {
12 | 'use strict';
13 |
14 | var Cookies = function (key, value, options) {
15 | return arguments.length === 1 ?
16 | Cookies.get(key) : Cookies.set(key, value, options);
17 | };
18 |
19 | // Allows for setter injection in unit tests
20 | Cookies._document = document;
21 | Cookies._navigator = navigator;
22 |
23 | Cookies.defaults = {
24 | path: '/'
25 | };
26 |
27 | Cookies.get = function (key) {
28 | if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
29 | Cookies._renewCache();
30 | }
31 |
32 | return Cookies._cache[key];
33 | };
34 |
35 | Cookies.set = function (key, value, options) {
36 | options = Cookies._getExtendedOptions(options);
37 | options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
38 |
39 | Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
40 |
41 | return Cookies;
42 | };
43 |
44 | Cookies.expire = function (key, options) {
45 | return Cookies.set(key, undefined, options);
46 | };
47 |
48 | Cookies._getExtendedOptions = function (options) {
49 | return {
50 | path: options && options.path || Cookies.defaults.path,
51 | domain: options && options.domain || Cookies.defaults.domain,
52 | expires: options && options.expires || Cookies.defaults.expires,
53 | secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure
54 | };
55 | };
56 |
57 | Cookies._isValidDate = function (date) {
58 | return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
59 | };
60 |
61 | Cookies._getExpiresDate = function (expires, now) {
62 | now = now || new Date();
63 | switch (typeof expires) {
64 | case 'number': expires = new Date(now.getTime() + expires * 1000); break;
65 | case 'string': expires = new Date(expires); break;
66 | }
67 |
68 | if (expires && !Cookies._isValidDate(expires)) {
69 | throw new Error('`expires` parameter cannot be converted to a valid Date instance');
70 | }
71 |
72 | return expires;
73 | };
74 |
75 | Cookies._generateCookieString = function (key, value, options) {
76 | key = encodeURIComponent(key);
77 | value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
78 | options = options || {};
79 |
80 | var cookieString = key + '=' + value;
81 | cookieString += options.path ? ';path=' + options.path : '';
82 | cookieString += options.domain ? ';domain=' + options.domain : '';
83 | cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
84 | cookieString += options.secure ? ';secure' : '';
85 |
86 | return cookieString;
87 | };
88 |
89 | Cookies._getCookieObjectFromString = function (documentCookie) {
90 | var cookieObject = {};
91 | var cookiesArray = documentCookie ? documentCookie.split('; ') : [];
92 |
93 | for (var i = 0; i < cookiesArray.length; i++) {
94 | var cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
95 |
96 | if (cookieObject[cookieKvp.key] === undefined) {
97 | cookieObject[cookieKvp.key] = cookieKvp.value;
98 | }
99 | }
100 |
101 | return cookieObject;
102 | };
103 |
104 | Cookies._getKeyValuePairFromCookieString = function (cookieString) {
105 | // "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
106 | var separatorIndex = cookieString.indexOf('=');
107 |
108 | // IE omits the "=" when the cookie value is an empty string
109 | separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
110 |
111 | return {
112 | key: decodeURIComponent(cookieString.substr(0, separatorIndex)),
113 | value: decodeURIComponent(cookieString.substr(separatorIndex + 1))
114 | };
115 | };
116 |
117 | Cookies._renewCache = function () {
118 | Cookies._cache = Cookies._getCookieObjectFromString(Cookies._document.cookie);
119 | Cookies._cachedDocumentCookie = Cookies._document.cookie;
120 | };
121 |
122 | Cookies._areEnabled = function () {
123 | return Cookies.set('cookies.js', 1).get('cookies.js') === '1';
124 | };
125 |
126 | Cookies.enabled = Cookies._areEnabled();
127 |
128 | return Cookies;
129 | });
--------------------------------------------------------------------------------
/lib/skit/platform/iter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @module
5 | * @license
6 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
7 | * License: MIT
8 | */
9 |
10 |
11 |
12 | /**
13 | * An object with a length property that is subscriptable, like an array,
14 | * but which might not be derived from the Array prototype.
15 | *
16 | * @class
17 | * @name ArrayLike
18 | * @property {number} length The length of the array-like object.
19 | */
20 |
21 |
22 | /**
23 | * Iterate over an array-like object with optional context.
24 | *
25 | * @param {ArrayLike} items The array-like object to iterate over.
26 | * @param {Function} fn The function to call with each item from {items}.
27 | * @param {Object=} opt_context The context for {this} inside {fn}.
28 | */
29 | module.exports.forEach = function forEach(items, fn, opt_context) {
30 | var length = items.length || 0;
31 | var stop = false;
32 | var stopFn = function() {
33 | stop = true;
34 | };
35 |
36 | for (var i = 0; i < length && !stop; i++) {
37 | fn.call(opt_context, items[i], i, stopFn);
38 | }
39 | };
40 | var forEach = module.exports.forEach;
41 |
42 |
43 | /**
44 | * Filter objects in {items} based on the result of {opt_fn}.
45 | *
46 | * @param {ArrayLike} items The array-like object to iterate over.
47 | * @param {Function=} opt_fn The function to call with each item from {items},
48 | * which should return a boolean value. If not present, the truthiness of
49 | * the item itself will be used.
50 | * @param {Object=} opt_context The context for {this} inside {opt_fn}.
51 | * @return {Array} A filtered array of items from {items}.
52 | */
53 | module.exports.filter = function filter(items, opt_fn, opt_context) {
54 | var fn = opt_fn || function(item) { return !!item };
55 | var array = [];
56 | forEach(items, function(item, i) {
57 | if (fn.call(opt_context, item, i)) {
58 | array.push(item);
59 | }
60 | });
61 | return array;
62 | };
63 |
64 |
65 | /**
66 | * Map objects in {items} to new values supplied by {fn}.
67 | *
68 | * @param {ArrayLike} items The array-like object to iterate over.
69 | * @param {Function} fn The function to call with each item from {items},
70 | * which should return a new object.
71 | * @param {Object=} opt_context The context for {this} inside {fn}.
72 | * @return {Array} The mapped values from {items}.
73 | */
74 | module.exports.map = function map(items, fn, opt_context) {
75 | var array = [];
76 | var skip = false;
77 | var shouldSkip = function() {
78 | skip = true;
79 | };
80 | forEach(items, function(item, i) {
81 | var mapped = fn.call(opt_context, item, i, shouldSkip);
82 | if (!skip) {
83 | array.push(mapped);
84 | }
85 | skip = false;
86 | });
87 | return array;
88 | };
89 |
90 |
91 | /**
92 | * @param {ArrayLike} array The array to iterate over.
93 | * @param {Object} item The object to find.
94 | * @return {boolean} Whether {array} contains {item}, using == to compare objects.
95 | */
96 | module.exports.contains = function contains(array, item) {
97 | if (!array || !array.length) {
98 | return false;
99 | }
100 |
101 | for (var i = 0; i < array.length; i++) {
102 | if (array[i] == item) {
103 | return true;
104 | }
105 | }
106 | return false;
107 | };
108 |
109 |
110 | /**
111 | * @param {ArrayLike} array The array to iterate over.
112 | * @param {Function} fn The function to call with each item from {items},
113 | * which should return whether the item matches.
114 | * @param {Object=} opt_context The context for {this} inside {fn}.
115 | * @return {number} The index of the item inside {array} if {fn} returned true
116 | * for any of the elements, or -1.
117 | */
118 | module.exports.indexOf = function indexOf(array, fn, opt_context) {
119 | for (var i = 0; i < array.length; i++) {
120 | var item = array[i];
121 | if (fn.call(opt_context, item)) {
122 | return i;
123 | }
124 | }
125 | return -1;
126 | };
127 |
128 |
129 | /**
130 | * @param {ArrayLike} array The array to iterate over.
131 | * @param {Function} fn The function to call with each item from {items},
132 | * which should return whether the item matches.
133 | * @param {Object=} opt_context The context for {this} inside {fn}.
134 | * @return {Object?} The item that {fn} returned true for, if any.
135 | */
136 | module.exports.find = function find(array, fn, opt_context) {
137 | return array[module.exports.indexOf(array, fn, opt_context)];
138 | };
139 |
140 |
141 | /**
142 | * Convert an array-like object to an Array.
143 | *
144 | * @param {ArrayLike} nonArray The non-array to convert.
145 | * @return {Array} The nonArray object as an Array.
146 | */
147 | module.exports.toArray = function toArray(nonArray) {
148 | var result = [];
149 | var length = nonArray.length || 0;
150 | for (var i = 0; i < length; i++) {
151 | result.push(nonArray[i]);
152 | }
153 | return result;
154 | };
155 |
--------------------------------------------------------------------------------
/lib/loader/SkitModule.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @license
5 | * (c) 2014 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 | var fs = require('fs');
10 | var path = require('path');
11 | var util = require('util');
12 |
13 | var NamedNode = require('./NamedNode');
14 | var scriptresource = require('./scriptresource');
15 | var styleresource = require('./styleresource');
16 | var TargetEnvironment = scriptresource.TargetEnvironment;
17 |
18 |
19 | function SkitModule(name, modulePath) {
20 | NamedNode.call(this, name);
21 | this.modulePath = modulePath;
22 |
23 | this.scripts_ = {};
24 | this.styles_ = {};
25 | }
26 | util.inherits(SkitModule, NamedNode);
27 |
28 |
29 | SkitModule.moduleName = function(fullPath) {
30 | var basename = path.basename(fullPath);
31 | // Foo.js, Foo_bar.js, Foo.bar.js, Foo_bar.bz.js -> all belong to the "Foo" module.
32 | var moduleName = basename.split('.').slice(0, 1)[0];
33 | return moduleName.split('_').slice(0, 1)[0];
34 | };
35 |
36 |
37 | SkitModule.prototype.addFile = function(fullPath) {
38 | var basename = path.basename(fullPath);
39 |
40 | // Foo.js -> 'js'
41 | // Foo.html -> 'html'
42 | // Foo_bar.html -> 'bar.html'
43 | // Foo_bar.js -> 'bar'
44 | if (basename.indexOf(this.name) != 0) {
45 | var err = new Error('Invalid module file, does not match module name: ' + this.name);
46 | err.fileName = fullPath;
47 | throw err;
48 | }
49 | var nickname = basename.replace(this.name, '').replace(/^[_.]+/, '').replace(/\.js$/, '');
50 |
51 | var extension = path.extname(fullPath);
52 | var isStyle = false;
53 | var ResourceKlass = scriptresource.getResourceWrapper(extension);
54 | if (!ResourceKlass) {
55 | isStyle = true;
56 | ResourceKlass = styleresource.getResourceWrapper(extension);
57 | }
58 |
59 | if (!ResourceKlass) {
60 | var err = new Error('Invalid resource -- could not identify wrapper: ' + fullPath);
61 | err.fileName = fullPath;
62 | throw err;
63 | }
64 |
65 | var source = fs.readFileSync(fullPath).toString();
66 | var resourcePath = this.modulePath + ':' + nickname;
67 | var resource = new ResourceKlass(fullPath, resourcePath, source);
68 |
69 | if (isStyle) {
70 | this.styles_[nickname] = resource;
71 | } else {
72 | this.scripts_[nickname] = resource;
73 | }
74 | };
75 |
76 |
77 | SkitModule.prototype.getResourceNamed = function(name) {
78 | this.buildResourceList();
79 | return this.scripts_[name] || this.styles_[name];
80 | };
81 |
82 |
83 | SkitModule.prototype.buildResourceList = function() {
84 | if (!this.__resourceList__) {
85 | var mainNickname = 'js';
86 | if (!(mainNickname in this.scripts_)) {
87 | mainNickname = Object.keys(this.scripts_)[0];
88 | }
89 |
90 | var alwaysInclude = this.buildAlwaysIncludeResourceList_();
91 | var resourcesList;
92 | if (mainNickname) {
93 | resourcesList = this.buildResourceListForScriptNamed_(mainNickname);
94 | Array.prototype.splice.apply(resourcesList, [-1, 0].concat(alwaysInclude));
95 | } else {
96 | resourcesList = alwaysInclude;
97 | }
98 | this.__resourceList__ = resourcesList;
99 | }
100 | return this.__resourceList__;
101 | };
102 |
103 |
104 | SkitModule.prototype.buildAlwaysIncludeResourceList_ = function() {
105 | return Object.keys(this.styles_).map(function(k) { return this.styles_[k]; }, this);
106 | };
107 |
108 |
109 | SkitModule.prototype.buildResourceListForScriptNamed_ = function(name) {
110 | var loaded = {};
111 | var all = [];
112 |
113 | var scriptResource = this.scripts_[name];
114 | if (!scriptResource) {
115 | throw new Error('Invalid reference to submodule "' + name + '" in module ' + this.modulePath);
116 | }
117 |
118 | var relativeDependencies = scriptResource.getRelativeDependencyPaths();
119 | var absoluteDependencies = [];
120 |
121 | relativeDependencies.forEach(function(dependencyPath) {
122 | var resources = this.getResourceListForRelativeDependency_(dependencyPath);
123 | if (!resources) {
124 | throw new Error('Invalid dependency: "' + dependencyPath + '" in module: ' + this.modulePath + ':' + name);
125 | }
126 |
127 | var absoluteDependency = resources[resources.length - 1];
128 | absoluteDependencies.push(absoluteDependency.resourcePath);
129 |
130 | resources.forEach(function(resource) {
131 | if (resource.resourcePath in loaded) {
132 | // continue
133 | return;
134 | }
135 |
136 | loaded[resource.resourcePath] = true;
137 | all.push(resource);
138 | });
139 | }, this);
140 |
141 | all.push(scriptResource);
142 | scriptResource.setAbsoluteDependencyPaths(absoluteDependencies);
143 |
144 | return all;
145 | };
146 |
147 |
148 | SkitModule.prototype.getResourceListForRelativeDependency_ = function(relativePath) {
149 | // Inner-module dependency; load that file first.
150 | if (relativePath.indexOf('__module__.') == 0) {
151 | var depNickname = relativePath.replace('__module__.', '');
152 | return this.buildResourceListForScriptNamed_(depNickname);
153 | }
154 |
155 | // Dependency in another module -- find its main object.
156 | var dependency = this.root().findNodeWithPath(relativePath);
157 | if (!dependency || !dependency.buildResourceList) {
158 | return null;
159 | }
160 |
161 | return dependency.buildResourceList();
162 | };
163 |
164 |
165 | module.exports = SkitModule;
166 |
--------------------------------------------------------------------------------
/bin/skit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * @license
5 | * (c) 2015 Cluster Labs, Inc. https://cluster.co/
6 | * License: MIT
7 | */
8 |
9 | var fs = require('fs');
10 | var path = require('path');
11 |
12 | var minimist = require('minimist');
13 |
14 | var skit = require('../../skit');
15 | var loader = require('../lib/loader/loader');
16 |
17 |
18 | function usage(command) {
19 | function usage_path_to_root() {
20 | console.log(' path_to_root - The root path for the skit tree, which')
21 | console.log(' contains "public" serving directory and')
22 | console.log(' serves as the root of skit module paths.')
23 | }
24 | function usage_path_to_optimized_root() {
25 | console.log(' path_to_optimized_root - The root path for the optimized')
26 | console.log(' tree, which will be generated by this')
27 | console.log(' command.')
28 | }
29 |
30 | switch (command) {
31 | case 'run':
32 | console.log('usage: skit run 6 | Featuring nearly 100% shared server and client-side code, 7 | zero configuration, 8 | automatic static resource bundling, 9 | and built-in server-side rendering. 10 |
11 |Skit is a JavaScript framework for building web pages with this controller lifecycle:
20 |Controller.create({
21 | preload: function(done) {
22 | MyAPIClient.getThing(function(thing) {
23 | this.thing = thing;
24 | done();
25 | }, this);
26 | },
27 | render: function() {
28 | return template(this.thing);
29 | },
30 | ready: function() {
31 | events.listen(dom.get('.thing'), 'click', function() {
32 | this.thing.clicked = true;
33 | this.rerender();
34 | }, this);
35 | }
36 | });
37 |
38 | … that execute like this:
39 |… automatically, without having to configure anything.
51 |76 | Skit is good for building web apps on existing HTTP-based APIs, 77 | like the one you probably already built for your mobile app. 78 |
79 |
81 | 83 | Skit is not a full-stack framework, 84 | or even a “Node.js framework” in the typical sense — 85 | it’s more like a client-side framework that also runs 86 | on the server side. 87 |
88 |
172 | 176 | The skit lifecycle starts on the server side, where the server loads 177 | the current page’s controller module and renders a response. 178 | Then, in the browser, the controller module is reconstructed and 179 | the execution continues. 180 |
181 | 182 |preload() — Loads data from the backend APIload() — Sets up state after data is loadedrender() — Generate <title> text and <body> HTML for the resulting pagepreloadrender↓ HTTP transport ↓
202 | 203 |preload, serialized as JSONload() — Sets up state after data is loaded (now in the client)ready() — Sets up client-side event handlers for clicks, scrolling, etc.Install skit and run an example project to get a feel for it:
242 |$ npm install skit 243 | $ ./node_modules/.bin/skit skeleton skit-example 244 | $ ./node_modules/.bin/skit run skit-example --debug245 |
246 | Also check out Getting Started for a more 247 | comprehensive walkthrough. 248 |
249 |254 | Visit https://launchkit.io/ and log in to see 255 | skit in action. Inspect the source returned from the server to find bits of skit magic. 256 |
257 |267 | Yeah. There are several reasons why: 268 |
269 |<my favorite framework>?
289 | 291 | Maybe! It won’t help too much if your existing client-side 292 | framework of choice depends on DOM manipulation for rendering, however. 293 |
294 |295 | I have successfully integrated React (and automagic .jsx compilation) 296 | in this example project; 297 | I’m no React expert, but it seems pretty cool. 298 |
299 |303 | Now I do! 304 | Tusen tack! Var är toaletten? 305 |
306 |