pakyow 0.11.3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +3 -19
  3. data/README.md +96 -23
  4. data/lib/pakyow/all.rb +12 -0
  5. data/lib/pakyow/version.rb +5 -1
  6. metadata +49 -98
  7. data/bin/pakyow +0 -4
  8. data/lib/generators/pakyow/app/app_generator.rb +0 -93
  9. data/lib/generators/pakyow/app/templates/Gemfile +0 -18
  10. data/lib/generators/pakyow/app/templates/README.md +0 -28
  11. data/lib/generators/pakyow/app/templates/Rakefile +0 -3
  12. data/lib/generators/pakyow/app/templates/app/lib/bindings.rb +0 -3
  13. data/lib/generators/pakyow/app/templates/app/lib/helpers.rb +0 -3
  14. data/lib/generators/pakyow/app/templates/app/lib/routes.rb +0 -7
  15. data/lib/generators/pakyow/app/templates/app/setup.rb +0 -24
  16. data/lib/generators/pakyow/app/templates/app/views/_templates/default.html +0 -33
  17. data/lib/generators/pakyow/app/templates/app/views/index.html +0 -96
  18. data/lib/generators/pakyow/app/templates/config.ru +0 -5
  19. data/lib/generators/pakyow/app/templates/env +0 -1
  20. data/lib/generators/pakyow/app/templates/env.example +0 -3
  21. data/lib/generators/pakyow/app/templates/gitignore +0 -8
  22. data/lib/generators/pakyow/app/templates/public/apple-touch-icon-precomposed.png +0 -0
  23. data/lib/generators/pakyow/app/templates/public/apple-touch-icon.png +0 -0
  24. data/lib/generators/pakyow/app/templates/public/favicon.ico +0 -0
  25. data/lib/generators/pakyow/app/templates/public/scripts/ring/LICENSE +0 -20
  26. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.js +0 -14
  27. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.min.js +0 -1
  28. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.js +0 -34
  29. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.min.js +0 -1
  30. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/loader.js +0 -9
  31. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/loader.min.js +0 -1
  32. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.js +0 -98
  33. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.min.js +0 -1
  34. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.js +0 -70
  35. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.min.js +0 -1
  36. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.js +0 -154
  37. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.min.js +0 -1
  38. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/notifier.js +0 -29
  39. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/notifier.min.js +0 -1
  40. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.js +0 -1936
  41. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.min.js +0 -1
  42. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/LICENSE +0 -20
  43. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/VERSION +0 -1
  44. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/reset.css +0 -2
  45. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/structure.css +0 -2
  46. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/syntax.css +0 -2
  47. data/lib/generators/pakyow/app/templates/public/styles/pakyow-css/theme.css +0 -2
  48. data/lib/generators/pakyow/app/templates/rspec +0 -3
  49. data/lib/generators/pakyow/app/templates/spec/integration/app_spec.rb +0 -17
  50. data/lib/generators/pakyow/app/templates/spec/spec_helper.rb +0 -7
  51. data/lib/pakyow.rb +0 -12
  52. data/lib/pakyow/command_line_interface.rb +0 -79
  53. data/lib/pakyow/commands/console.rb +0 -27
  54. data/lib/pakyow/commands/console_methods.rb +0 -10
  55. data/lib/pakyow/commands/server.rb +0 -38
