middleman 2.0.9.pre.2 → 2.0.9.pre.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/CHANGELOG +9 -0
  2. data/features/builder.feature +6 -0
  3. data/features/data.feature +16 -1
  4. data/features/fonts.feature +11 -0
  5. data/features/relative_assets.feature +8 -2
  6. data/features/step_definitions/middleman_steps.rb +9 -2
  7. data/fixtures/data-app/config.rb +3 -0
  8. data/fixtures/data-app/data/pages.yml +6 -0
  9. data/fixtures/data-app/source/index.html.haml +1 -0
  10. data/fixtures/data-app/source/layout.haml +3 -0
  11. data/fixtures/fonts-app/config.rb +0 -0
  12. data/fixtures/fonts-app/source/fonts/StMarie-Thin.otf +0 -0
  13. data/fixtures/fonts-app/source/stylesheets/fonts.css.sass +3 -0
  14. data/fixtures/glob-app/config.rb +1 -0
  15. data/fixtures/glob-app/source/index.html.haml +6 -0
  16. data/fixtures/glob-app/source/stylesheets/site.css.sass +1 -0
  17. data/fixtures/test-app/config.rb +1 -0
  18. data/fixtures/test-app/data/test2.json +4 -0
  19. data/fixtures/test-app/source/data3.html.erb +1 -0
  20. data/lib/middleman/base.rb +8 -3
  21. data/lib/middleman/builder.rb +61 -22
  22. data/lib/middleman/cli.rb +2 -1
  23. data/lib/middleman/core_extensions/assets.rb +1 -1
  24. data/lib/middleman/core_extensions/compass.rb +6 -2
  25. data/lib/middleman/core_extensions/data.rb +20 -3
  26. data/lib/middleman/templates/html5.rb +1 -0
  27. data/lib/middleman/templates/mobile.rb +17 -0
  28. data/lib/middleman/templates/mobile/source/404.html +38 -0
  29. data/lib/middleman/templates/mobile/source/README.markdown +64 -0
  30. data/lib/middleman/templates/mobile/source/crossdomain.xml +25 -0
  31. data/lib/middleman/templates/mobile/source/css/style.css +236 -0
  32. data/lib/middleman/templates/mobile/source/default.appcache +17 -0
  33. data/lib/middleman/templates/mobile/source/humans.txt +43 -0
  34. data/lib/middleman/templates/mobile/source/img/h/apple-touch-icon.png +0 -0
  35. data/lib/middleman/templates/mobile/source/img/h/splash.png +0 -0
  36. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon-precomposed.png +0 -0
  37. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon.png +0 -0
  38. data/lib/middleman/templates/mobile/source/img/l/splash.png +0 -0
  39. data/lib/middleman/templates/mobile/source/img/m/apple-touch-icon.png +0 -0
  40. data/lib/middleman/templates/mobile/source/index.html +95 -0
  41. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.js +8316 -0
  42. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.min.js +16 -0
  43. data/lib/middleman/templates/mobile/source/js/libs/modernizr-custom.js +14 -0
  44. data/lib/middleman/templates/mobile/source/js/libs/respond.min.js +7 -0
  45. data/lib/middleman/templates/mobile/source/js/mylibs/helper.js +147 -0
  46. data/lib/middleman/templates/mobile/source/js/script.js +0 -0
  47. data/lib/middleman/templates/mobile/source/robots.txt +5 -0
  48. data/lib/middleman/templates/mobile/source/sitemap.xml +10 -0
  49. data/lib/middleman/templates/mobile/source/test/index.html +31 -0
  50. data/lib/middleman/templates/mobile/source/test/qunit/qunit.css +148 -0
  51. data/lib/middleman/templates/mobile/source/test/qunit/qunit.js +1265 -0
  52. data/lib/middleman/templates/mobile/source/test/tests.js +26 -0
  53. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/Readme.PDF +0 -0
  54. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx1.snippet +31 -0
  55. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx2.snippet +2 -0
  56. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/ga.aspx +195 -0
  57. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/sample.aspx +44 -0
  58. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/ga.jsp +225 -0
  59. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp1.snippet +35 -0
  60. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp2.snippet +2 -0
  61. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/sample.jsp +51 -0
  62. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/ga.php +176 -0
  63. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php1.snippet +30 -0
  64. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php2.snippet +4 -0
  65. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/sample.php +44 -0
  66. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/ga.pl +195 -0
  67. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl1.snippet +27 -0
  68. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl2.snippet +1 -0
  69. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/sample.pl +38 -0
  70. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/COPYING +202 -0
  71. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/bookmark_bubble.js +559 -0
  72. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.html +43 -0
  73. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.js +57 -0
  74. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/arrow.png +0 -0
  75. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/close.png +0 -0
  76. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/generate_base64_images +33 -0
  77. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/icon_calendar.png +0 -0
  78. data/lib/middleman/templates/mobile/source/tools/wspl/README +27 -0
  79. data/lib/middleman/templates/mobile/source/tools/wspl/databasefactory.js +45 -0
  80. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker.js +324 -0
  81. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker_test.html +393 -0
  82. data/lib/middleman/templates/mobile/source/tools/wspl/dbworkerstarter.js +32 -0
  83. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears.js +595 -0
  84. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears_test.html +404 -0
  85. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5.js +203 -0
  86. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5_test.html +468 -0
  87. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi.js +202 -0
  88. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi_test.html +51 -0
  89. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset.js +71 -0
  90. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset_test.html +86 -0
  91. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction.js +196 -0
  92. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction_test.html +221 -0
  93. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils.js +94 -0
  94. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils_test.html +84 -0
  95. data/lib/middleman/templates/mobile/source/tools/wspl/global_functions.js +72 -0
  96. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/index.html +347 -0
  97. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/simplenotes.js +503 -0
  98. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/styles.css +66 -0
  99. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/template.js +75 -0
  100. data/lib/middleman/version.rb +1 -1
  101. data/middleman-x86-mingw32.gemspec +0 -1
  102. data/middleman.gemspec +3 -2
  103. metadata +116 -51
  104. data/lib/middleman/config.ru +0 -2
  105. data/lib/middleman/features/tiny_src.rb +0 -11
