simplyview 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/simply.everything.js +105 -70
  2. package/js/simply.api.js +46 -47
  3. package/js/simply.keyboard.js +19 -2
  4. package/js/simply.route.js +18 -16
  5. package/js/simply.viewmodel.js +20 -5
  6. package/package.json +7 -2
  7. package/docs/examples.md +0 -82
  8. package/docs/readme.md +0 -33
  9. package/docs/simply.action.md +0 -42
  10. package/docs/simply.activate.md +0 -27
  11. package/docs/simply.api.md +0 -188
  12. package/docs/simply.app.md +0 -27
  13. package/docs/simply.collect.md +0 -64
  14. package/docs/simply.command.md +0 -110
  15. package/docs/simply.include.md +0 -61
  16. package/docs/simply.keyboard.md +0 -60
  17. package/docs/simply.path.md +0 -3
  18. package/docs/simply.route.md +0 -133
  19. package/docs/simply.view.md +0 -53
  20. package/docs/simply.viewmodel.md +0 -3
  21. package/examples/commands.html +0 -70
  22. package/examples/counter.html +0 -52
  23. package/examples/examples.css +0 -3
  24. package/examples/github.html +0 -39
  25. package/examples/githubv4.html +0 -107
  26. package/examples/graphql.html +0 -51
  27. package/examples/graphql.html~ +0 -35
  28. package/examples/include/fifth.js +0 -1
  29. package/examples/include/first.js +0 -1
  30. package/examples/include/include.html +0 -4
  31. package/examples/include/include2.html +0 -1
  32. package/examples/include/index.html +0 -18
  33. package/examples/include/scripts.html +0 -16
  34. package/examples/include/third.js +0 -3
  35. package/examples/keyboard.html +0 -41
  36. package/examples/todo.html +0 -48
  37. package/examples/viewmodel.html +0 -359
  38. package/make +0 -18
  39. package/make~ +0 -17
  40. package/package.json~ +0 -31
  41. package/test/simply.route.test.js +0 -102
@@ -639,8 +639,8 @@ properties for a given parent, keep seperate index for this?
639
639
  handleEvents: function() {
640
640
  global.addEventListener('popstate', function() {
641
641
  if (route.match(getPath(document.location.pathname + document.location.hash)) === false) {
642
- route.match(getPath(document.location.pathname));
643
- }
642
+ route.match(getPath(document.location.pathname));
643
+ }
644
644
  });
645
645
  global.document.addEventListener('click', linkHandler);
646
646
  },
@@ -665,22 +665,24 @@ properties for a given parent, keep seperate index for this?
665
665
 
666
666
  var matches;
667
667
  if (!path) {
668
- if (route.match(document.location.pathname+document.location.hash)) {
669
- return true;
670
- } else {
671
- return route.match(document.location.pathname);
672
- }
668
+ if (route.match(document.location.pathname+document.location.hash)) {
669
+ return true;
670
+ } else {
671
+ return route.match(document.location.pathname);
672
+ }
673
673
  }
674
674
  path = getPath(path);