@@ -1,9 +0,0 @@
1
- pw.component.register('loader', function (view) {
2
- this.listen('socket:loading', function () {
3
- view.node.classList.add('ui-show');
4
- });
5
-
6
- this.listen('socket:loaded', function () {
7
- view.node.classList.remove('ui-show');
8
- });
9
- });
@@ -1 +0,0 @@
1
- pw.component.register("loader",function(o){this.listen("socket:loading",function(){o.node.classList.add("ui-show")}),this.listen("socket:loaded",function(){o.node.classList.remove("ui-show")})});
@@ -1,98 +0,0 @@
1
- pw.component.register('modal', function (view, config, name, id) {
2
- var self = this;
3
- var channel = 'modal:' + id;
4
- var blinder, modal;
5
- var blinderTemplate;
6
-
7
- if (blinderTemplate = document.querySelector('*[data-template="ui-modal-blinder"]')) {
8
- blinderTemplate = blinderTemplate.cloneNode(true);
9
- }
10
-
11
- this.listen(channel + ':navigator:enter', function (response) {
12
- if (!blinder) {
13
- if (blinderTemplate) {
14
- blinder = blinderTemplate.cloneNode(true);
15
- modal = blinder.querySelector('*[data-template="ui-modal-content"]');
16
- document.body.appendChild(blinder);
17
-
18
- blinder.removeAttribute('data-template');
19
- modal.removeAttribute('data-template');
20
- } else {
21
- blinder = document.createElement('DIV');
22
- blinder.classList.add('ui-modal-blinder');
23
-
24
- modal = document.createElement('DIV');
25
- modal.classList.add('ui-modal');
26
-
27
- blinder.appendChild(modal);
28
- document.body.appendChild(blinder);
29
- }
30
-
31
- blinder.addEventListener('click', function (evt) {
32
- if (evt.target === blinder) {
33
- evt.preventDefault();
34
- self.close();
35
-
36
- var uri = window.location.pathname;
37
-
38
- var opts = {
39
- uri: uri
40
- };
41
-
42
- window.history.pushState(opts, uri, uri);
43
- }
44
- });
45
- }
46
-
47
- modal.innerHTML = response.body;
48
- pw.component.findAndInit(blinder);
49
-
50
- blinder.classList.add('ui-appear');
51
- });
52
-
53
- this.listen(channel + ':navigator:exit', function () {
54
- self.close();
55
- });
56
-
57
- this.listen(channel + ':navigator:boot', function (uri) {
58
- self.load(uri);
59
- });
60
-
61
- view.node.addEventListener('click', function (evt) {
62
- evt.preventDefault();
63
- self.load(config.href || this.href);
64
- return false;
65
- });
66
-
67
- this.load = function (uri) {
68
- if (!window.socket) {
69
- document.location = uri;
70
- return;
71
- }
72
-
73
- var opts = {
74
- uri: uri,
75
- context: 'modal:' + id
76
- }
77
-
78
- if (config.container) {
79
- opts.container = config.container;
80
- }
81
-
82
- if (config.partial) {
83
- opts.partial = config.partial;
84
- }
85
-
86
- window.history.pushState(opts, uri, uri);
87
- };
88
-
89
- this.close = function () {
90
- if (!blinder || !modal) {
91
- return;
92
- }
93
-
94
- pw.node.remove(blinder);
95
- blinder = null;
96
- modal = null;
97
- };
98
- });
@@ -1 +0,0 @@
1
- pw.component.register("modal",function(t,e,n,o){var a,i,d,l=this,r="modal:"+o;(d=document.querySelector('*[data-template="ui-modal-blinder"]'))&&(d=d.cloneNode(!0)),this.listen(r+":navigator:enter",function(t){a||(d?(a=d.cloneNode(!0),i=a.querySelector('*[data-template="ui-modal-content"]'),document.body.appendChild(a),a.removeAttribute("data-template"),i.removeAttribute("data-template")):(a=document.createElement("DIV"),a.classList.add("ui-modal-blinder"),i=document.createElement("DIV"),i.classList.add("ui-modal"),a.appendChild(i),document.body.appendChild(a)),a.addEventListener("click",function(t){if(t.target===a){t.preventDefault(),l.close();var e=window.location.pathname,n={uri:e};window.history.pushState(n,e,e)}})),i.innerHTML=t.body,pw.component.findAndInit(a),a.classList.add("ui-appear")}),this.listen(r+":navigator:exit",function(){l.close()}),this.listen(r+":navigator:boot",function(t){l.load(t)}),t.node.addEventListener("click",function(t){return t.preventDefault(),l.load(e.href||this.href),!1}),this.load=function(t){if(!window.socket)return void(document.location=t);var n={uri:t,context:"modal:"+o};e.container&&(n.container=e.container),e.partial&&(n.partial=e.partial),window.history.pushState(n,t,t)},this.close=function(){a&&i&&(pw.node.remove(a),a=null,i=null)}});
@@ -1,70 +0,0 @@
1
- pw.component.register('mutable', function (view, config) {
2
- this.mutation = function (mutation) {
3
- // no socket, just submit the form
4
- if (!window.socket) {
5
- view.node.submit();
6
- return;
7
- }
8
-
9
- var datum = pw.util.dup(mutation);
10
- delete datum.__nested;
11
- delete datum.scope;
12
- delete datum.id;
13
-
14
- var message = {
15
- action: 'call-route'
16
- };
17
-
18
- if (view.node.tagName === 'FORM') {
19
- if (view.node.querySelector('input[type="file"]')) {
20
- // file uploads over websocket are not supported
21
- view.node.submit();
22
- return;
23
- }
24
-
25
- var method;
26
- var $methodOverride = view.node.querySelector('input[name="_method"]');
27
- if ($methodOverride) {
28
- method = $methodOverride.value;
29
- } else {
30
- method = view.node.getAttribute('method');
31
- }
32
-
33
- message.method = method;
34
- message.uri = view.node.getAttribute('action');
35
- message.input = pw.node.serialize(view.node);
36
- } else {
37
- //TODO deduce uri / method
38
-
39
- var input = {};
40
- input[mutation.scope] = datum;
41
- message.input = input;
42
- }
43
-
44
- var self = this;
45
- window.socket.send(message, function (res) {
46
- if (res.status === 302) {
47
- var dest = res.headers.Location;
48
-
49
- if (dest == window.location.pathname && (!window.context || window.context.name !== 'default')) {
50
- history.pushState({ uri: dest }, dest, dest);
51
- } else {
52
- //TODO trigger a response:redirect instead and let navigator subscribe
53
- history.pushState({ uri: dest }, dest, dest);
54
- }
55
- } else if (res.status === 400) {
56
- // bad request
57
- pw.component.broadcast('response:received', { response: res });
58
- return;
59
- } else {
60
- self.state.rollback();
61
- }
62
-
63
- pw.component.broadcast('response:received', { response: res });
64
-
65
- if (config.revert !== 'false') {
66
- self.revert();
67
- }
68
- });
69
- }
70
- });
@@ -1 +0,0 @@
1
- pw.component.register("mutable",function(e,t){this.mutation=function(o){if(!window.socket)return void e.node.submit();var n=pw.util.dup(o);delete n.__nested,delete n.scope,delete n.id;var i={action:"call-route"};if("FORM"===e.node.tagName){if(e.node.querySelector('input[type="file"]'))return void e.node.submit();var r,s=e.node.querySelector('input[name="_method"]');r=s?s.value:e.node.getAttribute("method"),i.method=r,i.uri=e.node.getAttribute("action"),i.input=pw.node.serialize(e.node)}else{var a={};a[o.scope]=n,i.input=a}var d=this;window.socket.send(i,function(e){if(302===e.status){var o=e.headers.Location;o!=window.location.pathname||window.context&&"default"===window.context.name?history.pushState({uri:o},o,o):history.pushState({uri:o},o,o)}else{if(400===e.status)return void pw.component.broadcast("response:received",{response:e});d.state.rollback()}pw.component.broadcast("response:received",{response:e}),"false"!==t.revert&&d.revert()})}});
@@ -1,154 +0,0 @@
1
- function boot() {
2
- if (!window.socket) {
3
- setTimeout(boot, 100);
4
- return;
5
- }
6
-
7
- if (window.location.hash) {
8
- var arr = window.location.hash.split('#:')[1].split('/');
9
- var context = arr.shift();
10
- var uri = arr.join('/');
11
-
12
- pw.component.broadcast(context + ':navigator:boot', uri);
13
- }
14
- }
15
-
16
- (function(history) {
17
- pw.init.register(boot);
18
-
19
- if (history) {
20
- var hasPushed = false;
21
- var pushState = history.pushState;
22
-
23
- history.pushState = function(state, title, uri) {
24
- hasPushed = true;
25
-
26
- if (typeof history.onpushstate == "function") {
27
- history.onpushstate({ state: state });
28
- }
29
-
30
- if (uri == window.location.pathname) {
31
- pw.component.broadcast(window.context.name + ':navigator:exit');
32
-
33
- window.context = {
34
- _state: state,
35
- name: 'default',
36
- uri: window.location.href
37
- };
38
-
39
- state.r_uri = uri;
40
- } else {
41
- handleState(state, 'forward');
42
- }
43
-
44
- return pushState.apply(history, [state, title, state.r_uri]);
45
- }
46
-
47
- window.onpopstate = function (evt) {
48
- if (!hasPushed) {
49
- return;
50
- }
51
-
52
- var state = evt.state;
53
-
54
- if (!state) {
55
- state = {};
56
- }
57
-
58
- if (!state.uri) {
59
- state.uri = window.context.uri;
60
- }
61
-
62
- handleState(state, 'back');
63
- }
64
- } else {
65
- // unsupported
66
- }
67
- })(window.history);
68
-
69
- window.context = {
70
- name: 'default',
71
- uri: window.location.href
72
- };
73
-
74
- function handleState(state, direction) {
75
- var uri = state.uri || state.url;
76
-
77
- // socket isn't ready, so just send 'em to the url
78
- if (!window.socket) {
79
- document.location = uri;
80
- return;
81
- }
82
-
83
- uri = uri.replace(document.location.origin, '');
84
- pw.component.broadcast('navigator:change', { uri: uri });
85
-
86
- if (state.context) {
87
- state.r_uri = document.location.pathname + '#:' + state.context + '/' + uri;
88
-
89
- window.context = {
90
- _state: state,
91
- name: state.context,
92
- uri: state.r_uri,
93
- container: state.container,
94
- partial: state.partial
95
- };
96
- } else {
97
- state.r_uri = uri;
98
-
99
- if (window.context.name !== 'default') {
100
- if (direction === 'back') {
101
- // we are leaving a context
102
- pw.component.broadcast(window.context.name + ':navigator:exit');
103
-
104
- window.context = {
105
- name: 'default',
106
- uri: state.uri
107
- };
108
-
109
- return;
110
- } else {
111
- // navigate in context
112
- state.r_uri = document.location.pathname + '#:' + window.context.name + '/' + uri;
113
- state.context = window.context.name;
114
- state.container = window.context.container;
115
- state.partial = window.context.partial;
116
- }
117
- }
118
- }
119
-
120
- var opts = {
121
- uri: uri,
122
- action: 'call-route',
123
- method: 'get'
124
- };
125
-
126
- if (state.container) {
127
- opts.container = state.container;
128
- }
129
-
130
- if (state.partial) {
131
- opts.partial = state.partial;
132
- }
133
-
134
- window.socket.send(opts, function (payload) {
135
- if (state.context) {
136
- pw.component.broadcast(state.context + ':navigator:enter', payload);
137
- } else {
138
- var body = payload.body[0];
139
-
140
- if (body.match(/<title>/)) {
141
- var title = body.split(/<title>/)[1].split('</title>')[0];
142
- document.querySelector('title').innerHTML = title;
143
- }
144
-
145
- var doc = document.documentElement.cloneNode();
146
- doc.innerHTML = body;
147
-
148
- document.body.innerHTML = doc.querySelector('body').innerHTML;
149
- pw.component.findAndInit(document.querySelectorAll('body')[0]);
150
-
151
- document.body.scrollTop = document.documentElement.scrollTop = 0;
152
- }
153
- });
154
- }
@@ -1 +0,0 @@
1
- function boot(){if(!window.socket)return void setTimeout(boot,100);if(window.location.hash){var t=window.location.hash.split("#:")[1].split("/"),n=t.shift(),o=t.join("/");pw.component.broadcast(n+":navigator:boot",o)}}function handleState(t,n){var o=t.uri||t.url;if(!window.socket)return void(document.location=o);if(o=o.replace(document.location.origin,""),pw.component.broadcast("navigator:change",{uri:o}),t.context)t.r_uri=document.location.pathname+"#:"+t.context+"/"+o,window.context={_state:t,name:t.context,uri:t.r_uri,container:t.container,partial:t.partial};else if(t.r_uri=o,"default"!==window.context.name){if("back"===n)return pw.component.broadcast(window.context.name+":navigator:exit"),void(window.context={name:"default",uri:t.uri});t.r_uri=document.location.pathname+"#:"+window.context.name+"/"+o,t.context=window.context.name,t.container=window.context.container,t.partial=window.context.partial}var e={uri:o,action:"call-route",method:"get"};t.container&&(e.container=t.container),t.partial&&(e.partial=t.partial),window.socket.send(e,function(n){if(t.context)pw.component.broadcast(t.context+":navigator:enter",n);else{var o=n.body[0];if(o.match(/<title>/)){var e=o.split(/<title>/)[1].split("</title>")[0];document.querySelector("title").innerHTML=e}var i=document.documentElement.cloneNode();i.innerHTML=o,document.body.innerHTML=i.querySelector("body").innerHTML,pw.component.findAndInit(document.querySelectorAll("body")[0]),document.body.scrollTop=document.documentElement.scrollTop=0}})}!function(t){if(pw.init.register(boot),t){var n=!1,o=t.pushState;t.pushState=function(e,i,a){return n=!0,"function"==typeof t.onpushstate&&t.onpushstate({state:e}),a==window.location.pathname?(pw.component.broadcast(window.context.name+":navigator:exit"),window.context={_state:e,name:"default",uri:window.location.href},e.r_uri=a):handleState(e,"forward"),o.apply(t,[e,i,e.r_uri])},window.onpopstate=function(t){if(n){var o=t.state;o||(o={}),o.uri||(o.uri=window.context.uri),handleState(o,"back")}}}}(window.history),window.context={name:"default",uri:window.location.href};
@@ -1,29 +0,0 @@
1
- pw.component.register('notifier', function (view, config) {
2
- var that = this;
3
-
4
- this.listen('notification:published', function (payload) {
5
- that.show(payload.notification);
6
- });
7
-
8
- this.listen('response:received', function (payload) {
9
- //TODO support notification type and add a class based on it for styling
10
- var notification = payload.response.headers['Pakyow-Notify'];
11
-
12
- if (notification) {
13
- that.show(notification);
14
- }
15
- });
16
-
17
- view.node.addEventListener('click', function (evt) {
18
- view.node.classList.add('hide');
19
- });
20
-
21
- this.message = function (channel, payload) {
22
- that.show(payload.notification);
23
- };
24
-
25
- this.show = function (notification) {
26
- view.node.innerText = notification;
27
- view.node.classList.remove('hide');
28
- };
29
- });
@@ -1 +0,0 @@
1
- pw.component.register("notifier",function(i,n){var e=this;this.listen("notification:published",function(i){e.show(i.notification)}),this.listen("response:received",function(i){var n=i.response.headers["Pakyow-Notify"];n&&e.show(n)}),i.node.addEventListener("click",function(n){i.node.classList.add("hide")}),this.message=function(i,n){e.show(n.notification)},this.show=function(n){i.node.innerText=n,i.node.classList.remove("hide")}});
@@ -1,1936 +0,0 @@
1
- var pw = {
2
- version: '0.2.4'
3
- };
4
-
5
- (function() {
6
- pw.util = {
7
- guid: function () {
8
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
9
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
10
- return v.toString(16);
11
- });
12
- },
13
-
14
- dup: function (object) {
15
- return JSON.parse(JSON.stringify(object));
16
- }
17
- };
18
- var fns = [];
19
-
20
- pw.init = {
21
- register: function (fn) {
22
- fns.push(fn);
23
- }
24
- };
25
-
26
- document.addEventListener("DOMContentLoaded", function() {
27
- fns.forEach(function (fn) {
28
- fn();
29
- });
30
- });
31
- var sigAttrs = ['data-scope', 'data-prop'];
32
- var valuelessTags = ['SELECT'];
33
- var selfClosingTags = ['AREA', 'BASE', 'BASEFONT', 'BR', 'HR', 'INPUT', 'IMG', 'LINK', 'META'];
34
-
35
- pw.node = {
36
- // returns the value of the node
37
- value: function (node) {
38
- if (node.tagName === 'INPUT') {
39
- if (node.type === 'checkbox') {
40
- if (node.checked) {
41
- return node.value ? node.value : true;
42
- } else {
43
- return false;
44
- }
45
- }
46
-
47
- return node.value;
48
- } else if (node.tagName === 'TEXTAREA') {
49
- return node.value;
50
- } else if (node.tagName === 'SELECT') {
51
- return node.value;
52
- }
53
-
54
- return node.textContent.trim();
55
- },
56
-
57
- /*
58
- Returns a representation of the node's state. Example:
59
-
60
- <div data-scope="list" data-id="1">
61
- <div data-scope="task" data-id="1">
62
- <label data-prop="desc">
63
- foo
64
- </label>
65
- </div>
66
- </div>
67
-
68
- [ [ { node: ..., id: '1', scope: 'list' }, [ { node: ..., id: '1', scope: 'task' }, [ { node: ..., prop: 'body' } ] ] ] ]
69
- */
70
-
71
- significant: function(node, arr) {
72
- if(node === document) {
73
- node = document.getElementsByTagName('body')[0];
74
- }
75
-
76
- if(arr === undefined) {
77
- arr = [];
78
- }
79
-
80
- var sig, nArr;
81
-
82
- if(sig = pw.node.isSignificant(node)) {
83
- nArr = [];
84
- arr.push([{ node: sig[0], type: sig[1] }, nArr]);
85
- } else {
86
- nArr = arr;
87
- }
88
-
89
- pw.node.toA(node.children).forEach(function (child) {
90
- pw.node.significant(child, nArr);
91
- });
92
-
93
- return arr;
94
- },
95
-
96
- // returns node and an indication of it's significance
97
- // (e.g value of scope/prop); returns false otherwise
98
- isSignificant: function(node) {
99
- var attr = sigAttrs.find(function (a) {
100
- return node.hasAttribute(a);
101
- });
102
-
103
- if (attr) {
104
- return [node, attr.split('-')[1]];
105
- } else {
106
- return false;
107
- }
108
- },
109
-
110
- mutable: function (node) {
111
- pw.node.significant(node).flatten().filter(function (n) {
112
- return pw.node.isMutable(n.node);
113
- }).map(function (n) {
114
- return n.node;
115
- });
116
- },
117
-
118
- // returns true if the node can mutate via interaction
119
- isMutable: function(node) {
120
- var tag = node.tagName;
121
- return tag === 'FORM' || (tag === 'INPUT' && !node.disabled);
122
- },
123
-
124
- // triggers event name on node with data
125
- trigger: function (evtName, node, data) {
126
- var evt = document.createEvent('Event');
127
- evt.initEvent(evtName, true, true);
128
-
129
- node._evtData = data;
130
- node.dispatchEvent(evt);
131
- },
132
-
133
- // replaces an event listener's callback for node by name
134
- replaceEventListener: function (eventName, node, cb) {
135
- node.removeEventListener(eventName);
136
- node.addEventListener(eventName, cb);
137
- },
138
-
139
- inForm: function (node) {
140
- if (node.tagName === 'FORM') {
141
- return true;
142
- }
143
-
144
- var next = node.parentNode;
145
- if (next && next !== document) {
146
- return pw.node.inForm(next);
147
- }
148
- },
149
-
150
- // finds and returns component for node
151
- component: function (node) {
152
- if (node.getAttribute('data-ui')) {
153
- return node;
154
- }
155
-
156
- var next = node.parentNode;
157
- if (next && next !== document) {
158
- return pw.node.component(next);
159
- }
160
- },
161
-
162
- // finds and returns scope for node
163
- scope: function (node) {
164
- if (node.getAttribute('data-scope')) {
165
- return node;
166
- }
167
-
168
- var next = node.parentNode;
169
- if (next && next !== document) {
170
- return pw.node.scope(next);
171
- }
172
- },
173
-
174
- // returns the name of the scope for node
175
- scopeName: function (node) {
176
- if (node.getAttribute('data-scope')) {
177
- return node.getAttribute('data-scope');
178
- }
179
-
180
- var next = node.parentNode;
181
- if (next && next !== document) {
182
- return pw.node.scopeName(next);
183
- }
184
- },
185
-
186
- // finds and returns prop for node
187
- prop: function (node) {
188
- if (node.getAttribute('data-prop')) {
189
- return node;
190
- }
191
-
192
- var next = node.parentNode;
193
- if (next && next !== document) {
194
- return pw.node.prop(next);
195
- }
196
- },
197
-
198
- // returns the name of the prop for node
199
- propName: function (node) {
200
- if (node.getAttribute('data-prop')) {
201
- return node.getAttribute('data-prop');
202
- }
203
-
204
- var next = node.parentNode;
205
- if (next && next !== document) {
206
- return pw.node.propName(next);
207
- }
208
- },
209
-
210
- // returns the name of the version for node
211
- versionName: function (node) {
212
- if (node.hasAttribute('data-version')) {
213
- return node.getAttribute('data-version');
214
- }
215
- },
216
-
217
- // creates a context in which view manipulations can occur
218
- invoke: function(node, cb) {
219
- cb.call(node);
220
- },
221
-
222
- invokeWithData: function(node, data, cb) {
223
- if (pw.node.isNodeList(node)) {
224
- node = pw.node.toA(node);
225
- }
226
-
227
- node = Array.ensure(node);
228
- data = Array.ensure(data);
229
-
230
- node.forEach(function (e, i) {
231
- cb.call(e, data[i]);
232
- });
233
- },
234
-
235
- match: function(node, data) {
236
- if (pw.node.isNodeList(node)) {
237
- node = pw.node.toA(node);
238
- }
239
-
240
- node = Array.ensure(node);
241
- data = Array.ensure(data);
242
-
243
- var collection = data.reduce(function (c, dm, i) {
244
- // get the view, or if we're out just use the last one
245
- var v = n[i] || n[n.length - 1];
246
-
247
- var dv = v.cloneNode(true);
248
- v.parentNode.insertBefore(dv);
249
- return c.concat([dv])
250
- }, []);
251
-
252
- node.forEach(function (o) {
253
- o.parentNode.removeChild(o);
254
- });
255
-
256
- return collection;
257
- },
258
-
259
- repeat: function(node, data, cb) {
260
- pw.node.invokeWithData(pw.node.match(node, data), data, cb);
261
- },
262
-
263
- // binds an object to a node
264
- bind: function (data, node, cb) {
265
- var scope = pw.node.findBindings(node)[0];
266
-
267
- pw.node.invokeWithData(node, data, function(dm) {
268
- if (!dm) {
269
- return;
270
- }
271
-
272
- if(dm.id) {
273
- this.setAttribute('data-id', dm.id);
274
- }
275
-
276
- pw.node.bindDataToScope(dm, scope, node);
277
-
278
- if(!(typeof cb === 'undefined')) {
279
- cb.call(this, dm);
280
- }
281
- });
282
- },
283
-
284
- apply: function (data, node, cb) {
285
- var c = pw.node.match(node, data);
286
- pw.node.bind(data, c, cb);
287
- return c;
288
- },
289
-
290
- findBindings: function (node) {
291
- var bindings = [];
292
- pw.node.breadthFirst(node, function() {
293
- var o = this;
294
-
295
- var scope = o.getAttribute('data-scope');
296
-
297
- if(!scope) {
298
- return;
299
- }
300
-
301
- var props = [];
302
- pw.node.breadthFirst(o, function() {
303
- var so = this;
304
-
305
- // don't go into deeper scopes
306
- if(o != so && so.getAttribute('data-scope')) {
307
- return;
308
- }
309
-
310
- var prop = so.getAttribute('data-prop');
311
-
312
- if(!prop) {
313
- return;
314
- }
315
-
316
- props.push({
317
- prop: prop,
318
- doc: so
319
- });
320
- });
321
-
322
- bindings.push({
323
- scope: scope,
324
- props: props,
325
- doc: o,
326
- });
327
- });
328
-
329
- return bindings;
330
- },
331
-
332
- bindDataToScope: function (data, scope, node) {
333
- if(!data || !scope) {
334
- return;
335
- }
336
-
337
- scope['props'].forEach(function (p) {
338
- k = p['prop'];
339
- v = data[k];
340
-
341
- if(!v) {
342
- v = '';
343
- }
344
-
345
- if(typeof v === 'object') {
346
- pw.node.bindValueToNode(v['__content'], p['doc']);
347
- pw.node.bindAttributesToNode(v['__attrs'], p['doc']);
348
- } else {
349
- pw.node.bindValueToNode(v, p['doc']);
350
- }
351
- });
352
- },
353
-
354
- bindAttributesToNode: function (attrs, node) {
355
- var nAtrs = pw.attrs.init(pw.view.init(node));
356
-
357
- for(var attr in attrs) {
358
- var v = attrs[attr];
359
- if(typeof v === 'function') {
360
- v = v.call(node.getAttribute(attr));
361
- }
362
-
363
- if (v) {
364
- if (v instanceof Array) {
365
- v.forEach(function (attrInstruction) {
366
- nAtrs[attrInstruction[0]](attr, attrInstruction[1]);
367
- });
368
- } else {
369
- nAtrs.set(attr, v);
370
- }
371
- } else {
372
- nAtrs.remove(attr);
373
- }
374
- }
375
- },
376
-
377
- bindValueToNode: function (value, node) {
378
- if(pw.node.isTagWithoutValue(node)) {
379
- return;
380
- }
381
-
382
- //TODO handle other form fields (port from pakyow-presenter)
383
- if (node.tagName === 'INPUT' && node.type === 'checkbox') {
384
- if (value === true || (node.value && value === node.value)) {
385
- node.checked = true;
386
- } else {
387
- node.checked = false;
388
- }
389
- } else if (node.tagName === 'TEXTAREA' || pw.node.isSelfClosingTag(node)) {
390
- node.value = value;
391
- } else {
392
- if (value) {
393
- node.innerHTML = value;
394
- }
395
- }
396
- },
397
-
398
- isTagWithoutValue: function(node) {
399
- return valuelessTags.indexOf(node.tagName) != -1 ? true : false;
400
- },
401
-
402
- isSelfClosingTag: function(node) {
403
- return selfClosingTags.indexOf(node.tagName) != -1 ? true : false;
404
- },
405
-
406
- breadthFirst: function (node, cb) {
407
- var queue = [node];
408
- while (queue.length > 0) {
409
- var subNode = queue.shift();
410
- if (!subNode) continue;
411
- if(typeof subNode == "object" && "nodeType" in subNode && subNode.nodeType === 1 && subNode.cloneNode) {
412
- cb.call(subNode);
413
- }
414
-
415
- var children = subNode.childNodes;
416
- if (children) {
417
- for(var i = 0; i < children.length; i++) {
418
- queue.push(children[i]);
419
- }
420
- }
421
- }
422
- },
423
-
424
- isNodeList: function(nodes) {
425
- return typeof nodes.length !== 'undefined';
426
- },
427
-
428
- byAttr: function (node, attr, value) {
429
- return pw.node.all(node).filter(function (o) {
430
- var ov = o.getAttribute(attr);
431
- return ov !== null && ((typeof value) === 'undefined' || ov == value);
432
- });
433
- },
434
-
435
- setAttr: function (node, attr, value) {
436
- if (attr === 'style') {
437
- value.pairs().forEach(function (kv) {
438
- node.style[kv[0]] = kv[1];
439
- });
440
- } else {
441
- if (attr === 'class') {
442
- value = value.join(' ');
443
- }
444
-
445
- if (attr === 'checked') {
446
- if (value) {
447
- value = 'checked';
448
- } else {
449
- value = '';
450
- }
451
-
452
- node.checked = value;
453
- }
454
-
455
- node.setAttribute(attr, value);
456
- }
457
- },
458
-
459
- all: function (node) {
460
- var arr = [];
461
-
462
- if (!node) {
463
- return arr;
464
- }
465
-
466
- if(document !== node) {
467
- arr.push(node);
468
- }
469
-
470
- return arr.concat(pw.node.toA(node.getElementsByTagName('*')));
471
- },
472
-
473
- before: function (node, newNode) {
474
- node.parentNode.insertBefore(newNode, node);
475
- },
476
-
477
- after: function (node, newNode) {
478
- node.parentNode.insertBefore(newNode, this.nextSibling);
479
- },
480
-
481
- replace: function (node, newNode) {
482
- node.parentNode.replaceChild(newNode, node);
483
- },
484
-
485
- append: function (node, newNode) {
486
- node.appendChild(newNode);
487
- },
488
-
489
- prepend: function (node, newNode) {
490
- node.insertBefore(newNode, node.firstChild);
491
- },
492
-
493
- remove: function (node) {
494
- node.parentNode.removeChild(node);
495
- },
496
-
497
- clear: function (node) {
498
- while (node.firstChild) {
499
- pw.node.remove(node.firstChild);
500
- }
501
- },
502
-
503
- title: function (node, value) {
504
- var titleNode;
505
- if (titleNode = node.getElementsByTagName('title')[0]) {
506
- titleNode.innerText = value;
507
- }
508
- },
509
-
510
- toA: function (nodeSet) {
511
- return Array.prototype.slice.call(nodeSet);
512
- },
513
-
514
- serialize: function (node) {
515
- var json = {};
516
- var working;
517
- var value;
518
- var split, last;
519
- var previous, previous_name;
520
- node.querySelectorAll('input, select, textarea').forEach(function (input) {
521
- working = json;
522
- split = input.name.split('[');
523
- last = split[split.length - 1];
524
- split.forEach(function (name) {
525
- value = pw.node.value(input);
526
-
527
- if (name == ']') {
528
- if (!(previous[previous_name] instanceof Array)) {
529
- previous[previous_name] = [];
530
- }
531
-
532
- if (value) {
533
- previous[previous_name].push(value);
534
- }
535
- }
536
-
537
- if (name != last) {
538
- value = {};
539
- }
540
-
541
- name = name.replace(']', '');
542
-
543
- if (name == '' || name == '_method') {
544
- return;
545
- }
546
-
547
- if (!working[name]) {
548
- working[name] = value;
549
- }
550
-
551
- previous = working;
552
- previous_name = name;
553
- working = working[name];
554
- });
555
- });
556
-
557
- return json;
558
- }
559
- };
560
- pw.attrs = {
561
- init: function (v_or_vs) {
562
- return new pw_Attrs(pw.collection.init(v_or_vs));
563
- }
564
- };
565
-
566
- var attrTypes = {
567
- hash: ['style'],
568
- bool: ['selected', 'checked', 'disabled', 'readonly', 'multiple'],
569
- mult: ['class']
570
- };
571
-
572
- var pw_Attrs = function (collection) {
573
- this.views = collection.views;
574
- };
575
-
576
- pw_Attrs.prototype = {
577
- findType: function (attr) {
578
- if (attrTypes.hash.indexOf(attr) > -1) return 'hash';
579
- if (attrTypes.bool.indexOf(attr) > -1) return 'bool';
580
- if (attrTypes.mult.indexOf(attr) > -1) return 'mult';
581
- return 'text';
582
- },
583
-
584
- findValue: function (view, attr) {
585
- switch (attr) {
586
- case 'class':
587
- return view.node.classList;
588
- case 'style':
589
- return view.node.style;
590
- }
591
-
592
- if (this.findType(attr) === 'bool') {
593
- return view.node.hasAttribute(attr);
594
- } else {
595
- return view.node.getAttribute(attr);
596
- }
597
- },
598
-
599
- set: function (attr, value) {
600
- this.views.forEach(function (view) {
601
- pw.node.setAttr(view.node, attr, value);
602
- });
603
- },
604
-
605
- remove: function (attr) {
606
- this.views.forEach(function (view) {
607
- view.node.removeAttribute(attr);
608
- });
609
- },
610
-
611
- ensure: function (attr, value) {
612
- this.views.forEach(function (view) {
613
- var currentValue = this.findValue(view, attr);
614
-
615
- if (attr === 'class') {
616
- if (!currentValue.contains(value)) {
617
- currentValue.add(value);
618
- }
619
- } else if (attr === 'style') {
620
- value.pairs().forEach(function (kv) {
621
- view.node.style[kv[0]] = kv[1];
622
- });
623
- } else if (this.findType(attr) === 'bool') {
624
- if (!view.node.hasAttribute(attr)) {
625
- pw.node.setAttr(view.node, attr, attr);
626
- }
627
- } else { // just a text attr
628
- var currentValue = view.node.getAttribute(attr) || '';
629
- if (!currentValue.match(value)) {
630
- pw.node.setAttr(view.node, attr, currentValue + value);
631
- }
632
- }
633
- }, this);
634
- },
635
-
636
- deny: function (attr, value) {
637
- this.views.forEach(function (view) {
638
- var currentValue = this.findValue(view, attr);
639
- if (attr === 'class') {
640
- if (currentValue.contains(value)) {
641
- currentValue.remove(value);
642
- }
643
- } else if (attr === 'style') {
644
- value.pairs().forEach(function (kv) {
645
- view.node.style[kv[0]] = view.node.style[kv[0]].replace(kv[1], '');
646
- });
647
- } else if (this.findType(attr) === 'bool') {
648
- if (view.node.hasAttribute(attr)) {
649
- view.node.removeAttribute(attr);
650
- }
651
- } else { // just a text attr
652
- pw.node.setAttr(view.node, attr, view.node.getAttribute(attr).replace(value, ''));
653
- }
654
- }, this);
655
- },
656
-
657
- insert: function (attr, value) {
658
- this.views.forEach(function (view) {
659
- var currentValue = this.findValue(view, attr);
660
-
661
- switch (attr) {
662
- case 'class':
663
- currentValue.add(value);
664
- break;
665
- default:
666
- pw.node.setAttr(view.node, attr, currentValue + value);
667
- break;
668
- }
669
- }, this);
670
- }
671
- };
672
- /*
673
- State related functions.
674
- */
675
-
676
- pw.state = {
677
- build: function (sigArr, parentObj) {
678
- var nodeState;
679
- return sigArr.reduce(function (acc, sig) {
680
- if (nodeState = pw.state.buildForNode(sig, parentObj)) {
681
- acc.push(nodeState);
682
- }
683
-
684
- return acc;
685
- }, []);
686
- },
687
-
688
- buildForNode: function (sigTuple, parentObj) {
689
- var sig = sigTuple[0];
690
- var obj = {};
691
-
692
- if (sig.type === 'scope') {
693
- obj.id = sig.node.getAttribute('data-id');
694
- obj.scope = sig.node.getAttribute('data-scope');
695
- } else if (sig.type === 'prop' && parentObj) {
696
- parentObj[sig.node.getAttribute('data-prop')] = pw.node.value(sig.node);
697
- return;
698
- }
699
-
700
- obj['__nested'] = pw.state.build(sigTuple[1], obj);
701
-
702
- return obj;
703
- },
704
-
705
- // creates and returns a new pw_State for the document or node
706
- init: function (node, observer) {
707
- return new pw_State(node, observer);
708
- }
709
- };
710
-
711
-
712
- /*
713
- pw_State represents the state for a document or node.
714
- */
715
-
716
- var pw_State = function (node) {
717
- this.node = node;
718
- //FIXME storing diffs is probably better than full snapshots
719
- this.snapshots = [];
720
- this.update();
721
- }
722
-
723
- pw_State.prototype = {
724
- update: function () {
725
- this.snapshots.push(pw.state.build(pw.node.significant(this.node)));
726
- },
727
-
728
- // gets the current represented state from the node and diffs it with the current state
729
- diffNode: function (node) {
730
- if (node.hasAttribute('data-ui')) {
731
- return {
732
- '__nested': pw.state.build(pw.node.significant(node))
733
- };
734
- }
735
-
736
- return pw.state.build(pw.node.significant(pw.node.scope(node)))[0];
737
- },
738
-
739
- revert: function () {
740
- var initial = pw.util.dup(this.snapshots[0]);
741
- this.snapshots = [initial];
742
- return initial;
743
- },
744
-
745
- rollback: function () {
746
- this.snapshots.pop();
747
- return this.current();
748
- },
749
-
750
- // returns the current state for a node
751
- node: function (nodeState) {
752
- return this.current.flatten().find(function (state) {
753
- return state.scope === nodeState.scope && state.id === nodeState.id;
754
- });
755
- },
756
-
757
- append: function (state) {
758
- var copy = this.copy();
759
- copy.push(state);
760
- this.snapshots.push(copy);
761
- },
762
-
763
- prepend: function (state) {
764
- var copy = this.copy();
765
- copy.unshift(state);
766
- this.snapshots.push(copy);
767
- },
768
-
769
- remove: function (state) {
770
- var copy = this.copy();
771
- var match = copy.find(function (s) {
772
- return s.id === state.id;
773
- });
774
-
775
- if (match) {
776
- copy.splice(copy.indexOf(match), 1);
777
- this.snapshots.push(copy);
778
- }
779
- },
780
-
781
- copy: function () {
782
- return pw.util.dup(this.current());
783
- },
784
-
785
- current: function () {
786
- return this.snapshots[this.snapshots.length - 1];
787
- },
788
-
789
- initial: function () {
790
- return this.snapshots[0];
791
- }
792
- };
793
- /*
794
- View related functions.
795
- */
796
-
797
- pw.view = {
798
- // creates and returns a new pw_View for the document or node
799
- init: function (node) {
800
- return new pw_View(node);
801
- },
802
-
803
- fromStr: function (str) {
804
- var nodeType = 'div';
805
-
806
- if (str.match(/^<tr/) || str.match(/^<tbody/)) {
807
- nodeType = 'table';
808
- }
809
-
810
- var e = document.createElement(nodeType);
811
- e.innerHTML = str;
812
-
813
- return pw.view.init(e.childNodes[0]);
814
- }
815
- };
816
-
817
- /*
818
- pw_View contains a document with state. It watches for
819
- interactions with the document that trigger mutations
820
- in state. It can also apply state to the view.
821
- */
822
-
823
- var pw_View = function (node) {
824
- this.node = node;
825
- }
826
-
827
- pw_View.prototype = {
828
- clone: function () {
829
- return pw.view.init(this.node.cloneNode(true));
830
- },
831
-
832
- // pakyow api
833
-
834
- title: function (value) {
835
- pw.node.title(this.node, value);
836
- },
837
-
838
- text: function (value) {
839
- this.node.innerText = value;
840
- },
841
-
842
- html: function (value) {
843
- this.node.innerHTML = value
844
- },
845
-
846
- component: function (name) {
847
- return pw.collection.init(
848
- pw.node.byAttr(this.node, 'data-ui', name).reduce(function (views, node) {
849
- return views.concat(pw.view.init(node));
850
- }, []), this);
851
- },
852
-
853
- attrs: function () {
854
- return pw.attrs.init(this);
855
- },
856
-
857
- invoke: function (cb) {
858
- pw.node.invoke(this.node, cb);
859
- },
860
-
861
- match: function (data) {
862
- pw.node.match(this.node, data);
863
- },
864
-
865
- invokeWithData: function (data, cb) {
866
- pw.node.invokeWithData(this.node, data, cb);
867
- },
868
-
869
- repeat: function (data, cb) {
870
- pw.node.repeat(this.node, data, cb);
871
- },
872
-
873
- bind: function (data, cb) {
874
- pw.node.bind(data, this.node, cb);
875
- },
876
-
877
- apply: function (data, cb) {
878
- pw.node.apply(data, this.node, cb);
879
- },
880
-
881
- use: function (version, cb) {
882
- var self = this;
883
-
884
- if (this.node.getAttribute('data-version') != version) {
885
- this.node.setAttribute('data-version', version);
886
-
887
- var lookup = {
888
- scope: this.node.getAttribute('data-scope'),
889
- version: version
890
- };
891
-
892
- window.socket.fetchView(lookup, function (view) {
893
- view.node.setAttribute('data-channel', self.node.getAttribute('data-channel'));
894
- pw.node.replace(self.node, view.node);
895
- self.node = view.node;
896
- cb();
897
- });
898
- } else {
899
- cb();
900
- }
901
- },
902
-
903
- setEndpoint: function (endpoint) {
904
- this.endpoint = endpoint;
905
- return this;
906
- },
907
-
908
- first: function () {
909
- return this;
910
- },
911
-
912
- length: function () {
913
- return 1;
914
- }
915
- };
916
-
917
- // pass through lookups
918
- ['scope', 'prop'].forEach(function (method) {
919
- pw_View.prototype[method] = function (name) {
920
- return pw.collection.init(
921
- pw.node.byAttr(this.node, 'data-' + method, name).reduce(function (views, node) {
922
- return views.concat(pw.view.init(node));
923
- }, []), this, name);
924
- };
925
- });
926
-
927
- // pass through functions without view
928
- ['remove', 'clear', 'versionName'].forEach(function (method) {
929
- pw_View.prototype[method] = function () {
930
- return pw.node[method](this.node);
931
- };
932
- });
933
-
934
- // pass through functions with view
935
- ['after', 'before', 'replace', 'append', 'prepend', 'insert'].forEach(function (method) {
936
- pw_View.prototype[method] = function (view) {
937
- return pw.node[method](this.node, view.node);
938
- };
939
- });
940
- pw.collection = {
941
- init: function (view_or_views, parent, scope) {
942
- if (view_or_views instanceof pw_Collection) {
943
- return view_or_views
944
- } else if (view_or_views.constructor !== Array) {
945
- view_or_views = [view_or_views];
946
- }
947
-
948
- return new pw_Collection(view_or_views, parent, scope);
949
- },
950
-
951
- fromNodes: function (nodes, parent, scope) {
952
- return pw.collection.init(nodes.map(function (node) {
953
- return pw.view.init(node);
954
- }), parent, scope);
955
- }
956
- };
957
-
958
- var pw_Collection = function (views, parent, scope) {
959
- this.views = views;
960
- this.parent = parent;
961
- this._scope = scope;
962
- };
963
-
964
- pw_Collection.prototype = {
965
- clone: function () {
966
- return pw.collection.init(this.views.map(function (view) {
967
- return view.clone();
968
- }));
969
- },
970
-
971
- last: function () {
972
- return this.views[this.length() - 1];
973
- },
974
-
975
- first: function () {
976
- return this.views[0];
977
- },
978
-
979
- removeView: function(view) {
980
- var index = this.views.indexOf(view);
981
-
982
- if (index > -1) {
983
- this.views.splice(index, 1)[0].remove();
984
- }
985
- },
986
-
987
- addView: function(view_or_views) {
988
- var views = [];
989
-
990
- if (view_or_views instanceof pw_Collection) {
991
- views = view_or_views.views;
992
- } else {
993
- views.push(view_or_views);
994
- }
995
-
996
- if (this.length() > 0) {
997
- views.forEach(function (view) {
998
- pw.node.after(this.last().node, view.node);
999
- }, this);
1000
- } else if (this.parent) {
1001
- views.forEach(function (view) {
1002
- this.parent.append(view);
1003
- }, this);
1004
- }
1005
-
1006
- this.views = this.views.concat(views);
1007
- },
1008
-
1009
- order: function (orderedIds) {
1010
- orderedIds.forEach(function (id) {
1011
- if (!id) {
1012
- return;
1013
- }
1014
-
1015
- var match = this.views.find(function (view) {
1016
- return view.node.getAttribute('data-id') == id.toString();
1017
- });
1018
-
1019
- if (match) {
1020
- match.node.parentNode.appendChild(match.node);
1021
-
1022
- // also reorder the list of views
1023
- var i = this.views.indexOf(match);
1024
- this.views.splice(i, 1);
1025
- this.views.push(match);
1026
- }
1027
- }, this);
1028
- },
1029
-
1030
- length: function () {
1031
- return this.views.length;
1032
- },
1033
-
1034
- // pakyow api
1035
-
1036
- attrs: function () {
1037
- return pw.attrs.init(this.views);
1038
- },
1039
-
1040
- append: function (data) {
1041
- data = Array.ensure(data);
1042
-
1043
- var last = this.last();
1044
- this.views.push(last.append(data));
1045
- return last;
1046
- },
1047
-
1048
- prepend: function(data) {
1049
- data = Array.ensure(data);
1050
-
1051
- var prependedViews = data.map(function (datum) {
1052
- var view = this.first().prepend(datum);
1053
- this.views.push(view);
1054
- return view;
1055
- }, this);
1056
-
1057
- return pw.collection.init(prependedViews);
1058
- },
1059
-
1060
- invoke: function (cb) {
1061
- pw.node.invoke(this.views, cb);
1062
- },
1063
-
1064
- invokeWithData: function(data, fn) {
1065
- data = Array.ensure(data);
1066
-
1067
- this.views.forEach(function (view, i) {
1068
- fn.call(view, data[i]);
1069
- });
1070
- },
1071
-
1072
- match: function (data, fn) {
1073
- data = Array.ensure(data);
1074
-
1075
- if (data.length === 0) {
1076
- this.remove();
1077
- return fn.call(this);
1078
- } else {
1079
- var firstView;
1080
- var firstParent;
1081
-
1082
- if (this.views[0]) {
1083
- firstView = this.views[0].clone();
1084
- firstParent = this.views[0].node.parentNode;
1085
- }
1086
-
1087
- this.views.slice(0).forEach(function (view) {
1088
- var id = view.node.getAttribute('data-id');
1089
-
1090
- if (!id && data[0].id) {
1091
- this.removeView(view);
1092
- return;
1093
- } else if (id) {
1094
- if (!data.find(function (datum) { return datum.id && datum.id.toString() === id })) {
1095
- this.removeView(view);
1096
- }
1097
- }
1098
- }, this);
1099
-
1100
- if (data.length > this.length()) {
1101
- var self = this;
1102
- this.endpoint.template(this, function (view) {
1103
- if (!view) {
1104
- view = firstView.clone();
1105
- self.parent = pw.view.init(firstParent);
1106
- }
1107
-
1108
- data.forEach(function (datum) {
1109
- if (!self.views.find(function (view) {
1110
- return view.node.getAttribute('data-id') === (datum.id || '').toString()
1111
- })) {
1112
- var viewToAdd = view.clone();
1113
-
1114
- if (viewToAdd instanceof pw_Collection) {
1115
- viewToAdd = viewToAdd.views[0];
1116
- }
1117
-
1118
- viewToAdd.node.setAttribute('data-id', datum.id);
1119
- self.addView(viewToAdd);
1120
-
1121
- pw.component.findAndInit(viewToAdd.node);
1122
- }
1123
- }, self);
1124
-
1125
- return fn.call(self);
1126
- });
1127
- } else {
1128
- return fn.call(this);
1129
- }
1130
- }
1131
-
1132
- return this;
1133
- },
1134
-
1135
- repeat: function (data, fn) {
1136
- this.match(data, function () {
1137
- this.invokeWithData(data, fn);
1138
- });
1139
- },
1140
-
1141
- bind: function (data, fn) {
1142
- this.invokeWithData(data, function(datum) {
1143
- this.bind(datum);
1144
-
1145
- if(!(typeof fn === 'undefined')) {
1146
- fn.call(this, datum);
1147
- }
1148
- });
1149
-
1150
- return this;
1151
- },
1152
-
1153
- apply: function (data, fn) {
1154
- this.match(data, function () {
1155
- var id;
1156
-
1157
- this.order(data.map(function (datum) {
1158
- if (id = datum.id) {
1159
- return id.toString();
1160
- }
1161
- }));
1162
-
1163
- this.bind(data, fn);
1164
- });
1165
- },
1166
-
1167
- version: function (data, fn) {
1168
- var self = this;
1169
- this.match(data, function () {
1170
- this.invokeWithData(data, fn);
1171
- });
1172
- },
1173
-
1174
- setEndpoint: function (endpoint) {
1175
- this.endpoint = endpoint;
1176
- return this;
1177
- }
1178
- };
1179
-
1180
- // lookup functions
1181
- ['scope', 'prop', 'component'].forEach(function (method) {
1182
- pw_Collection.prototype[method] = function (name) {
1183
- return pw.collection.init(
1184
- this.views.reduce(function (views, view) {
1185
- return views.concat(view[method](name).views);
1186
- }, [])
1187
- );
1188
- };
1189
- });
1190
-
1191
- // pass through functions
1192
- ['remove', 'clear', 'text', 'html'].forEach(function (method) {
1193
- pw_Collection.prototype[method] = function (arg) {
1194
- this.views.forEach(function (view) {
1195
- view[method](arg);
1196
- });
1197
- };
1198
- });
1199
- /*
1200
- Component init.
1201
- */
1202
-
1203
- pw.init.register(function () {
1204
- pw.component.findAndInit(document.querySelectorAll('body')[0]);
1205
- });
1206
-
1207
- /*
1208
- Component related functions.
1209
- */
1210
-
1211
- // stores component functions by name
1212
- var components = {};
1213
-
1214
- // stores component instances by channel
1215
- var channelComponents = {};
1216
- var channelBroadcasts = {};
1217
-
1218
- // component instances
1219
- var componentInstances = {};
1220
-
1221
- pw.component = {
1222
- init: function (view, config) {
1223
- return new pw_Component(view, config);
1224
- },
1225
-
1226
- resetChannels: function () {
1227
- channelComponents = {};
1228
- },
1229
-
1230
- findAndInit: function (node) {
1231
- pw.node.byAttr(node, 'data-ui').forEach(function (uiNode) {
1232
- if (uiNode._ui) {
1233
- return;
1234
- }
1235
-
1236
- var name = uiNode.getAttribute('data-ui');
1237
- var cfn = components[name] || pw.component.init;
1238
-
1239
- if (!componentInstances[name]) {
1240
- componentInstances[name] = [];
1241
- }
1242
-
1243
- var channel = uiNode.getAttribute('data-channel');
1244
- var config = uiNode.getAttribute('data-config');
1245
- var view = pw.view.init(uiNode);
1246
- var id = componentInstances[name].length;
1247
-
1248
- var component = new cfn(view, pw.component.buildConfigObject(config), name, id);
1249
- component.init(view, config, name);
1250
-
1251
- pw.component.registerForChannel(component, channel);
1252
- componentInstances[name].push(component);
1253
-
1254
- uiNode._ui = true;
1255
- });
1256
- },
1257
-
1258
- push: function (packet) {
1259
- var channel = packet.channel;
1260
- var payload = packet.payload;
1261
- var instruct = payload.instruct;
1262
-
1263
- (channelComponents[channel] || []).forEach(function (component) {
1264
- if (instruct) {
1265
- component.instruct(channel, instruct);
1266
- } else {
1267
- component.message(channel, payload);
1268
- }
1269
- });
1270
- },
1271
-
1272
- register: function (name, fn) {
1273
- var proto = pw_Component.prototype;
1274
-
1275
- Object.getOwnPropertyNames(proto).forEach(function (method) {
1276
- fn.prototype[method] = proto[method];
1277
- });
1278
-
1279
- components[name] = fn;
1280
- },
1281
-
1282
- buildConfigObject: function(configString) {
1283
- if (!configString) {
1284
- return {};
1285
- }
1286
-
1287
- return configString.split(';').reduce(function (config, option) {
1288
- var kv = option.trim().split(':');
1289
- config[kv[0].trim()] = kv[1].trim();
1290
- return config;
1291
- }, {});
1292
- },
1293
-
1294
- registerForChannel: function (component, channel) {
1295
- // store component instance by channel for messaging
1296
- if (!channelComponents[channel]) {
1297
- channelComponents[channel] = [];
1298
- }
1299
-
1300
- channelComponents[channel].push(component);
1301
- },
1302
-
1303
- registerForBroadcast: function (channel, cb, component) {
1304
- if (!channelBroadcasts[channel]) {
1305
- channelBroadcasts[channel] = [];
1306
- }
1307
-
1308
- channelBroadcasts[channel].push([cb, component]);
1309
- },
1310
-
1311
- deregisterForBroadcast: function (channel, component) {
1312
- var components = channelBroadcasts[channel];
1313
-
1314
- var instanceTuple = components.find(function (tuple) {
1315
- return tuple[1] == component;
1316
- });
1317
-
1318
- var i = components.indexOf(instanceTuple);
1319
- components.splice(i, 1);
1320
- },
1321
-
1322
- broadcast: function (channel, payload) {
1323
- (channelBroadcasts[channel] || []).forEach(function (cbTuple) {
1324
- cbTuple[0].call(cbTuple[1], payload);
1325
- });
1326
- }
1327
- };
1328
-
1329
- /*
1330
- pw_Component makes it possible to build custom controls.
1331
- */
1332
-
1333
- var pw_Component = function (view, config, name) {
1334
- // placeholder
1335
- };
1336
-
1337
- pw_Component.prototype = {
1338
- init: function (view, config, name) {
1339
- var node = view.node;
1340
- this.view = view;
1341
- this.node = node;
1342
- this.config = config;
1343
- this.name = name;
1344
- this.templates = {};
1345
- var self = this;
1346
-
1347
- // setup templates
1348
- pw.node.toA(node.querySelectorAll(':scope > *[data-template]')).forEach(function (templateNode) {
1349
- var cloned = templateNode.cloneNode(true);
1350
- pw.node.remove(templateNode);
1351
-
1352
- var scope = cloned.getAttribute('data-scope');
1353
-
1354
- if (this.templates[scope]) {
1355
- this.templates[scope].views.push(pw.view.init(cloned));
1356
- } else {
1357
- this.templates[scope] = pw.collection.init(pw.view.init(cloned));
1358
- }
1359
-
1360
- cloned.removeAttribute('data-template');
1361
- }, this);
1362
-
1363
- // setup our initial state
1364
- this.state = pw.state.init(this.node);
1365
-
1366
- // register as a dependent to the parent component
1367
- if (this.dCb) {
1368
- var parentComponent = pw.node.component(this.node.parentNode);
1369
-
1370
- if (parentComponent) {
1371
- parentComponent.addEventListener('mutated', function (evt) {
1372
- self.transform(self.dCb(evt.target._evtData));
1373
- });
1374
-
1375
- self.transform(self.dCb(pw.state.init(parentComponent).current()));
1376
- }
1377
- }
1378
-
1379
- // make it mutable
1380
- var mutableCb = function (evt) {
1381
- var scope = pw.node.scope(evt.target);
1382
-
1383
- if (scope) {
1384
- evt.preventDefault();
1385
- self.mutated(scope);
1386
- }
1387
- };
1388
-
1389
- node.addEventListener('submit', mutableCb);
1390
- node.addEventListener('change', function (evt) {
1391
- if (!pw.node.inForm(evt.target)) {
1392
- mutableCb(evt);
1393
- }
1394
- });
1395
-
1396
- //TODO define other mutable things
1397
-
1398
- if (this.inited) {
1399
- this.inited();
1400
- }
1401
- },
1402
-
1403
- listen: function (channel, cb) {
1404
- pw.component.registerForBroadcast(channel, cb, this);
1405
- },
1406
-
1407
- ignore: function (channel) {
1408
- pw.component.deregisterForBroadcast(channel, this);
1409
- },
1410
-
1411
- // Bubbles an event up to a parent component. Intended to be used
1412
- // as an alternative to `broadcast` in cases where child components
1413
- // have an impact on their parents.
1414
- bubble: function (channel, payload) {
1415
- var parentComponent = pw.node.component(this.node.parentNode);
1416
-
1417
- (channelBroadcasts[channel] || []).forEach(function (cbTuple) {
1418
- if (cbTuple[1].node == parentComponent) {
1419
- cbTuple[0].call(cbTuple[1], payload);
1420
- }
1421
- });
1422
- },
1423
-
1424
- // Trickles an event down to child components. Intended to be used
1425
- // as an alternative to `broadcast` in cases where parent components
1426
- // have an impact on their children.
1427
- trickle: function (channel, payload) {
1428
- var channels = (channelBroadcasts[channel] || []);
1429
- pw.node.toA(this.node.getElementsByTagName('*')).forEach(function (node) {
1430
- channels.forEach(function (cbTuple) {
1431
- if (cbTuple[1].node == node) {
1432
- cbTuple[0].call(cbTuple[1], payload);
1433
- }
1434
- });
1435
- })
1436
- },
1437
-
1438
- //TODO this is pretty similary to processing instructions
1439
- // for views in that we also have to handle the empty case
1440
- //
1441
- // there might be an opportunity for some refactoring
1442
- instruct: function (channel, instructions) {
1443
- this.endpoint = pw.instruct;
1444
-
1445
- var current = this.state.current();
1446
- if (current.length === 1) {
1447
- var view = this.view.scope(current[0].scope);
1448
- var node = view.views[0].node;
1449
- if (node.getAttribute('data-version') === 'empty') {
1450
- var self = this;
1451
- pw.instruct.template(view, function (rview) {
1452
- var parent = node.parentNode;
1453
- parent.replaceChild(rview.node, node);
1454
-
1455
- instructions.forEach(function (instruction) {
1456
- self[instruction[0]](instruction[1]);
1457
- });
1458
- });
1459
-
1460
- return;
1461
- }
1462
- }
1463
-
1464
- instructions.forEach(function (instruction) {
1465
- this[instruction[0]](instruction[1]);
1466
- }, this);
1467
- },
1468
-
1469
- message: function (channel, payload) {
1470
- // placeholder
1471
- },
1472
-
1473
- mutated: function (node) {
1474
- this.mutation(this.state.diffNode(node));
1475
- this.state.update();
1476
-
1477
- pw.node.trigger('mutated', this.node, this.state.current());
1478
- },
1479
-
1480
- mutation: function (mutation) {
1481
- // placeholder
1482
- },
1483
-
1484
- transform: function (state) {
1485
- this._transform(state);
1486
- },
1487
-
1488
- _transform: function (state) {
1489
- if (!state) {
1490
- return;
1491
- }
1492
-
1493
- if (state.length > 0) {
1494
- this.view.scope(state[0].scope).setEndpoint(this.endpoint || this).apply(state);
1495
- } else {
1496
- pw.node.breadthFirst(this.view.node, function () {
1497
- if (this.hasAttribute('data-scope')) {
1498
- pw.node.remove(this);
1499
- }
1500
- });
1501
- }
1502
-
1503
- pw.node.trigger('mutated', this.node, this.state.current());
1504
- },
1505
-
1506
- revert: function () {
1507
- this.transform(this.state.revert());
1508
- },
1509
-
1510
- rollback: function () {
1511
- this.transform(this.state.rollback());
1512
- },
1513
-
1514
- template: function (view, cb) {
1515
- var template;
1516
-
1517
- if (template = this.templates[view.scope]) {
1518
- cb(template);
1519
- }
1520
- },
1521
-
1522
- remove: function (data) {
1523
- this.state.remove(data);
1524
- this.transform(this.state.current());
1525
- },
1526
-
1527
- append: function (data) {
1528
- this.state.append(data);
1529
- this.transform(this.state.current());
1530
- },
1531
-
1532
- prepend: function (data) {
1533
- this.state.prepend(data);
1534
- this.transform(this.state.current());
1535
- },
1536
-
1537
- parent: function () {
1538
- var parent = pw.node.scope(this.node);
1539
-
1540
- if (parent) {
1541
- return pw.state.init(parent).current()[0];
1542
- }
1543
- },
1544
-
1545
- dependent: function (cb) {
1546
- this.dCb = cb;
1547
- }
1548
- };
1549
- /*
1550
- Socket init.
1551
- */
1552
-
1553
- pw.init.register(function () {
1554
- pw.socket.init({
1555
- cb: function (socket) {
1556
- window.socket = socket;
1557
- pw.component.broadcast('socket:available');
1558
- }
1559
- });
1560
- });
1561
-
1562
- /*
1563
- Socket related functions.
1564
- */
1565
-
1566
- pw.socket = {
1567
- init: function (options) {
1568
- return pw.socket.connect(
1569
- options.host,
1570
- options.port,
1571
- options.protocol,
1572
- options.connId,
1573
- options.cb
1574
- );
1575
- },
1576
-
1577
- connect: function (host, port, protocol, connId, cb) {
1578
- if(typeof host === 'undefined') host = window.location.hostname;
1579
- if(typeof port === 'undefined') port = window.location.port;
1580
- if(typeof protocol === 'undefined') protocol = window.location.protocol;
1581
- if(typeof connId === 'undefined') connId = document.getElementsByTagName('body')[0].getAttribute('data-socket-connection-id');
1582
-
1583
- if (!connId) {
1584
- return;
1585
- }
1586
-
1587
- var wsUrl = '';
1588
-
1589
- if (protocol === 'http:') {
1590
- wsUrl += 'ws://';
1591
- } else if (protocol === 'https:') {
1592
- wsUrl += 'wss://';
1593
- }
1594
-
1595
- wsUrl += host;
1596
-
1597
- if (port) {
1598
- wsUrl += ':' + port;
1599
- }
1600
-
1601
- wsUrl += '/?socket_connection_id=' + connId;
1602
-
1603
- return new pw_Socket(wsUrl, cb);
1604
- }
1605
- };
1606
-
1607
- var pw_Socket = function (url, cb) {
1608
- var self = this;
1609
-
1610
- this.callbacks = {};
1611
-
1612
- this.url = url;
1613
- this.initCb = cb;
1614
-
1615
- this.ws = new WebSocket(url);
1616
-
1617
- this.id = url.split('socket_connection_id=')[1];
1618
-
1619
- var pingInterval;
1620
-
1621
- this.ws.onmessage = function (evt) {
1622
- pw.component.broadcast('socket:loaded');
1623
-
1624
- var data = JSON.parse(evt.data);
1625
- if (data.id) {
1626
- var cb = self.callbacks[data.id];
1627
- if (cb) {
1628
- cb.call(this, data);
1629
- return;
1630
- }
1631
- }
1632
-
1633
- self.message(data);
1634
- };
1635
-
1636
- this.ws.onclose = function (evt) {
1637
- console.log('socket closed');
1638
- clearInterval(pingInterval);
1639
- self.reconnect();
1640
- };
1641
-
1642
- this.ws.onopen = function (evt) {
1643
- console.log('socket open');
1644
-
1645
- if(self.initCb) {
1646
- self.initCb(self);
1647
- }
1648
-
1649
- pingInterval = setInterval(function () {
1650
- self.send({ action: 'ping' });
1651
- }, 30000);
1652
- }
1653
- };
1654
-
1655
- pw_Socket.prototype = {
1656
- send: function (message, cb) {
1657
- pw.component.broadcast('socket:loading');
1658
-
1659
- message.id = pw.util.guid();
1660
- if (!message.input) {
1661
- message.input = {};
1662
- }
1663
- message.input.socket_connection_id = this.id;
1664
- this.callbacks[message.id] = cb;
1665
- this.ws.send(JSON.stringify(message));
1666
- },
1667
-
1668
- //TODO handle custom messages (e.g. not pakyow specific)
1669
- message: function (packet) {
1670
- console.log('received message');
1671
- console.log(packet);
1672
-
1673
- var selector = '*[data-channel="' + packet.channel + '"]';
1674
-
1675
- if (packet.channel && packet.channel.split(':')[0] === 'component') {
1676
- pw.component.push(packet);
1677
- return;
1678
- }
1679
-
1680
- var nodes = pw.node.toA(document.querySelectorAll(selector));
1681
-
1682
- if (nodes.length === 0) {
1683
- //TODO decide how to handle this condition; there are times where this
1684
- // is going to be the case and not an error; at one point we were simply
1685
- // reloading the page, but that doesn't work in all cases
1686
- return;
1687
- }
1688
-
1689
- pw.instruct.process(pw.collection.fromNodes(nodes, selector), packet, this);
1690
- },
1691
-
1692
- reconnect: function () {
1693
- var self = this;
1694
-
1695
- if (!self.wait) {
1696
- self.wait = 100;
1697
- } else {
1698
- self.wait *= 1.25;
1699
- }
1700
-
1701
- console.log('reconnecting socket in ' + self.wait + 'ms');
1702
-
1703
- setTimeout(function () {
1704
- pw.socket.init({ cb: self.initCb });
1705
- }, self.wait);
1706
- },
1707
-
1708
- fetchView: function (lookup, cb) {
1709
- var uri;
1710
-
1711
- if (window.location.hash) {
1712
- var arr = window.location.hash.split('#:')[1].split('/');
1713
- arr.shift();
1714
- uri = arr.join('/');
1715
- } else {
1716
- uri = window.location.pathname + window.location.search;
1717
- }
1718
-
1719
- this.send({
1720
- action: 'fetch-view',
1721
- lookup: lookup,
1722
- uri: uri
1723
- }, function (res) {
1724
- var view = pw.view.fromStr(res.body);
1725
-
1726
- if (view.node) {
1727
- view.node.removeAttribute('data-id');
1728
- cb(view);
1729
- } else {
1730
- cb();
1731
- }
1732
- });
1733
- }
1734
- };
1735
- pw.instruct = {
1736
- process: function (collection, packet, socket) {
1737
- if (collection.length() === 1 && collection.views[0].node.getAttribute('data-version') === 'empty') {
1738
- pw.instruct.fetchView(packet, socket, collection.views[0].node);
1739
- } else {
1740
- pw.instruct.perform(collection, packet.payload);
1741
- }
1742
- },
1743
-
1744
- fetchView: function (packet, socket, node) {
1745
- socket.fetchView({ channel: packet.channel }, function (view) {
1746
- if (view) {
1747
- var parent = node.parentNode;
1748
- parent.replaceChild(view.node, node);
1749
-
1750
- var selector = '*[data-channel="' + packet.channel + '"]';
1751
- var nodes = pw.node.toA(parent.querySelectorAll(selector));
1752
- pw.instruct.perform(pw.collection.fromNodes(nodes, selector), packet.payload);
1753
- } else {
1754
- console.log('trouble fetching view :(');
1755
- }
1756
- });
1757
- },
1758
-
1759
- // TODO: make this smart and cache results, invalidating
1760
- // if the websocket connection reconnects (since that means
1761
- // the server probably restarted)
1762
- template: function (view, cb) {
1763
- var lookup = {};
1764
-
1765
- if (!view || !view.first()) {
1766
- return cb();
1767
- }
1768
-
1769
- var node = view.first().node;
1770
-
1771
- if (node.hasAttribute('data-channel')) {
1772
- lookup.channel = view.first().node.getAttribute('data-channel');
1773
- } else if (node.hasAttribute('data-ui') && node.hasAttribute('data-scope')) {
1774
- lookup.component = pw.node.component(node).getAttribute('data-ui');
1775
- lookup.scope = node.getAttribute('data-scope');
1776
- } else {
1777
- cb();
1778
- return;
1779
- }
1780
-
1781
- window.socket.fetchView(lookup, function (view) {
1782
- cb(view);
1783
- });
1784
- },
1785
-
1786
- perform: function (collection, instructions, cb) {
1787
- var self = this;
1788
- instructions = instructions || [];
1789
-
1790
- function instruct (subject, instruction) {
1791
- var method = instruction[0];
1792
- var value = instruction[1];
1793
- var nested = instruction[2];
1794
-
1795
- // remap instructions to the ring name
1796
- if (method === 'with') {
1797
- method = 'invoke';
1798
- }
1799
-
1800
- if (method === 'for') {
1801
- method = 'invokeWithData';
1802
- }
1803
-
1804
- if (collection[method]) {
1805
- if (method == 'invoke' || method == 'invokeWithData' || method == 'bind' || method == 'repeat' || method == 'apply' || method == 'version') {
1806
- var cbLength = collection.length();
1807
- var cbCount = 0;
1808
- var nestedCb = function () {
1809
- cbCount++;
1810
-
1811
- if (cbCount == cbLength) {
1812
- next();
1813
- }
1814
- }
1815
- collection.setEndpoint(self)[method].call(collection, value, function (datum) {
1816
- pw.instruct.perform(this, nested[value.indexOf(datum)], nestedCb);
1817
- });
1818
- return;
1819
- } else if (method == 'attrs') {
1820
- self.performAttr(collection.attrs(), nested);
1821
- return;
1822
- } else if (method == 'use') {
1823
- collection.setEndpoint(self);
1824
- collection.use(value, next);
1825
- return;
1826
- } else {
1827
- var mutatedViews = collection[method].call(collection, value);
1828
- }
1829
- } else {
1830
- console.log('could not find method named: ' + method);
1831
- return;
1832
- }
1833
-
1834
- if (nested instanceof Array) {
1835
- pw.instruct.perform(mutatedViews, nested, next);
1836
- return;
1837
- } else if (mutatedViews) {
1838
- collection = mutatedViews;
1839
- }
1840
-
1841
- next();
1842
- };
1843
-
1844
- var i = 0;
1845
- function next() {
1846
- if (i < instructions.length) {
1847
- instruct(collection, instructions[i++]);
1848
- } else {
1849
- done();
1850
- }
1851
- };
1852
-
1853
- function done() {
1854
- if (cb) {
1855
- cb();
1856
- } else {
1857
- pw.component.findAndInit(collection.node);
1858
- }
1859
- };
1860
-
1861
- next();
1862
- },
1863
-
1864
- performAttr: function (context, attrInstructions) {
1865
- attrInstructions.forEach(function (attrInstruct) {
1866
- var attr = attrInstruct[0];
1867
- var value = attrInstruct[1];
1868
- var nested = attrInstruct[2];
1869
-
1870
- if (value) {
1871
- context.set(attr, value);
1872
- } else {
1873
- context[nested[0][0]](attr, nested[0][1]);
1874
- }
1875
- });
1876
- }
1877
- };
1878
- if (!Array.prototype.flatten) {
1879
- Array.prototype.flatten = function () {
1880
- return this.reduce(function (flat, toFlatten) {
1881
- return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten);
1882
- }, []);
1883
- };
1884
- }
1885
-
1886
- if (!Array.prototype.find) {
1887
- Array.prototype.find = function(predicate) {
1888
- if (this == null) {
1889
- throw new TypeError('Array.prototype.find called on null or undefined');
1890
- }
1891
- if (typeof predicate !== 'function') {
1892
- throw new TypeError('predicate must be a function');
1893
- }
1894
- var list = Object(this);
1895
- var length = list.length >>> 0;
1896
- var thisArg = arguments[1];
1897
- var value;
1898
-
1899
- for (var i = 0; i < length; i++) {
1900
- value = list[i];
1901
- if (predicate.call(thisArg, value, i, list)) {
1902
- return value;
1903
- }
1904
- }
1905
- return undefined;
1906
- };
1907
- }
1908
-
1909
- Array.ensure = function (value) {
1910
- if(!(value instanceof Array)) {
1911
- return [value];
1912
- }
1913
-
1914
- return value
1915
- }
1916
-
1917
- NodeList.prototype.forEach = Array.prototype.forEach;
1918
- if (!Object.prototype.pairs) {
1919
- Object.defineProperty(Object.prototype, "pairs", {
1920
- value: function() {
1921
- return Object.keys(this).map(function (key) {
1922
- return [key, this[key]];
1923
- }, this);
1924
- },
1925
- enumerable: false
1926
- });
1927
- }
1928
-
1929
- if (typeof define === "function" && define.amd) {
1930
- define(pw);
1931
- } else if (typeof module === "object" && module.exports) {
1932
- module.exports = pw;
1933
- } else {
1934
- this.pw = pw;
1935
- }
1936
- })();