@@ -0,0 +1,347 @@
1
+ <!DOCTYPE html>
2
+ <!--
3
+ Copyright 2009 Google Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ -->
17
+
18
+ <!--
19
+
20
+ A simple application showing the use of the web storage portability layer
21
+ code and cache pattern for offline web application.
22
+
23
+ -->
24
+ <html>
25
+ <head>
26
+ <title>SimpleNotes Demo</title>
27
+ <link href="styles.css" rel="stylesheet" type="text/css" />
28
+ <script src="../global_functions.js" language="javascript"
29
+ type="text/javascript"></script>
30
+ <script src="../dbwrapperapi.js" language="javascript"
31
+ type="text/javascript"></script>
32
+ <script src="../dbwrapper_html5.js" language="javascript"
33
+ type="text/javascript"></script>
34
+ <script src="../databasefactory.js" language="javascript"
35
+ type="text/javascript"></script>
36
+ <script src="template.js" language="javascript"
37
+ type="text/javascript"></script>
38
+ <script src="simplenotes.js" language="javascript"
39
+ type="text/javascript"></script>
40
+ </head>
41
+ <body>
42
+
43
+ <div id="list-note" class="screen list-note-screen" >
44
+ <div class="title-bar">Note List</div>
45
+ <div class="top command-bar">
46
+ <button id="view-note-new">New</button>
47
+ </div>
48
+ <div class="status-bar">status bar</div>
49
+ <div class="list-note-container-empty" style="display:none;"></div>
50
+ <div class="list-note-container" style="display:none;">
51
+ <div class="list-note-item">
52
+ <a href="javascript:showNote(%noteKey%);">%subject%</a>
53
+ </div>
54
+ </div>
55
+ <div class="top command-bar">
56
+ <button id="view-note-new">New</button>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="view-note" class="screen view-note-screen" style="display:none;">
61
+ <div class="title-bar">View Note</div>
62
+ <div class="top command-bar">
63
+ <button id="view-note-back">Back</button>
64
+ <button id="view-note-edit">Edit Note</button>
65
+ </div>
66
+ <div class="status-bar">status bar</div>
67
+ <div class="view-note-container" style="display:none;">
68
+ <div class="subject">%subject%</div>
69
+ <div class="body">%body%</div>
70
+ </div>
71
+ <div class="bottom command-bar">
72
+ <button id="view-note-back">Back</button>
73
+ <button id="view-note-edit">Edit Note</button>
74
+ </div>
75
+ </div>
76
+
77
+ <div id="edit-note" class="screen edit-note-screen" style="display:none;">
78
+ <div class="title-bar">Edit Note</div>
79
+ <div class="top command-bar">
80
+ <button id="edit-back-save">Save Note</button>
81
+ <button id="edit-back-revert">Revert Note</button>
82
+ </div>
83
+ <div class="status-bar">status bar</div>
84
+ <div>Subject:</div>
85
+ <textarea rows="1" style="width:100%;" id="edit-note-subject"></textarea>
86
+ <br>
87
+ <div>Note:</div>
88
+ <textarea rows="20" style="width:100%;" id="edit-note-body"></textarea>
89
+ <div class="bottom command-bar">
90
+ <button id="edit-back-save">Save Note</button>
91
+ <button id="edit-back-revert">Revert Note</button>
92
+ </div>
93
+ </div>
94
+
95
+ </body>
96
+ <script>
97
+
98
+ /**
99
+ * Tests the given DOM element for a specific CSS class.
100
+ * @param {Object} e A DOM element.
101
+ * @param {string} css A CSS class identifier to match.
102
+ * @return {boolean} true if the element has the CSS class.
103
+ */
104
+ function hasCssClass(e, css) {
105
+ return e && e.className && e.className.search(css + '( |$)') > -1;
106
+ };
107
+
108
+ /**
109
+ * Finds the parent element of a given element that has
110
+ * the specified CSS class.
111
+ * @param {Object} e A DOM element.
112
+ * @param {string} css A CSS class identifier to match.
113
+ * @param {Object} opt_stop A parent DOM element to stop at.
114
+ * @return {Object} A DOM element parent of e or null.
115
+ */
116
+ function findParentOfClass(e, css, opt_stop) {
117
+ var stop = opt_stop || document;
118
+ while (e && e != stop && !hasCssClass(e, css)) {
119
+ e = e.parentElement;
120
+ }
121
+ return hasCssClass(e, css) ? e : null;
122
+ };
123
+
124
+ /**
125
+ * Navigates to the note creation screen.
126
+ */
127
+ function editNote(event) {
128
+ google.logger('editNote: <' + currentNote.subject + '>');
129
+ hideBlock('#edit-note .status-bar');
130
+
131
+ var subject = document.getElementById('edit-note-subject');
132
+ var body = document.getElementById('edit-note-body');
133
+ subject.value = currentNote.subject;
134
+ body.value = currentNote.body;
135
+
136
+ google.logger('end of editNote');
137
+ }
138
+
139
+ /**
140
+ * Saves a note.
141
+ */
142
+ function saveNote(event) {
143
+ google.logger('saveNote: do database or other activity here');
144
+ // Copy contents from fields and save.
145
+ var subject = document.getElementById('edit-note-subject');
146
+ var body = document.getElementById('edit-note-body');
147
+
148
+ cache.applyUiChange(currentNote.noteKey, subject.value,
149
+ body.value, showNote);
150
+ google.logger('saveNote');
151
+ }
152
+
153
+ /**
154
+ * Abandons the note.
155
+ */
156
+ function abandonNote(event) {
157
+ google.logger('abandonNote: do database or other activity here');
158
+ showNote(currentNote.noteKey);
159
+ google.logger('abandonNote');
160
+ }
161
+
162
+ /**
163
+ * Prepares to edit a new note.
164
+ */
165
+ function newNote(event) {
166
+ google.logger('newNote: do database or other activity here');
167
+ alert('not implemented');
168
+ // grab the contents and inject into the database.
169
+ var subject = document.getElementById('edit-note-subject');
170
+ var body = document.getElementById('edit-note-body');
171
+ subject.value = '';
172
+ body.value = '';
173
+ google.logger('newNote');
174
+ }
175
+
176
+ /**
177
+ * Maps button names to customizer functions.
178
+ */
179
+ var buttonHandlerSpecification = {
180
+ 'view-note-new': ['edit-note', newNote],
181
+ 'view-note-back': ['list-note', showNoteList],
182
+ 'view-note-edit': ['edit-note', editNote],
183
+ 'edit-back-save': ['view-note', saveNote],
184
+ 'edit-back-revert': ['view-note', abandonNote]
185
+ };
186
+
187
+ /**
188
+ * Hides a block specified by a CSS selector and parent node.
189
+ * @param {string} selector A CSS selector string.
190
+ * @param {Object} opt_el A DOM element.
191
+ */
192
+ function hideBlock(selector, opt_el) {
193
+ var el = opt_el || document;
194
+ var targetEl = el.querySelector(selector);
195
+ targetEl.style.display = 'none';
196
+ }
197
+
198
+ /**
199
+ * Shows a block specified by a CSS selector and parent node, inserting
200
+ * the specified string as the innerHTML property of the selected block.
201
+ * @param {string} selector A CSS selector
202
+ * @param {string} html String to assign to the innerHTML property.
203
+ * @param {Object} opt_el A DOM element.
204
+ */
205
+ function showBlock(selector, html, opt_el) {
206
+ var el = opt_el || document;
207
+ var targetEl = el.querySelector(selector);
208
+ if (html) targetEl.innerHTML = html;
209
+ targetEl.style.display = 'block';
210
+ }
211
+
212
+ /**
213
+ * Handles all button events.
214
+ */
215
+ function buttonHandler(event) {
216
+ google.logger('clicked on: ' + event.target.id);
217
+
218
+ var currentScreen = findParentOfClass(event.target, 'screen');
219
+ var nextScreen = document.getElementById(
220
+ buttonHandlerSpecification[event.target.id][0]);
221
+ var helperFunction = buttonHandlerSpecification[event.target.id][1];
222
+
223
+ // All button handlers have a common implementation.
224
+ // 1. hide current screen
225
+ currentScreen.style.display = 'none';
226
+
227
+ // 2. show new screen
228
+ nextScreen.style.display = 'block';
229
+
230
+ // 3. Activate status
231
+ showBlock('.status-bar', 'Working...', nextScreen);
232
+
233
+ // 4. Call customizer function (which will probably query the model
234
+ // stored in the cache.
235
+ helperFunction(event);
236
+
237
+ // 5. A (possibly asynchronous) callback from the customizer will
238
+ // finish the action by running the appropriate view function.
239
+ google.logger('end of button Handler');
240
+ }
241
+
242
+ /**
243
+ * Call back from a specific JavaScript url to show a specific
244
+ * note.
245
+ * @param {number} noteKey
246
+ * @return {boolean}
247
+ */
248
+ function showNote(noteKey) {
249
+ hideBlock('#list-note');
250
+ showBlock('#view-note .status-bar', 'Working...');
251
+ showBlock('#view-note');
252
+ cache.getValues('fullnote', [noteKey], renderFullNote);
253
+ return false;
254
+ }
255
+
256
+ /**
257
+ * Generates HTML for a note and display it.
258
+ * @param {Object} oneNote
259
+ * @param {boolean} complete True if the note has been provided or
260
+ * if an updated note is arriving. (Necessary for future support for
261
+ * sub-note updates.)
262
+ */
263
+ function renderFullNote(oneNote, complete) {
264
+ google.logger('renderFullNote');
265
+
266
+ showBlock('.view-note-container', noteViewTemplate.process(oneNote));
267
+ if (complete) {
268
+ hideBlock('#view-note .status-bar');
269
+ }
270
+ currentNote = oneNote;
271
+ google.logger('done renderFullNote');
272
+ }
273
+
274
+ /**
275
+ * Generates HTML for a note list and display it.
276
+ * @param {Array.<Object>} noteHeaders
277
+ * @param {boolean} complete True if all in-existence notes that lie
278
+ * in the query range have been provided even if less than what was
279
+ * requested.
280
+ */
281
+ function renderNoteList(noteHeaders, complete) {
282
+ google.logger('renderNoteList: ' + noteHeaders.length);
283
+
284
+ if (noteHeaders.length == 0) {
285
+ showBlock('.list-note-container-empty', 'No notes');
286
+ } else {
287
+ var notes = noteHeaders.map(function(n) {
288
+ return noteListTemplate.process(n);
289
+ });
290
+ showBlock('.list-note-container', notes.join());
291
+ }
292
+ if (complete) {
293
+ hideBlock('#list-note .status-bar');
294
+ }
295
+ google.logger('done renderNoteList');
296
+ }
297
+
298
+ /**
299
+ * Query for an updated note list.
300
+ */
301
+ function showNoteList() {
302
+ google.logger('showNoteList');
303
+ cache.getValues('list', [0,20], renderNoteList);
304
+ currentNote = undefined;
305
+ google.logger('end of showNoteList');
306
+ }
307
+
308
+ /**
309
+ * A constructor for the controller. Sets up the UI and
310
+ * queries the cache for initial notelist contents.
311
+ */
312
+ function createController() {
313
+ google.logger('createController:');
314
+ var buttons = document.querySelectorAll('.command-bar button');
315
+ Array.prototype.forEach.call(buttons, function(b) {
316
+ b.addEventListener('click', buttonHandler);
317
+ });
318
+ google.logger('controller is live');
319
+ showNoteList();
320
+ }
321
+
322
+ google.logger('about to fetch the template');
323
+ var currentNote = undefined;
324
+
325
+ var noteListTemplate = new google.wspl.simplenotes.Template(
326
+ document.querySelector('.list-note-container').innerHTML);
327
+
328
+ var noteViewTemplate = new google.wspl.simplenotes.Template(
329
+ document.querySelector('.view-note-container').innerHTML);
330
+
331
+ // var noteEditTemplate = new google.wspl.simplenotes.Template();
332
+
333
+ google.logger('created the template');
334
+
335
+ // debugging code... tear out...
336
+ // test the template construction...
337
+ // showBlock('.list-note-container', noteListTemplate.process(
338
+ // {noteKey: 123, subject: 'hi rob'}));
339
+
340
+
341
+ google.logger('loaded database');
342
+ var cache = new google.wspl.simplenotes.Cache();
343
+ cache.startCache(createController);
344
+
345
+ google.logger('finished loading page');
346
+ </script>
347
+ </html>
@@ -0,0 +1,503 @@
1
+ /*
2
+ Copyright 2009 Google Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * @fileoverview A concrete example of the cache pattern for building an offline
19
+ * webapplication: a cache for SimpleNotes.
20
+ */
21
+
22
+ google.wspl.simplenotes = google.wspl.simplenotes || {};
23
+ google.logger('start simplenotes.js');
24
+
25
+ /**
26
+ * Status keys for the write buffer.
27
+ * @enum {number}
28
+ */
29
+ google.wspl.simplenotes.WriteBufferStates = {
30
+ /**
31
+ * The update is in flight to the server.
32
+ */
33
+ INFLIGHT: 1,
34
+
35
+ /**
36
+ * The update needs to be (re)sent to the server but is not in flight.
37
+ */
38
+ SEND: 2,
39
+
40
+ /**
41
+ * The update needs to be applied to the cached notes.
42
+ */
43
+ REAPPLY: 8
44
+ };
45
+
46
+ /**
47
+ * Creates a SimpleNotes cache wrapping a backing database.
48
+ * @constructor
49
+ */
50
+ google.wspl.simplenotes.Cache = function() {
51
+ this.dbms_ = google.wspl.DatabaseFactory.createDatabase(
52
+ 'simple-notes', 'http://yourdomain/dbworker.js');
53
+
54
+ /**
55
+ * Cache directory is a two-tuple over a range. (Holes
56
+ * must be allowed to support delection.)
57
+ * This is the lower (inclusive) bound of the cached range.
58
+ * @type {number}
59
+ * @private
60
+ */
61
+ this.start_ = -1;
62
+
63
+ /**
64
+ *
65
+ * This is the upper (inclusive) bound of the cached range.
66
+ * @type {number}
67
+ * @private
68
+ */
69
+ this.end_ = -1;
70
+
71
+ /**
72
+ * Start of range of notes known to exist on server at time of last
73
+ * response.
74
+ * @param {number}
75
+ */
76
+ this.serverStart_ = -1;
77
+
78
+ /**
79
+ * End of range of notes known to exist on server at time of last
80
+ * response.
81
+ * @param {number}
82
+ */
83
+ this.serverEnd_ = -1;
84
+
85
+ /**
86
+ * Time of last refresh.
87
+ * @type {number}
88
+ * @private
89
+ */
90
+ this.lastRefresh_ = -1;
91
+
92
+ /**
93
+ * Last missing query.
94
+ * @type {Object}
95
+ * @private
96
+ */
97
+ this.lastMiss_ = undefined;
98
+ };
99
+
100
+ /**
101
+ * Interval between refreshes in milliseconds.
102
+ * @type {number}
103
+ * @private
104
+ */
105
+ google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_ = 2000;
106
+
107
+ google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_ =
108
+ new google.wspl.Statement(
109
+ 'CREATE TABLE IF NOT EXISTS cached_notes (' +
110
+ 'noteKey INTEGER UNIQUE PRIMARY KEY,' +
111
+ 'subject TEXT,' +
112
+ 'body TEXT' +
113
+ ');'
114
+ );
115
+
116
+ google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_ =
117
+ new google.wspl.Statement(
118
+ 'CREATE TABLE IF NOT EXISTS write_buffer (' +
119
+ 'sequence INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT,' +
120
+ 'noteKey INTEGER,' +
121
+ 'status INTEGER,' +
122
+ 'subject TEXT,' +
123
+ 'body TEXT' +
124
+ ');'
125
+ );
126
+
127
+ google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_ =
128
+ new google.wspl.Statement(
129
+ 'SELECT MIN(noteKey) as minNoteKey FROM cached_notes;');
130
+
131
+ google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_ =
132
+ new google.wspl.Statement(
133
+ 'SELECT MAX(noteKey) as maxNoteKey FROM cached_notes;');
134
+
135
+ /**
136
+ * Builds a cache and writebuffer combination for notes and then
137
+ * invokes the given callback.
138
+ * @param {function) callback.
139
+ */
140
+ google.wspl.simplenotes.Cache.prototype.startCache = function(callback) {
141
+ google.logger('startCache');
142
+ var statc = 0;
143
+ var self = this;
144
+
145
+ var perStatCallback = function(tx, result) {
146
+ google.logger('perStatCallback');
147
+ if (statc == 4) {
148
+ self.start_ = (result.isValidRow()) ? result.getRow().minNoteKey : -1;
149
+ self.serverStart_ = self.start_; // Temporary. Remove when server exists.
150
+ } else if (statc == 5) {
151
+ self.end_ = (result.isValidRow()) ? result.getRow().maxNoteKey : -1;
152
+ self.serverEnd_ = self.end_; // Temporary. Remove when server exists.
153
+ }
154
+ statc++;
155
+ };
156
+
157
+ this.dbms_.executeAll([
158
+ google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_,
159
+ google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_,
160
+ google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_,
161
+ google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_,
162
+ google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_,
163
+ google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_],
164
+ {onSuccess: perStatCallback, onFailure: this.logError_},
165
+ {onSuccess: callback, onFailure: this.logError_});
166
+ google.logger('finished startCache');
167
+ };
168
+
169
+ /**
170
+ * Stub function to be replaced with a server communication.
171
+ * @param {Array.<Object>} updates Payload to send to server.
172
+ */
173
+ google.wspl.simplenotes.Cache.prototype.sendXhrPost = function(updates) {
174
+ google.logger('Should dispatch XHR to server now.');
175
+ };
176
+
177
+ /**
178
+ * @type {google.wspl.Statement}
179
+ * @private
180
+ */
181
+ google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_ =
182
+ new google.wspl.Statement(
183
+ 'SELECT noteKey, subject from cached_notes WHERE ' +
184
+ 'noteKey >= ? AND ' +
185
+ 'noteKey <= ? ' +
186
+ ';'
187
+ );
188
+
189
+ /**
190
+ * Tests if the given range is stored in the cache.
191
+ * Note that this mechanism requires extension to handle the
192
+ * creation of new notes.
193
+ * @param {number} start Lower bound (inclusive) on range.
194
+ * @param {number} end Uppder bound (inclusive) on range.
195
+ * @private
196
+ */
197
+ google.wspl.simplenotes.Cache.prototype.isCacheHit_ = function(start, end) {
198
+ return start >= this.start_ && end <= this.end_
199
+ };
200
+
201
+ /**
202
+ * Logs a possibly useful error message.
203
+ * @param {Object} error An error descriptor.
204
+ * @private
205
+ */
206
+ google.wspl.simplenotes.Cache.prototype.logError_ = function(error) {
207
+ google.logger('Simple Notes Cache is sad: ' + error);
208
+ };
209
+
210
+ /**
211
+ * Queries the cache for a list of note headers.
212
+ * @param {number} start The lower id in a range of notes.
213
+ * @param {number} end The higher id in a range of notes.
214
+ * @param {function(Array.<Object>)} valuesCallback A function to call
215
+ * with the result set after the transaction has completed.
216
+ * @private
217
+ */
218
+ google.wspl.simplenotes.Cache.prototype.getNoteList_ = function(start, end,
219
+ valuesCallback) {
220
+ var notes = [];
221
+
222
+ var accumulateResults = function(tx, result) {
223
+ for(; result.isValidRow(); result.next()) {
224
+ notes.push(result.getRow());
225
+ google.logger('pushed...');
226
+ }
227
+ };
228
+
229
+ var inTransactionGetNotes = function(tx) {
230
+ tx.execute(google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_.
231
+ createStatement([start, end]), {
232
+ onSuccess: accumulateResults,
233
+ onFailure: this.logError_});
234
+ };
235
+
236
+ var hit = this.isCacheHit_(start, end);
237
+ this.dbms_.createTransaction(inTransactionGetNotes, {onSuccess: function() {
238
+ valuesCallback(notes, hit);
239
+ }, onFailure: this.logError_});
240
+
241
+ if (hit) {
242
+ this.fetchFromServer(this.start_, this.end_); // Refresh
243
+ } else {
244
+ this.fetchFromServer(Math.min(this.start_, start), Math.max(this.end_, end));
245
+ this.lastMiss_ = {callback: valuesCallback, start: start, end: end};
246
+ }
247
+ };
248
+
249
+ /**
250
+ * @type {google.wspl.Statement}
251
+ * @private
252
+ */
253
+ google.wspl.simplenotes.Cache.GET_ONE_NOTE_ =
254
+ new google.wspl.Statement(
255
+ 'SELECT noteKey, subject, body from cached_notes WHERE ' +
256
+ 'noteKey = ? ' +
257
+ ';'
258
+ );
259
+
260
+ /**
261
+ * Queries the cache for a list of note headers.
262
+ * @param {number} noteId The note to get from the cache.
263
+ * @param {function(Array.<Object>)} valuesCallback A function to call
264
+ * with the result set after the transaction has completed.
265
+ * @private
266
+ */
267
+ google.wspl.simplenotes.Cache.prototype.getOneNote_ = function(noteId,
268
+ callback) {
269
+ var note;
270
+
271
+ this.dbms_.execute(google.wspl.simplenotes.Cache.GET_ONE_NOTE_.
272
+ createStatement([noteId]),
273
+ {onSuccess: function(tx, result) { note = result.getRow(); },
274
+ onFailure: this.logError_},
275
+ {onSuccess: function() { callback(note, true); },
276
+ onFailure: this.logError_});
277
+ };
278
+
279
+ /**
280
+ * Queries the cache for either a list of notes or a single note including
281
+ * its body.
282
+ * @param {string} type The kind of values desired: 'list' or 'fullnote'.
283
+ * @param {Array.<number>} query The query for the values.
284
+ * @param {function(Array.<Object>)} valuesCallback A function to call
285
+ * with the result set after the transaction has completed.
286
+ */
287
+ google.wspl.simplenotes.Cache.prototype.getValues = function(type,
288
+ query, valuesCallback) {
289
+
290
+ // Reduce any query to what would be available from the server
291
+ query[0] = Math.max(this.serverStart_, query[0]);
292
+ query[1] = Math.min(this.serverEnd_, query[1]);
293
+
294
+ if (type == 'list') {
295
+ this.getNoteList_(query[0], query[1], valuesCallback);
296
+ } else if (type == 'fullnote') {
297
+ this.getOneNote_(query[0], valuesCallback);
298
+ }
299
+ };
300
+
301
+ /**
302
+ * SQL trigger to insert a new change from write buffer to
303
+ * cache.
304
+ * @private
305
+ */
306
+ google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_ =
307
+ new google.wspl.Statement(
308
+ 'CREATE TRIGGER IF NOT EXISTS updateTrigger ' +
309
+ 'AFTER INSERT ON write_buffer ' +
310
+ 'BEGIN ' +
311
+ ' REPLACE INTO cached_notes ' +
312
+ ' SELECT noteKey, subject, body ' +
313
+ ' FROM write_buffer WHERE status & 8 = 8; ' +
314
+ ' UPDATE write_buffer SET status = status & ~ 8; ' +
315
+ 'END;'
316
+ );
317
+
318
+ /**
319
+ * SQL trigger to replay changes from the write buffer to
320
+ * the cache.
321
+ * @private
322
+ */
323
+ google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_ =
324
+ new google.wspl.Statement(
325
+ 'CREATE TRIGGER IF NOT EXISTS replayTrigger ' +
326
+ 'AFTER UPDATE ON write_buffer WHEN NEW.status & 8 = 8 ' +
327
+ 'BEGIN ' +
328
+ ' REPLACE INTO cached_notes ' +
329
+ ' SELECT noteKey, subject, body ' +
330
+ ' FROM write_buffer ' +
331
+ ' WHERE noteKey = NEW.noteKey ' +
332
+ ' ORDER BY sequence ASC;' +
333
+ ' UPDATE write_buffer SET status = status & ~ 8; ' +
334
+ 'END;'
335
+ );
336
+
337
+
338
+ /**
339
+ * SQL statement to mark actions for replay.
340
+ */
341
+ google.wspl.simplenotes.Cache.MARK_FOR_REPLAY =
342
+ new google.wspl.Statement(
343
+ 'UPDATE write_buffer SET status = status | 8;');
344
+
345
+ /**
346
+ * SQL statement to insert notes updates.
347
+ * @type {!google.wspl.Statement}
348
+ * @private
349
+ */
350
+ google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_ =
351
+ new google.wspl.Statement(
352
+ 'INSERT INTO write_buffer (' +
353
+ 'noteKey, status, subject, body' +
354
+ ') ' + 'VALUES(?,?,?,?);');
355
+
356
+ /**
357
+ * Updates the given entry and write a new write buffer entry.
358
+ * @param {number} noteKey
359
+ * @param {string} subject
360
+ * @param {string} body
361
+ * @param {function(number)} ackCallback
362
+ */
363
+ google.wspl.simplenotes.Cache.prototype.applyUiChange = function(noteKey,
364
+ subject, body, ackCallback) {
365
+ var self = this;
366
+ var update = [noteKey, 2 | 8, subject, body];
367
+ var stat = google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_.createStatement(
368
+ update);
369
+
370
+ this.dbms_.execute(stat, null, {onSuccess: function() {
371
+ google.logger('applyUiChange cb');
372
+ ackCallback(noteKey);
373
+ }, onFailure: function (error) {
374
+ self.logError_(error);
375
+ ackCallback(-1);
376
+ }});
377
+ };
378
+
379
+ /**
380
+ * SQL statement to insert notes updates.
381
+ * @type {!google.wspl.Statement}
382
+ * @private
383
+ */
384
+ google.wspl.simplenotes.Cache.INSERT_NOTE_ =
385
+ new google.wspl.Statement(
386
+ 'REPLACE INTO cached_notes (noteKey, subject, body) ' +
387
+ 'VALUES(?,?,?);' );
388
+
389
+ /**
390
+ * SQL statement to force replay of pending actions by setting a bit
391
+ * flag on each write-buffer row indicating that it should be reapplied
392
+ * to the contents of the cache.
393
+ * @type {!google.wspl.Statement}
394
+ * @private
395
+ */
396
+ google.wspl.simplenotes.Cache.FORCE_REPLAY_ =
397
+ new google.wspl.Statement(
398
+ 'UPDATE write_buffer SET status = status | 8;' );
399
+
400
+ /**
401
+ * SQL statement to delete notes no longer to be cached.
402
+ * @type {!google.wspl.Statement}
403
+ * @private
404
+ */
405
+ google.wspl.simplenotes.Cache.EVICT_ =
406
+ new google.wspl.Statement(
407
+ 'DELETE FROM cached_notes WHERE noteKey < ? OR noteKey > ?;');
408
+
409
+ /**
410
+ * Applies the changes delivered from the server by first inserting
411
+ * them into the cache and reapplying the write-buffer to the cache.
412
+ * @param {!Array.<Object>} notes An array of arrays.
413
+ */
414
+ google.wspl.simplenotes.Cache.prototype.insertUpdate = function(notes) {
415
+ var self = this; var stats = [];
416
+ var start = notes[0].noteKey;
417
+ var end = notes[0].noteKey;
418
+
419
+ for (var i = 0; i < notes.length; i++) {
420
+ stats.push(google.wspl.simplenotes.Cache.INSERT_NOTE_.
421
+ createStatement([notes[i].noteKey, notes[i].subject, notes[i].body]));
422
+ start = Math.min(start, notes[0].noteKey);
423
+ end = Math.max(end, notes[0].noteKey);
424
+ }
425
+ stats.push(google.wspl.simplenotes.Cache.EVICT_.createStatement([start, end]));
426
+ stats.push(google.wspl.simplenotes.Cache.FORCE_REPLAY_);
427
+
428
+ var inTrans = function(tx) {
429
+ self.start_ = start;
430
+ self.end_ = end;
431
+ tx.executeAll(stats);
432
+ };
433
+
434
+ var afterInsert = function(tx) {
435
+ if (this.lastMiss_ &&
436
+ this.isCacheHit_(this.lastMiss_.start, this.lastMiss_.end)) {
437
+ this.lastMiss_.callback(notes);
438
+ this.lastMiss_ = undefined;
439
+ }
440
+ };
441
+
442
+ this.dbms_.createTransaction(inTrans, {onSuccess: afterInsert,
443
+ onError: this.logError_});
444
+ };
445
+
446
+ /**
447
+ * SQL statement to force replay of pending actions by setting a bit
448
+ * flag on each write-buffer row indicating that it should be reapplied
449
+ * to the contents of the cache.
450
+ * @type {!google.wspl.Statement}
451
+ * @private
452
+ */
453
+ google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_ =
454
+ new google.wspl.Statement(
455
+ 'SELECT noteKey, subject, body FROM write_buffer WHERE status & 2 = 2;');
456
+
457
+
458
+
459
+ /**
460
+ * SQL statement to mark write buffer statements as inflight.
461
+ * @type {!google.wspl.Statement}
462
+ * @private
463
+ */
464
+ google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_ =
465
+ new google.wspl.Statement(
466
+ 'UPDATE write_buffer SET status = status & ~2 | 1 WHERE status & 2 = 2;');
467
+
468
+ /**
469
+ * Fetches new material from the server as required.
470
+ * @param {number} start
471
+ * @param {number} end
472
+ * @param {function} opt_valueCallBack
473
+ */
474
+ google.wspl.simplenotes.Cache.prototype.fetchFromServer = function(start,
475
+ end) {
476
+ google.logger('fetchFromServer');
477
+ var now = this.dbms_.getCurrentTime();
478
+ if (start >= this.start_ && end <= this.end_ &&
479
+ now - this.lastRefresh_ <
480
+ google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_) {
481
+ return;
482
+ }
483
+
484
+ var updates = []; var self = this; var flag = 1; var sql = []
485
+ sql.push(google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_);
486
+ sql.push(google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_);
487
+
488
+ var accumulateUpdates = function(tx, rs) {
489
+ if (flag == 1) {
490
+ for(; rs.isValidRow(); rs.next()) { updates.push(['u', rs.getRow()]); }
491
+ flag++;
492
+ }
493
+ };
494
+
495
+ var ackAndPost = function() {
496
+ updates.push(['q', {start: start, end: end}]);
497
+ self.sendXhrPost(updates);
498
+ };
499
+
500
+ this.dbms_.executeAll(sql,
501
+ {onSuccess: accumulateUpdates, onFailure: this.logError_},
502
+ {onSuccess: ackAndPost, onFailure: this.logError_});
503
+ };