675
675
  for ( var i=0; i<routeInfo.length; i++) {
676
- if (path && path[path.length-1]!='/') {
677
- matches = routeInfo[i].match.exec(path+'/');
678
- if (matches) {
679
- path+='/';
680
- history.replaceState({}, '', getUrl(path));
676
+ matches = routeInfo[i].match.exec(path);
677
+ if (!matches || !matches.length) {
678
+ if (path && path[path.length-1]!='/') {
679
+ matches = routeInfo[i].match.exec(path+'/');
680
+ if (matches) {
681
+ path+='/';
682
+ history.replaceState({}, '', getUrl(path));
683
+ }
681
684
  }
682
685
  }
683
- matches = routeInfo[i].match.exec(path);
684
686
  if (matches && matches.length) {
685
687
  var params = {};
686
688
  routeInfo[i].params.forEach(function(key, i) {
@@ -697,9 +699,9 @@ properties for a given parent, keep seperate index for this?
697
699
  args.result = routeInfo[i].action.call(route, params);
698
700
  runListeners('finish', args);
699
701
  return args.result;
700
- }
702
+ }
701
703
  }
702
- return false;
704
+ return false;
703
705
  },
704
706
  goto: function(path) {
705
707
  history.pushState({},'',getUrl(path));
@@ -734,7 +736,7 @@ properties for a given parent, keep seperate index for this?
734
736
  listeners[action][route] = listeners[action][route].filter(function(listener) {
735
737
  return listener != callback;
736
738
  });
737
- },
739
+ },
738
740
  init: function(params) {
739
741
  if (params.root) {
740
742
  options.root = params.root;
@@ -1125,8 +1127,25 @@ properties for a given parent, keep seperate index for this?
1125
1127
  if (e.target.closest('[data-simply-keyboard]')) {
1126
1128
  selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
1127
1129
  }
1128
- if (keys[selectedKeyboard] && keys[selectedKeyboard][e.code]) {
1129
- keys[selectedKeyboard][e.code].call(app,e);
1130
+ let key = '';
1131
+ if (e.ctrlKey && e.keyCode!=17) {
1132
+ key+='Control+';
1133
+ }
1134
+ if (e.metaKey && e.keyCode!=224) {
1135
+ key+='Meta+';
1136
+ }
1137
+ if (e.altKey && e.keyCode!=18) {
1138
+ key+='Alt+';
1139
+ }
1140
+ if (e.shiftKey && e.keyCode!=16) {
1141
+ key+='Shift+';
1142
+ }
1143
+ key+=e.key;
1144
+
1145
+ if (keys[selectedKeyboard] && keys[selectedKeyboard][key]) {
1146
+ let keyboard = keys[selectedKeyboard]
1147
+ keyboard.app = app;
1148
+ keyboard[key].call(keyboard,e);
1130
1149
  }
1131
1150
  });
1132
1151
 
@@ -1582,6 +1601,7 @@ properties for a given parent, keep seperate index for this?
1582
1601
  load();
1583
1602
  } else {
1584
1603
  global.document.addEventListener('simply-content-loaded', function() {
1604
+ console.log('switching...')
1585
1605
  load();
1586
1606
  });
1587
1607
  }
@@ -1600,10 +1620,18 @@ properties for a given parent, keep seperate index for this?
1600
1620
  })(this);
1601
1621
  (function(global) {
1602
1622
  'use strict';
1623
+
1624
+ function etag() {
1625
+ let d = '';
1626
+ while (d.length < 32) d += Math.random().toString(16).substr(2);
1627
+ const vr = ((parseInt(d.substr(16, 1), 16) & 0x3) | 0x8).toString(16);
1628
+ return `${d.substr(0, 8)}-${d.substr(8, 4)}-4${d.substr(13, 3)}-${vr}${d.substr(17, 3)}-${d.substr(20, 12)}`;
1629
+ }
1603
1630
 
1604
1631
  function ViewModel(name, data, options) {
1605
1632
  this.name = name;
1606
1633
  this.data = data || [];
1634
+ this.data.etag = etag();
1607
1635
  this.view = {
1608
1636
  options: {},
1609
1637
  data: [] //Array.from(this.data).slice()
@@ -1626,15 +1654,22 @@ properties for a given parent, keep seperate index for this?
1626
1654
  // this.data is a reference to the data passed, so that any changes in it will get applied
1627
1655
  // to the original
1628
1656
  this.data = params.data;
1657
+ this.data.etag = etag()
1629
1658
  }
1630
1659
  // the view is a shallow copy of the array, so that changes in sort order and filtering
1631
1660
  // won't get applied to the original, but databindings on its children will still work
1632
1661
  this.view.data = Array.from(this.data).slice();
1633
- var plugins = this.plugins.start.concat(this.plugins.select, this.plugins.order, this.plugins.render, this.plugins.finish);
1634
- var self = this;
1635
- plugins.forEach(function(plugin) {
1636
- plugin.call(self, params);
1662
+ this.view.data.etag = this.data.etag;
1663
+ let data = this.view.data;
1664
+ let plugins = this.plugins.start.concat(this.plugins.select, this.plugins.order, this.plugins.render, this.plugins.finish);
1665
+ plugins.forEach(plugin => {
1666
+ data = plugin.call(this, params, data);
1667
+ if (!data) {
1668
+ data = this.view.data;
1669
+ }
1670
+ this.view.data = data
1637
1671
  });
1672
+ this.view.data = data;
1638
1673
 
1639
1674
  if (global.editor) {
1640
1675
  global.editor.addDataSource(this.name,{
@@ -1759,7 +1794,8 @@ properties for a given parent, keep seperate index for this?
1759
1794
  createFilter: createFilter,
1760
1795
  createSort: createSort,
1761
1796
  createPaging: createPaging,
1762
- updateDataSource: updateDataSource
1797
+ updateDataSource: updateDataSource,
1798
+ etag
1763
1799
  };
1764
1800
 
1765
1801
  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
@@ -1775,7 +1811,7 @@ properties for a given parent, keep seperate index for this?
1775
1811
  'use strict';
1776
1812
 
1777
1813
  var api = {
1778
- /**
1814
+ /**
1779
1815
  * Returns a Proxy object that translates property access to a URL in the api
1780
1816
  * and method calls to a fetch on that URL.
1781
1817
  * @param options: a list of options for fetch(),
@@ -1897,27 +1933,27 @@ properties for a given parent, keep seperate index for this?
1897
1933
  */
1898
1934
  getResult: function(response, options) {
1899
1935
  if (response.ok) {
1900
- switch(options.responseFormat) {
1901
- case 'text':
1902
- return response.text();
1903
- break;
1904
- case 'formData':
1905
- return response.formData();
1906
- break;
1907
- case 'blob':
1908
- return response.blob();
1909
- break;
1910
- case 'arrayBuffer':
1911
- return response.arrayBuffer();
1912
- break;
1913
- case 'unbuffered':
1914
- return response.body;
1915
- break;
1916
- case 'json':
1917
- default:
1918
- return response.json();
1919
- break;
1920
- }
1936
+ switch(options.responseFormat) {
1937
+ case 'text':
1938
+ return response.text();
1939
+ break;
1940
+ case 'formData':
1941
+ return response.formData();
1942
+ break;
1943
+ case 'blob':
1944
+ return response.blob();
1945
+ break;
1946
+ case 'arrayBuffer':
1947
+ return response.arrayBuffer();
1948
+ break;
1949
+ case 'unbuffered':
1950
+ return response.body;
1951
+ break;
1952
+ case 'json':
1953
+ default:
1954
+ return response.json();
1955
+ break;
1956
+ }
1921
1957
  } else {
1922
1958
  throw {
1923
1959
  status: response.status,
@@ -1925,23 +1961,22 @@ properties for a given parent, keep seperate index for this?
1925
1961
  response: response
1926
1962
  }
1927
1963
  }
1928
- },
1929
-
1930
- logError: function(error, options) {
1964
+ },
1965
+ logError: function(error, options) {
1931
1966
  console.error(error.status, error.message);
1932
- }
1967
+ }
1933
1968
  }
1934
1969
 
1935
1970
  var defaultOptions = {
1936
- path: '',
1937
- responseFormat: 'json',
1938
- paramsFormat: 'search',
1939
- verbs: ['get','post'],
1940
- handlers: {
1941
- fetch: api.fetch,
1942
- result: api.getResult,
1943
- error: api.logError
1944
- }
1971
+ path: '',
1972
+ responseFormat: 'json',
1973
+ paramsFormat: 'search',
1974
+ verbs: ['get','post'],
1975
+ handlers: {
1976
+ fetch: api.fetch,
1977
+ result: api.getResult,
1978
+ error: api.logError
1979
+ }
1945
1980
  };
1946
1981
 
1947
1982
  function cd(path, name) {
@@ -1952,20 +1987,20 @@ properties for a given parent, keep seperate index for this?
1952
1987
  return path+encodeURIComponent(name);
1953
1988
  }
1954
1989
 
1955
- function fetchChain(prop, params) {
1956
- var options = this;
1957
- return this.handlers.fetch
1958
- .call(this, prop, params, options)
1959
- .then(function(res) {
1960
- return options.handlers.result.call(options, res, options);
1961
- })
1962
- .catch(function(error) {
1963
- return options.handlers.error.call(options, error, options);
1964
- });
1965
- }
1990
+ function fetchChain(prop, params) {
1991
+ var options = this;
1992
+ return this.handlers.fetch
1993
+ .call(this, prop, params, options)
1994
+ .then(function(res) {
1995
+ return options.handlers.result.call(options, res, options);
1996
+ })
1997
+ .catch(function(error) {
1998
+ return options.handlers.error.call(options, error, options);
1999
+ });
2000
+ }
1966
2001
 
1967
2002
  function getApiHandler(options) {
1968
- options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
2003
+ options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
1969
2004
  options = Object.assign({}, defaultOptions, options);
1970
2005
 
1971
2006
  return {
package/js/simply.api.js CHANGED
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var api = {
5
- /**
5
+ /**
6
6
  * Returns a Proxy object that translates property access to a URL in the api
7
7
  * and method calls to a fetch on that URL.
8
8
  * @param options: a list of options for fetch(),
@@ -124,27 +124,27 @@
124
124
  */
125
125
  getResult: function(response, options) {
126
126
  if (response.ok) {
127
- switch(options.responseFormat) {
128
- case 'text':
129
- return response.text();
130
- break;
131
- case 'formData':
132
- return response.formData();
133
- break;
134
- case 'blob':
135
- return response.blob();
136
- break;
137
- case 'arrayBuffer':
138
- return response.arrayBuffer();
139
- break;
140
- case 'unbuffered':
141
- return response.body;
142
- break;
143
- case 'json':
144
- default:
145
- return response.json();
146
- break;
147
- }
127
+ switch(options.responseFormat) {
128
+ case 'text':
129
+ return response.text();
130
+ break;
131
+ case 'formData':
132
+ return response.formData();
133
+ break;
134
+ case 'blob':
135
+ return response.blob();
136
+ break;
137
+ case 'arrayBuffer':
138
+ return response.arrayBuffer();
139
+ break;
140
+ case 'unbuffered':
141
+ return response.body;
142
+ break;
143
+ case 'json':
144
+ default:
145
+ return response.json();
146
+ break;
147
+ }
148
148
  } else {
149
149
  throw {
150
150
  status: response.status,
@@ -152,23 +152,22 @@
152
152
  response: response
153
153
  }
154
154
  }
155
- },
156
-
157
- logError: function(error, options) {
155
+ },
156
+ logError: function(error, options) {
158
157
  console.error(error.status, error.message);
159
- }
158
+ }
160
159
  }
161
160
 
162
161
  var defaultOptions = {
163
- path: '',
164
- responseFormat: 'json',
165
- paramsFormat: 'search',
166
- verbs: ['get','post'],
167
- handlers: {
168
- fetch: api.fetch,
169
- result: api.getResult,
170
- error: api.logError
171
- }
162
+ path: '',
163
+ responseFormat: 'json',
164
+ paramsFormat: 'search',
165
+ verbs: ['get','post'],
166
+ handlers: {
167
+ fetch: api.fetch,
168
+ result: api.getResult,
169
+ error: api.logError
170
+ }
172
171
  };
173
172
 
174
173
  function cd(path, name) {
@@ -179,20 +178,20 @@
179
178
  return path+encodeURIComponent(name);
180
179
  }
181
180
 
182
- function fetchChain(prop, params) {
183
- var options = this;
184
- return this.handlers.fetch
185
- .call(this, prop, params, options)
186
- .then(function(res) {
187
- return options.handlers.result.call(options, res, options);
188
- })
189
- .catch(function(error) {
190
- return options.handlers.error.call(options, error, options);
191
- });
192
- }
181
+ function fetchChain(prop, params) {
182
+ var options = this;
183
+ return this.handlers.fetch
184
+ .call(this, prop, params, options)
185
+ .then(function(res) {
186
+ return options.handlers.result.call(options, res, options);
187
+ })
188
+ .catch(function(error) {
189
+ return options.handlers.error.call(options, error, options);
190
+ });
191
+ }
193
192
 
194
193
  function getApiHandler(options) {
195
- options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
194
+ options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
196
195
  options = Object.assign({}, defaultOptions, options);
197
196
 
198
197
  return {
@@ -25,8 +25,25 @@
25
25
  if (e.target.closest('[data-simply-keyboard]')) {
26
26
  selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
27
27
  }
28
- if (keys[selectedKeyboard] && keys[selectedKeyboard][e.code]) {
29
- keys[selectedKeyboard][e.code].call(app,e);
28
+ let key = '';
29
+ if (e.ctrlKey && e.keyCode!=17) {
30
+ key+='Control+';
31
+ }
32
+ if (e.metaKey && e.keyCode!=224) {
33
+ key+='Meta+';
34
+ }
35
+ if (e.altKey && e.keyCode!=18) {
36
+ key+='Alt+';
37
+ }
38
+ if (e.shiftKey && e.keyCode!=16) {
39
+ key+='Shift+';
40
+ }
41
+ key+=e.key;
42
+
43
+ if (keys[selectedKeyboard] && keys[selectedKeyboard][key]) {
44
+ let keyboard = keys[selectedKeyboard]
45
+ keyboard.app = app;
46
+ keyboard[key].call(keyboard,e);
30
47
  }
31
48
  });
32
49
 
@@ -118,8 +118,8 @@
118
118
  handleEvents: function() {
119
119
  global.addEventListener('popstate', function() {
120
120
  if (route.match(getPath(document.location.pathname + document.location.hash)) === false) {
121
- route.match(getPath(document.location.pathname));
122
- }
121
+ route.match(getPath(document.location.pathname));
122
+ }
123
123
  });
124
124
  global.document.addEventListener('click', linkHandler);
125
125
  },
@@ -144,22 +144,24 @@
144
144
 
145
145
  var matches;
146
146
  if (!path) {
147
- if (route.match(document.location.pathname+document.location.hash)) {
148
- return true;
149
- } else {
150
- return route.match(document.location.pathname);
151
- }
147
+ if (route.match(document.location.pathname+document.location.hash)) {
148
+ return true;
149
+ } else {
150
+ return route.match(document.location.pathname);
151
+ }
152
152
  }
153
153
  path = getPath(path);
154
154
  for ( var i=0; i<routeInfo.length; i++) {
155
- if (path && path[path.length-1]!='/') {
156
- matches = routeInfo[i].match.exec(path+'/');
157
- if (matches) {
158
- path+='/';
159
- history.replaceState({}, '', getUrl(path));
155
+ matches = routeInfo[i].match.exec(path);
156
+ if (!matches || !matches.length) {
157
+ if (path && path[path.length-1]!='/') {
158
+ matches = routeInfo[i].match.exec(path+'/');
159
+ if (matches) {
160
+ path+='/';
161
+ history.replaceState({}, '', getUrl(path));
162
+ }
160
163
  }
161
164
  }
162
- matches = routeInfo[i].match.exec(path);
163
165
  if (matches && matches.length) {
164
166
  var params = {};
165
167
  routeInfo[i].params.forEach(function(key, i) {
@@ -176,9 +178,9 @@
176
178
  args.result = routeInfo[i].action.call(route, params);
177
179
  runListeners('finish', args);
178
180
  return args.result;
179
- }
181
+ }
180
182
  }
181
- return false;
183
+ return false;
182
184
  },
183
185
  goto: function(path) {
184
186
  history.pushState({},'',getUrl(path));
@@ -213,7 +215,7 @@
213
215
  listeners[action][route] = listeners[action][route].filter(function(listener) {
214
216
  return listener != callback;
215
217
  });
216
- },
218
+ },
217
219
  init: function(params) {
218
220
  if (params.root) {
219
221
  options.root = params.root;
@@ -1,9 +1,17 @@
1
1
  (function(global) {
2
2
  'use strict';
3
+
4
+ function etag() {
5
+ let d = '';
6
+ while (d.length < 32) d += Math.random().toString(16).substr(2);
7
+ const vr = ((parseInt(d.substr(16, 1), 16) & 0x3) | 0x8).toString(16);
8
+ return `${d.substr(0, 8)}-${d.substr(8, 4)}-4${d.substr(13, 3)}-${vr}${d.substr(17, 3)}-${d.substr(20, 12)}`;
9
+ }
3
10
 
4
11
  function ViewModel(name, data, options) {
5
12
  this.name = name;
6
13
  this.data = data || [];
14
+ this.data.etag = etag();
7
15
  this.view = {
8
16
  options: {},
9
17
  data: [] //Array.from(this.data).slice()
@@ -26,14 +34,20 @@
26
34
  // this.data is a reference to the data passed, so that any changes in it will get applied
27
35
  // to the original
28
36
  this.data = params.data;
37
+ this.data.etag = etag()
29
38
  }
30
39
  // the view is a shallow copy of the array, so that changes in sort order and filtering
31
40
  // won't get applied to the original, but databindings on its children will still work
32
41
  this.view.data = Array.from(this.data).slice();
33
- var plugins = this.plugins.start.concat(this.plugins.select, this.plugins.order, this.plugins.render, this.plugins.finish);
34
- var self = this;
35
- plugins.forEach(function(plugin) {
36
- plugin.call(self, params);
42
+ this.view.data.etag = this.data.etag;
43
+ let data = this.view.data;
44
+ let plugins = this.plugins.start.concat(this.plugins.select, this.plugins.order, this.plugins.render, this.plugins.finish);
45
+ plugins.forEach(plugin => {
46
+ data = plugin.call(this, params, data);
47
+ if (!data) {
48
+ data = this.view.data;
49
+ }
50
+ this.view.data = data
37
51
  });
38
52
 
39
53
  if (global.editor) {
@@ -159,7 +173,8 @@
159
173
  createFilter: createFilter,
160
174
  createSort: createSort,
161
175
  createPaging: createPaging,
162
- updateDataSource: updateDataSource
176
+ updateDataSource: updateDataSource,
177
+ etag
163
178
  };
164
179
 
165
180
  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplyview",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Library to rapidly build UI components, using declarative tools",
5
5
  "main": "dist/simply.everything.js",
6
6
  "directories": {
@@ -27,5 +27,10 @@
27
27
  "homepage": "https://github.com/simplyedit/simplyview#readme",
28
28
  "devDependencies": {
29
29
  "jest": "^24.9.0"
30
- }
30
+ },
31
+ "files": [
32
+ "README.md",
33
+ "dist/",
34
+ "js/"
35
+ ]
31
36
  }
package/docs/examples.md DELETED
@@ -1,82 +0,0 @@
1
- # Examples
2
-
3
- ## Counter
4
-
5
- The counter app is an example to introduce the basic concepts. Here is the HTML:
6
-
7
- ```html
8
- <div id="counterApp">
9
- <input type="text" data-simply-field="counter">
10
- <button data-simply-command="add1">+</button>
11
- <button data-simply-command="sub1">-</button>
12
- <div>Counter is now: <span data-simply-field="counter"></span></div>
13
- </div>
14
- ```
15
-
16
- It uses two components of SimplyView and SimplyEdit, through
17
- `data-simply-command` and `data-simply-field`.
18
-
19
- `data-simply-command` is handled by [simply.command](simply.command.md). Both
20
- commands are defined on buttons, so they will trigger when the button is
21
- pressed. The javascript code tied to these commands is defined with the
22
- [simply.app](simply.app.md) component:
23
-
24
- ```javascript
25
- var counterApp = simply.app({
26
- container: document.getElementById('counterApp'),
27
- commands: {
28
- add1: function() {
29
- counterApp.view.counter++;
30
- },
31
- sub1: function() {
32
- counterApp.view.counter--;
33
- }
34
- },
35
- view: {
36
- counter: 1
37
- }
38
- });
39
- ```
40
-
41
- simply.app is a wrapper that combines a number of components with a single
42
- configuration parameter. The commands section here is passed on to simply.command.
43
-
44
- When you press the button with `data-simply-command="add1"`, the command
45
- handler for this app is triggered and searches its commands for 'add1'.
46
- It then calls this javascript function and it will increase
47
- `counterApp.view.counter`.
48
-
49
- This is where the second component, which uses `data-simply-field`, comes in.
50
- The `counterApp.view` object is automatically tied to SimplyEdit, by simply.app.
51
- So whenever you update a variable inside `counterApp.view`, SimplyEdit will
52
- also update any HTML element which references the same variable.
53
-
54
- In this case `counterApp.view.counter` is tied to the input element with
55
- `data-simply-field="counter"`.
56
-
57
- SimplyEdit also does the reverse, whenever you change the input value,
58
- SimplyEdit will also change the `counterApp.view.counter` value. This is called
59
- two-way databinding.
60
-
61
- Two-way databinding is not instantanous, so whenever you change a value in
62
- javascript or in the DOM, it will take a short while for SimplyEdit to update
63
- the other side as well. There are a number of events that will tell you when
64
- the values are in sync again.
65
-
66
- ## Todo
67
-
68
- The TodoMVC application is a standard web application implemented in many different
69
- javascript frameworks. We've build one using SimplyEdit and SimplyView, which you
70
- can find at [todomvc.simplyedit.io](https://todomvc.simplyedit.io/).
71
-
72
- The code is on github at
73
- [github.com/simplyedit/todomvc](https://github.com/simplyedit/todomvc). The
74
- Readme explains how it is build.
75
-
76
- ## Hackernews PWA
77
-
78
- Just like the TodoMVC application, the Hackernews PWA is also a standard web
79
- application implemented in many frameworks. You can find the SimplyEdit/SimplyView
80
- version at [hnpwa.simplyedit.io](https://hnpwa.simplyedit.io/). The code is at
81
- [github.com/simplyedit/hnpwa](https://github.com/simplyedit/hnpwa) and the
82
- Readme explains how it was built.