pushpop-rails 1.0.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 (31) hide show
  1. data/README.md +42 -0
  2. data/Rakefile +29 -0
  3. data/lib/generators/pushpop/install/install_generator.rb +17 -0
  4. data/lib/pushpop-rails.rb +4 -0
  5. data/lib/pushpop/rails.rb +8 -0
  6. data/lib/pushpop/rails/engine.rb +7 -0
  7. data/lib/pushpop/rails/version.rb +5 -0
  8. data/vendor/assets/Pushpop/background.png +0 -0
  9. data/vendor/assets/Pushpop/background@2x.png +0 -0
  10. data/vendor/assets/Pushpop/externals/scrollkit/background.png +0 -0
  11. data/vendor/assets/Pushpop/externals/scrollkit/scrollkit.css +202 -0
  12. data/vendor/assets/Pushpop/externals/scrollkit/scrollkit.js +924 -0
  13. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.eot +0 -0
  14. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.svg +57 -0
  15. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.ttf +0 -0
  16. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.woff +0 -0
  17. data/vendor/assets/Pushpop/pushpop-modal-view-stack/pushpop-modal-view-stack.css +148 -0
  18. data/vendor/assets/Pushpop/pushpop-modal-view-stack/pushpop-modal-view-stack.js +306 -0
  19. data/vendor/assets/Pushpop/pushpop-popover-view-stack/pushpop-popover-view-stack.css +170 -0
  20. data/vendor/assets/Pushpop/pushpop-popover-view-stack/pushpop-popover-view-stack.js +278 -0
  21. data/vendor/assets/Pushpop/pushpop-split-view/pushpop-split-view.css +38 -0
  22. data/vendor/assets/Pushpop/pushpop-split-view/pushpop-split-view.js +33 -0
  23. data/vendor/assets/Pushpop/pushpop-tab-view/pushpop-tab-view.css +130 -0
  24. data/vendor/assets/Pushpop/pushpop-tab-view/pushpop-tab-view.js +298 -0
  25. data/vendor/assets/Pushpop/pushpop-table-view/pushpop-table-view.css +1273 -0
  26. data/vendor/assets/Pushpop/pushpop-table-view/pushpop-table-view.js +2275 -0
  27. data/vendor/assets/Pushpop/pushpop.css +2243 -0
  28. data/vendor/assets/Pushpop/pushpop.js +1554 -0
  29. data/vendor/assets/javascripts/pushpop_rails.js +7 -0
  30. data/vendor/assets/stylesheets/pushpop_rails.css +9 -0
  31. metadata +92 -0
@@ -0,0 +1,2275 @@
1
+ ;'use strict';
2
+
3
+ // The base Pushpop object.
4
+ var Pushpop = window['Pushpop'] || {};
5
+
6
+ /**
7
+ Creates a new TableView.
8
+ @param {HTMLUListElement} element The UL element to initialize as a new TableView.
9
+ @constructor
10
+ */
11
+ Pushpop.TableView = function TableView(element) {
12
+ if (!element) return;
13
+
14
+ var $element = this.$element = $(element);
15
+ element = this.element = $element[0];
16
+
17
+ var tableView = element.tableView;
18
+ if (tableView) return tableView;
19
+
20
+ var self = element.tableView = this;
21
+
22
+ // Make sure this table view has a scroll view, otherwise, stop initialization.
23
+ var view = this.getView();
24
+ if (!view) return;
25
+
26
+ var scrollView = this.scrollView = view.getScrollView();
27
+ if (!scrollView) return;
28
+
29
+ // Determine if a search bar should be added to this table view.
30
+ var containsSearchBar = $element.attr('data-contains-search-bar') || 'false';
31
+ if ((containsSearchBar = containsSearchBar !== 'false')) this.setSearchBar(new Pushpop.TableViewSearchBar(this));
32
+
33
+ // Set up the loading message element for this table view.
34
+ var $loadingMessageElement = this._$loadingMessageElement = $('<div class="pp-table-view-loading-message pp-hidden"/>').insertBefore(scrollView.$content);
35
+ var $loadingSpinnerElement = this._$loadingSpinnerElement = $('<div class="pp-table-view-loading-spinner"/>');
36
+ var loadingSpinner = this._loadingSpinner = new Spinner({
37
+ lines: 12, // The number of lines to draw
38
+ length: 6, // The length of each line
39
+ width: 4, // The line thickness
40
+ radius: 8, // The radius of the inner circle
41
+ corners: 1, // Corner roundness (0..1)
42
+ color: '#111', // #rgb or #rrggbb
43
+ speed: 1, // Rounds per second
44
+ trail: 60, // Afterglow percentage
45
+ hwaccel: true // Whether to use hardware acceleration
46
+ }).spin($loadingSpinnerElement[0]);
47
+
48
+ this.setLoadingMessageHtml(this.getLoadingMessageHtml());
49
+
50
+ // Instantiate instance variables.
51
+ this._renderedCells = [];
52
+ this._reusableCells = {};
53
+ this._selectedRowIndexes = [];
54
+
55
+ // Determine if this is running on a device with touch support.
56
+ var isTouchSupported = !!('ontouchstart' in window);
57
+
58
+ // Handle virtual rendering of table view cells when the table view is scrolled.
59
+ scrollView.$bind('scroll', function(evt) {
60
+ if (self.getDrawing()) return;
61
+
62
+ var minimumScrollPositionThreshold = self.getMinimumScrollPositionThreshold();
63
+ var maximumScrollPositionThreshold = self.getMaximumScrollPositionThreshold();
64
+ var scrollPosition = self.getScrollPosition();
65
+
66
+ if ((minimumScrollPositionThreshold !== -1 && scrollPosition < minimumScrollPositionThreshold) ||
67
+ (maximumScrollPositionThreshold !== -1 && scrollPosition >= maximumScrollPositionThreshold)) self.draw();
68
+ });
69
+
70
+ // Force a redraw when the DidScrollToTop event occurs on the scroll view.
71
+ scrollView.$bind(ScrollKit.ScrollView.EventType.DidScrollToTop, function(evt) { self.draw(); });
72
+
73
+ // Handle mouse/touch events to allow the user to tap accessory buttons.
74
+ var isPendingAccessoryButtonTap = false;
75
+
76
+ $element.delegate('.pp-table-view-cell-accessory', isTouchSupported ? 'touchstart' : 'mousedown', function(evt) {
77
+ isPendingAccessoryButtonTap = true;
78
+ });
79
+ $element.delegate('.pp-table-view-cell-accessory', isTouchSupported ? 'touchend' : 'mouseup', function(evt) {
80
+ if (!isPendingAccessoryButtonTap) return;
81
+ isPendingAccessoryButtonTap = false;
82
+
83
+ var tableViewCell = $(this).parent()[0].tableViewCell;
84
+ if (!tableViewCell) return;
85
+
86
+ var index = tableViewCell.getIndex();
87
+
88
+ // Trigger the AccessoryButtonTappedForRowWithIndex event on this and all parent table view elements.
89
+ self.triggerEventOnParentTableViews($.Event(Pushpop.TableView.EventType.AccessoryButtonTappedForRowWithIndex, {
90
+ tableView: self,
91
+ tableViewCell: tableViewCell,
92
+ index: index,
93
+ item: self.getDataSource().getFilteredItemAtIndex(index),
94
+ element: this
95
+ }), true);
96
+ });
97
+
98
+ // Handle mouse/touch events to allow the user to tap editing accessory buttons.
99
+ var isPendingEditingAccessoryButtonTap = false;
100
+
101
+ $element.delegate('.pp-table-view-cell-editing-accessory', isTouchSupported ? 'touchstart' : 'mousedown', function(evt) {
102
+ isPendingEditingAccessoryButtonTap = true;
103
+ });
104
+ $element.delegate('.pp-table-view-cell-editing-accessory', isTouchSupported ? 'touchend' : 'mouseup', function(evt) {
105
+ if (!isPendingEditingAccessoryButtonTap) return;
106
+ isPendingEditingAccessoryButtonTap = false;
107
+
108
+ var tableViewCell = $(this).parent()[0].tableViewCell;
109
+ if (!tableViewCell) return;
110
+
111
+ var index = tableViewCell.getIndex();
112
+
113
+ // Trigger the EditingAccessoryButtonTappedForRowWithIndex event on this and all parent table view elements.
114
+ self.triggerEventOnParentTableViews($.Event(Pushpop.TableView.EventType.EditingAccessoryButtonTappedForRowWithIndex, {
115
+ tableView: self,
116
+ tableViewCell: tableViewCell,
117
+ index: index,
118
+ item: self.getDataSource().getFilteredItemAtIndex(index),
119
+ element: this
120
+ }), true);
121
+ });
122
+
123
+ // Handle mouse/touch events to allow the user to make row selections.
124
+ var isPendingSelection = false, selectionTimeout = null;
125
+
126
+ $element.delegate('li', isTouchSupported ? 'touchstart' : 'mousedown', function(evt) {
127
+ var tableViewCell = this.tableViewCell;
128
+ if (!tableViewCell) return;
129
+
130
+ // Don't allow row to be selected if an accessory button is pending a tap.
131
+ if (isPendingAccessoryButtonTap || isPendingEditingAccessoryButtonTap) return;
132
+
133
+ isPendingSelection = true;
134
+
135
+ selectionTimeout = window.setTimeout(function() {
136
+ if (!isPendingSelection) return;
137
+ isPendingSelection = false;
138
+
139
+ tableViewCell.didReceiveTap();
140
+ self.selectRowAtIndex(tableViewCell.getIndex());
141
+ }, self.getSelectionTimeoutDuration());
142
+ });
143
+ $element.delegate('li', isTouchSupported ? 'touchend' : 'mouseup', function(evt) {
144
+ var tableViewCell = this.tableViewCell;
145
+ if (!tableViewCell || !isPendingSelection) return;
146
+
147
+ isPendingSelection = false;
148
+
149
+ window.clearTimeout(selectionTimeout);
150
+
151
+ tableViewCell.didReceiveTap();
152
+ self.selectRowAtIndex(this.tableViewCell.getIndex());
153
+ evt.preventDefault();
154
+ });
155
+
156
+ // Cancel any pending accessory, editing accessory or row selections if a mouse or touch move occurs.
157
+ $element.bind(isTouchSupported ? 'touchmove' : 'mousemove', function(evt) {
158
+ if (isPendingAccessoryButtonTap) isPendingAccessoryButtonTap = false;
159
+ else if (isPendingEditingAccessoryButtonTap) isPendingEditingAccessoryButtonTap = false;
160
+ else if (isPendingSelection) {
161
+ isPendingSelection = false;
162
+ window.clearTimeout(selectionTimeout);
163
+ }
164
+ });
165
+
166
+ // Create a new data source from a data set URL.
167
+ var dataSetUrl = $element.attr('data-set-url');
168
+ if (dataSetUrl) $.getJSON(dataSetUrl, function(dataSet) {
169
+ $element.html(null);
170
+ self.setDataSource(new Pushpop.TableViewDataSource(dataSet));
171
+ });
172
+
173
+ // Create a new data source from existing <li/> elements.
174
+ else (function(self, $element) {
175
+ var dataSet = [];
176
+ var dashAlpha = /-([a-z]|[0-9])/ig;
177
+ var camelCase = function(string) { return string.replace(dashAlpha, function(all, letter) { return (letter + '').toUpperCase(); }); };
178
+
179
+ $element.children('li').each(function(index, element) {
180
+ var data = { title: $(element).html() };
181
+ var attributes = element.attributes;
182
+ var attribute, attributeName, attributeValue;
183
+ for (var i = 0, length = attributes.length; i < length; i++) {
184
+ attribute = attributes[i];
185
+ attributeName = attribute.name;
186
+ attributeValue = attribute.value;
187
+
188
+ try { attributeValue = JSON.parse(attributeValue); } catch (ex) {}
189
+
190
+ if (attributeName.indexOf('data-') === 0) data[camelCase(attributeName.substring(5))] = attributeValue;
191
+ }
192
+
193
+ data.reuseIdentifier = data.reuseIdentifier || 'pp-table-view-cell-default';
194
+
195
+ dataSet.push(data);
196
+ });
197
+
198
+ $element.html(null);
199
+ self.setDataSource(new Pushpop.TableViewDataSource(dataSet));
200
+ })(self, $element);
201
+ };
202
+
203
+ /**
204
+ Event types for Pushpop.TableView.
205
+ */
206
+ Pushpop.TableView.EventType = {
207
+ DidSelectRowAtIndex: 'Pushpop:TableView:DidSelectRowAtIndex',
208
+ DidDeselectRowAtIndex: 'Pushpop:TableView:DidDeselectRowAtIndex',
209
+ AccessoryButtonTappedForRowWithIndex: 'Pushpop:TableView:AccessoryButtonTappedForRowWithIndex',
210
+ EditingAccessoryButtonTappedForRowWithIndex: 'Pushpop:TableView:EditingAccessoryButtonTappedForRowWithIndex',
211
+ DidReloadData: 'Pushpop:TableView:DidReloadData',
212
+ DidDrawRowsWithIndexes: 'Pushpop:TableView:DidDrawRowsWithIndexes',
213
+ DidChangeValueForItemInDataSource: 'Pushpop:TableView:DidChangeValueForItemInDataSource',
214
+ DidChangeDataSource: 'Pushpop:TableView:DidChangeDataSource'
215
+ };
216
+
217
+ Pushpop.TableView._reusableCellPrototypes = {};
218
+ Pushpop.TableView.getReusableCellPrototypes = function() {
219
+ var reusableCellPrototypes = Pushpop.TableView._reusableCellPrototypes, items = [];
220
+ for (var reusableCellPrototype in reusableCellPrototypes) items.push(reusableCellPrototypes[reusableCellPrototype]);
221
+ return items;
222
+ };
223
+
224
+ Pushpop.TableView.getReusableCellPrototypeWithIdentifier = function(reuseIdentifier) { return Pushpop.TableView._reusableCellPrototypes[reuseIdentifier]; };
225
+
226
+ Pushpop.TableView.registerReusableCellPrototype = function(cellPrototype) {
227
+ var reuseIdentifier;
228
+ if (!cellPrototype || !(reuseIdentifier = cellPrototype.getReuseIdentifier())) return;
229
+ Pushpop.TableView._reusableCellPrototypes[reuseIdentifier] = cellPrototype;
230
+ };
231
+
232
+ Pushpop.TableView.prototype = {
233
+ constructor: Pushpop.TableView,
234
+
235
+ element: null,
236
+ $element: null,
237
+
238
+ scrollView: null,
239
+
240
+ /**
241
+
242
+ */
243
+ getVisibleHeight: function() { return this.scrollView.getSize().height; },
244
+
245
+ /**
246
+
247
+ */
248
+ getTotalHeight: function() { return this.getDataSource().getNumberOfRows() * this.getRowHeight(); },
249
+
250
+ /**
251
+
252
+ */
253
+ getScrollPosition: function() { return this.scrollView.getScrollPosition().y; },
254
+
255
+ _minimumScrollPositionThreshold: -1,
256
+
257
+ /**
258
+
259
+ */
260
+ getMinimumScrollPositionThreshold: function() { return this._minimumScrollPositionThreshold; },
261
+
262
+ _maximumScrollPositionThreshold: -1,
263
+
264
+ /**
265
+
266
+ */
267
+ getMaximumScrollPositionThreshold: function() { return this._maximumScrollPositionThreshold; },
268
+
269
+ /**
270
+
271
+ */
272
+ getMinimumVisibleRowIndex: function() { return Math.min(Math.floor(this.getScrollPosition() / this.getRowHeight()), this.getDataSource().getNumberOfRows() - 1); },
273
+
274
+ /**
275
+
276
+ */
277
+ getMaximumVisibleRowIndex: function() { return Math.min(this.getMinimumVisibleRowIndex() + this.getNumberOfVisibleRows(), this.getDataSource().getNumberOfRows()) - 1; },
278
+
279
+ /**
280
+
281
+ */
282
+ getNumberOfVisibleRows: function() { return Math.min(Math.ceil(this.getVisibleHeight() / this.getRowHeight()), this.getDataSource().getNumberOfRows()); },
283
+
284
+ _renderedCells: null, // []
285
+
286
+ /**
287
+
288
+ */
289
+ getRenderedCells: function() { return this._renderedCells; },
290
+
291
+ /**
292
+
293
+ */
294
+ getRenderedCellAtIndex: function(index) {
295
+ var renderedCells = this.getRenderedCells();
296
+ for (var i = 0, length = renderedCells.length, renderedCell; i < length; i++) if ((renderedCell = renderedCells[i]).getIndex() === index) return renderedCell;
297
+ return null;
298
+ },
299
+
300
+ _minimumRenderedRowIndex: -1,
301
+
302
+ /**
303
+
304
+ */
305
+ getMinimumRenderedRowIndex: function() { return this._minimumRenderedRowIndex; },
306
+
307
+ _maximumRenderedRowIndex: -1,
308
+
309
+ /**
310
+
311
+ */
312
+ getMaximumRenderedRowIndex: function() { return this._maximumRenderedRowIndex; },
313
+
314
+ _reusableCells: null, // {}
315
+
316
+ /**
317
+
318
+ */
319
+ getReusableCells: function() { return this._reusableCells; },
320
+
321
+ /**
322
+
323
+ */
324
+ getReusableCellsWithIdentifier: function(reuseIdentifier) {
325
+ var reusableCells = this.getReusableCells();
326
+ return reusableCells[reuseIdentifier] || (reusableCells[reuseIdentifier] = []);
327
+ },
328
+
329
+ /**
330
+ Returns a new or recycled TableViewCell with the specified reuse identifier.
331
+ @description This method will first attempt to reuse a recycled TableViewCell
332
+ with the specified reuse identifier. If no recycled TableViewCells with that
333
+ reuse identifier are available, a new one will be instantiated and returned.
334
+ The TableViewCell that is returned is always added to the |renderedCells| array.
335
+ @type Pushpop.TableViewCell
336
+ */
337
+ dequeueReusableCellWithIdentifier: function(reuseIdentifier) {
338
+ var renderedCells = this.getRenderedCells();
339
+ var reusableCells = this.getReusableCellsWithIdentifier(reuseIdentifier);
340
+
341
+ var cell = null, cellPrototype = null;
342
+
343
+ if (reusableCells.length > 0) {
344
+ cell = reusableCells.pop();
345
+ } else {
346
+ cellPrototype = Pushpop.TableView.getReusableCellPrototypeWithIdentifier(reuseIdentifier);
347
+ cell = (cellPrototype) ? new cellPrototype.constructor(reuseIdentifier) : new Pushpop.TableViewCell(reuseIdentifier);
348
+ }
349
+
350
+ renderedCells.push(cell);
351
+ cell.tableView = this;
352
+
353
+ return cell;
354
+ },
355
+
356
+ _drawing: false,
357
+
358
+ getDrawing: function() { return this._drawing; },
359
+
360
+ /**
361
+
362
+ */
363
+ draw: function() {
364
+ if (this._drawing) return;
365
+ this._drawing = true;
366
+
367
+ var dataSource = this.getDataSource();
368
+
369
+ var minimumVisibleRowIndex = this.getMinimumVisibleRowIndex();
370
+ var maximumVisibleRowIndex = this.getMaximumVisibleRowIndex();
371
+
372
+ var numberOfRows = dataSource.getNumberOfRows();
373
+ var numberOfVisibleRows = this.getNumberOfVisibleRows();
374
+ var numberOfLeadingRows = Math.min(numberOfVisibleRows, minimumVisibleRowIndex);
375
+ var numberOfTrailingRows = Math.min(numberOfVisibleRows, numberOfRows - maximumVisibleRowIndex - 1);
376
+
377
+ var lastMinimumRenderedRowIndex = this.getMinimumRenderedRowIndex();
378
+ var lastMaximumRenderedRowIndex = this.getMaximumRenderedRowIndex();
379
+
380
+ var minimumRenderedRowIndex = this._minimumRenderedRowIndex = minimumVisibleRowIndex - numberOfLeadingRows;
381
+ var maximumRenderedRowIndex = this._maximumRenderedRowIndex = maximumVisibleRowIndex + numberOfTrailingRows;
382
+
383
+ var lastMinimumScrollPositionThreshold = this._minimumScrollPositionThreshold;
384
+ var lastMaximumScrollPositionThreshold = this._maximumScrollPositionThreshold;
385
+
386
+ var scrollPosition = this.getScrollPosition();
387
+
388
+ var visibleHeight = this.getVisibleHeight();
389
+ var totalHeight = this.getTotalHeight();
390
+ var rowHeight = this.getRowHeight();
391
+
392
+ var scrollView = this.scrollView;
393
+ var lastScrollViewMargin = scrollView.getMargin();
394
+ var scrollViewMarginTop = minimumRenderedRowIndex * rowHeight;
395
+ var scrollViewMarginBottom = (numberOfRows - maximumRenderedRowIndex - 1) * rowHeight;
396
+
397
+ scrollView.setMargin(scrollViewMarginTop, lastScrollViewMargin.right, scrollViewMarginBottom, lastScrollViewMargin.left);
398
+
399
+ var $element = this.$element;
400
+ var minimumRowIndexToRender, maximumRowIndexToRender;
401
+ var minimumRowIndexToRemove, maximumRowIndexToRemove;
402
+ var i, cellToAdd, renderedCellToRemove;
403
+
404
+ // Render higher-indexed rows.
405
+ if (minimumRenderedRowIndex > lastMinimumRenderedRowIndex || maximumRenderedRowIndex > lastMaximumRenderedRowIndex) {
406
+ minimumRowIndexToRender = lastMaximumRenderedRowIndex + 1;
407
+ maximumRowIndexToRender = maximumRenderedRowIndex;
408
+ minimumRowIndexToRemove = lastMinimumRenderedRowIndex;
409
+ maximumRowIndexToRemove = minimumRenderedRowIndex - 1;
410
+
411
+ for (i = minimumRowIndexToRender; i <= maximumRowIndexToRender; i++) {
412
+ cellToAdd = dataSource.getCellForRowAtIndex(this, i);
413
+ cellToAdd.setSelected(this.isRowSelectedAtIndex(i));
414
+ $element.append(cellToAdd.$element);
415
+ }
416
+ }
417
+
418
+ // Render lower-indexed rows.
419
+ else if (minimumRenderedRowIndex < lastMinimumRenderedRowIndex || maximumRenderedRowIndex < lastMaximumRenderedRowIndex) {
420
+ minimumRowIndexToRender = minimumRenderedRowIndex;
421
+ maximumRowIndexToRender = lastMinimumRenderedRowIndex - 1;
422
+ minimumRowIndexToRemove = maximumRenderedRowIndex + 1;
423
+ maximumRowIndexToRemove = lastMaximumRenderedRowIndex;
424
+
425
+ for (i = maximumRowIndexToRender; i >= minimumRowIndexToRender; i--) {
426
+ cellToAdd = dataSource.getCellForRowAtIndex(this, i);
427
+ cellToAdd.setSelected(this.isRowSelectedAtIndex(i));
428
+ $element.prepend(cellToAdd.$element);
429
+ }
430
+ }
431
+
432
+ for (i = minimumRowIndexToRemove; i <= maximumRowIndexToRemove; i++) if ((renderedCellToRemove = this.getRenderedCellAtIndex(i))) renderedCellToRemove.prepareForReuse();
433
+
434
+ var minimumScrollPositionThreshold = this._minimumScrollPositionThreshold = (minimumRenderedRowIndex > 0) ? scrollPosition - visibleHeight : -1;
435
+ var maximumScrollPositionThreshold = this._maximumScrollPositionThreshold = (maximumRenderedRowIndex < numberOfRows - 1) ? scrollPosition + visibleHeight : -1;
436
+
437
+ // console.log('Minimum Visible Index: ' + minimumVisibleRowIndex + ', Maximum Visible Index: ' + maximumVisibleRowIndex);
438
+ // console.log('Visible Rows: ' + numberOfVisibleRows + ', Leading Rows: ' + numberOfLeadingRows + ', Trailing Rows: ' + numberOfTrailingRows);
439
+ // console.log(lastMinimumRenderedRowIndex + ' -> ' + minimumRenderedRowIndex + ', ' + lastMaximumRenderedRowIndex + ' -> ' + maximumRenderedRowIndex);
440
+ // console.log('Row Indexes To Render: ' + minimumRowIndexToRender + ' - ' + maximumRowIndexToRender);
441
+ // console.log('Row Indexes To Remove: ' + minimumRowIndexToRemove + ' - ' + maximumRowIndexToRemove);
442
+ // console.log('================================================================');
443
+
444
+ this._drawing = false;
445
+
446
+ this.$trigger($.Event(Pushpop.TableView.EventType.DidDrawRowsWithIndexes, {
447
+ tableView: this,
448
+ dataSource: dataSource,
449
+ minimumRowIndex: minimumRenderedRowIndex,
450
+ maximumRowIndex: maximumRenderedRowIndex
451
+ }));
452
+ },
453
+
454
+ /**
455
+
456
+ */
457
+ reloadData: function() {
458
+ var scrollView = this.scrollView;
459
+ var scrollPosition = $.extend({}, scrollView.getScrollPosition());
460
+ var renderedCells = this.getRenderedCells();
461
+ var renderedCellsToReuse = [];
462
+
463
+ var i, length;
464
+ for (i = 0, length = renderedCells.length; i < length; i++) renderedCellsToReuse.push(renderedCells[i]);
465
+ for (i = 0, length = renderedCellsToReuse.length; i < length; i++) renderedCellsToReuse[i].prepareForReuse();
466
+
467
+ this._minimumScrollPositionThreshold = -1;
468
+ this._maximumScrollPositionThreshold = -1;
469
+ this._minimumRenderedRowIndex = -1;
470
+ this._maximumRenderedRowIndex = -1;
471
+
472
+ this.draw();
473
+
474
+ scrollView.setScrollPosition(scrollPosition.x, scrollPosition.y);
475
+ },
476
+
477
+ _$loadingMessageElement: null,
478
+
479
+ _loadingMessageHtml: 'Loading...',
480
+
481
+ /**
482
+
483
+ */
484
+ getLoadingMessageHtml: function() { return this._loadingMessageHtml; },
485
+
486
+ /**
487
+
488
+ */
489
+ setLoadingMessageHtml: function(loadingMessageHtml) { this._$loadingMessageElement.html(this._loadingMessageHtml = loadingMessageHtml).append(this._$loadingSpinnerElement); },
490
+
491
+ _$loadingSpinnerElement: null,
492
+
493
+ _loadingSpinner: null,
494
+
495
+ /**
496
+
497
+ */
498
+ getLoadingSpinner: function() { return this._loadingSpinner; },
499
+
500
+ _loadingMessageHidden: true,
501
+
502
+ /**
503
+
504
+ */
505
+ getLoadingMessageHidden: function() { return this._loadingMessageHidden; },
506
+
507
+ /**
508
+
509
+ */
510
+ setLoadingMessageHidden: function(loadingMessageHidden) {
511
+ if ((this._loadingMessageHidden = loadingMessageHidden)) {
512
+ this._$loadingMessageElement.addClass('pp-hidden');
513
+ } else {
514
+ this._$loadingMessageElement.removeClass('pp-hidden');
515
+ }
516
+ },
517
+
518
+ _dataSource: null,
519
+
520
+ /**
521
+ Returns the TableViewDataSource for this TableView.
522
+ @type Pushpop.TableViewDataSource
523
+ */
524
+ getDataSource: function() { return this._dataSource; },
525
+
526
+ /**
527
+ Sets a TableViewDataSource for this TableView and reloads the data.
528
+ @param {Pushpop.TableViewDataSource} dataSource The TableViewDataSource to bind
529
+ to this TableView.
530
+ */
531
+ setDataSource: function(dataSource) {
532
+ var previousDataSource = this.getDataSource();
533
+
534
+ this._dataSource = dataSource;
535
+ dataSource.setTableView(this);
536
+
537
+ this.$trigger($.Event(Pushpop.TableView.EventType.DidChangeDataSource, {
538
+ tableView: this,
539
+ dataSource: dataSource,
540
+ previousDataSource: previousDataSource
541
+ }));
542
+
543
+ this.reloadData();
544
+ },
545
+
546
+ _searchBar: null,
547
+
548
+ /**
549
+ Returns the TableViewSearchBar for this TableView if it contains one.
550
+ @type Pushpop.TableViewSearchBar
551
+ */
552
+ getSearchBar: function() { return this._searchBar; },
553
+
554
+ /**
555
+ Sets a TableViewSearchBar for this TableView.
556
+ @param {Pushpop.TableViewSearchBar} searchBar The TableViewSearchBar to attach
557
+ to this TableView.
558
+ */
559
+ setSearchBar: function(searchBar) { this._searchBar = searchBar; },
560
+
561
+ _rowHeight: 44,
562
+
563
+ /**
564
+
565
+ */
566
+ getRowHeight: function() { return this._rowHeight; },
567
+
568
+ /**
569
+
570
+ */
571
+ setRowHeight: function(rowHeight) { this._rowHeight = rowHeight; },
572
+
573
+ _editing: false,
574
+
575
+ /**
576
+ Determines if this TableView is in editing mode.
577
+ @type Boolean
578
+ */
579
+ getEditing: function() { return this._editing; },
580
+
581
+ /**
582
+ Used for setting the table view in or out of editing mode.
583
+ @param {Boolean} editing A flag indicating if the TableView should be in editing mode.
584
+ @param {Boolean} [animated] An optional flag indicating if the transition in or out of
585
+ editing mode should be animated (default: true).
586
+ */
587
+ setEditing: function(editing, animated) {
588
+ if ((this._editing = editing)) {
589
+ this.$element.addClass('pp-table-view-editing');
590
+ } else {
591
+ this.$element.removeClass('pp-table-view-editing');
592
+ }
593
+ },
594
+
595
+ _selectionTimeoutDuration: 250,
596
+
597
+ /**
598
+
599
+ */
600
+ getSelectionTimeoutDuration: function() { return this._selectionTimeoutDuration; },
601
+
602
+ /**
603
+
604
+ */
605
+ setSelectionTimeoutDuration: function(selectionTimeoutDuration) { this._selectionTimeoutDuration = selectionTimeoutDuration; },
606
+
607
+ _selectedRowIndexes: null, // []
608
+
609
+ /**
610
+ Returns the index in for the first selected row.
611
+ @description NOTE: This is an index of a row in the data source, NOT an index
612
+ of a cell in the DOM. If no rows are selected, this method will return -1.
613
+ @type Number
614
+ */
615
+ getIndexForSelectedRow: function() {
616
+ var selectedRowIndexes = this._selectedRowIndexes;
617
+ return (selectedRowIndexes && selectedRowIndexes.length > 0) ? selectedRowIndexes[0] : -1;
618
+ },
619
+
620
+ /**
621
+ Returns the indexes in for the selected rows.
622
+ @description NOTE: The array contains indexes of rows in the data source, NOT
623
+ indexes of cells in the DOM. If no rows are selected, this array will be empty.
624
+ @type Array
625
+ */
626
+ getIndexesForSelectedRows: function() {
627
+ return this._selectedRowIndexes;
628
+ },
629
+
630
+ /**
631
+ Determines if the specified index is a selected row.
632
+ @description NOTE: This is an index of a row in the data source, NOT an index
633
+ of a cell in the DOM.
634
+ @type Boolean
635
+ */
636
+ isRowSelectedAtIndex: function(index) {
637
+ var selectedRowIndexes = this._selectedRowIndexes;
638
+ for (var i = 0, length = selectedRowIndexes.length; i < length; i++) if (selectedRowIndexes[i] === index) return true;
639
+ return false;
640
+ },
641
+
642
+ /**
643
+ Selects the row at the specified index and triggers the DidSelectRowAtIndex event on
644
+ this and all parent table view elements.
645
+ @description NOTE: If the row contains a child data source, this method will automatically
646
+ push a dynamic table view using the child data source. The DidSelectRowAtIndex event contains
647
+ a flag |hasChildDataSource| to indicate whether or not a new dynamic table view was pushed
648
+ prior to the event.
649
+ @param {Number} index The index of a row in the data source to select.
650
+ @param {Boolean} [animated] A flag indicating if the selection should be animated
651
+ if the row is currently visible.
652
+ */
653
+ selectRowAtIndex: function(index, animated) {
654
+ var dataSource = this.getDataSource();
655
+ var shouldSelectRowAtIndex = dataSource.shouldSelectRowAtIndex(index);
656
+ if (!shouldSelectRowAtIndex) return;
657
+
658
+ this.deselectAllRows();
659
+
660
+ var $element = this.$element;
661
+ this._selectedRowIndexes.push(index);
662
+
663
+ var tableViewCell, $cells = this.$element.children();
664
+ for (var i = 0, length = $cells.length; i < length; i++) {
665
+ tableViewCell = $cells[i].tableViewCell;
666
+ if (tableViewCell.getIndex() === index) {
667
+ tableViewCell.setSelected(true);
668
+ tableViewCell.forceReflow();
669
+ break;
670
+ }
671
+ }
672
+
673
+ var self = this;
674
+
675
+ // If this row contains a child data source, automatically push a new dynamic table view with it.
676
+ if (dataSource.rowHasChildDataSourceAtIndex(index)) {
677
+ var childDataSource = dataSource.getChildDataSourceForRowAtIndex(index);
678
+ var viewStack = this.getViewStack();
679
+
680
+ if (childDataSource && viewStack) {
681
+ viewStack.pushNewTableView(function(childTableView) {
682
+ if (self.getSearchBar()) childTableView.setSearchBar(new Pushpop.TableViewSearchBar(childTableView));
683
+ childTableView.setDataSource(childDataSource);
684
+ childTableView.setParentTableView(self);
685
+ });
686
+
687
+ // Trigger the DidSelectRowAtIndex event on this and all parent table view elements.
688
+ window.setTimeout(function() {
689
+ self.triggerEventOnParentTableViews($.Event(Pushpop.TableView.EventType.DidSelectRowAtIndex, {
690
+ tableView: self,
691
+ index: index,
692
+ item: dataSource.getFilteredItemAtIndex(index),
693
+ hasChildDataSource: true
694
+ }), true);
695
+ }, 1);
696
+
697
+ return;
698
+ }
699
+ }
700
+
701
+ // Trigger the DidSelectRowAtIndex event on this and all parent table view elements.
702
+ window.setTimeout(function() {
703
+ self.triggerEventOnParentTableViews($.Event(Pushpop.TableView.EventType.DidSelectRowAtIndex, {
704
+ tableView: self,
705
+ index: index,
706
+ item: dataSource.getFilteredItemAtIndex(index),
707
+ hasChildDataSource: false
708
+ }), true);
709
+ }, 1);
710
+ },
711
+
712
+ /**
713
+ De-selects the row at the specified index and optionally animates the de-selection
714
+ if the row is currently visible.
715
+ @description NOTE: This method will not modify any other existing selections.
716
+ @param {Number} index The index of a row in the data source to de-select.
717
+ @param {Boolean} [animated] A flag indicating if the de-selection should be
718
+ animated if the row is currently visible.
719
+ */
720
+ deselectRowAtIndex: function(index, animated) {
721
+ var $element = this.$element;
722
+ var selectedRowIndexes = this._selectedRowIndexes;
723
+ var i, length;
724
+ for (i = 0, length = selectedRowIndexes.length; i < length; i++) {
725
+ if (selectedRowIndexes[i] === index) {
726
+ selectedRowIndexes.splice(i, 1);
727
+ break;
728
+ }
729
+ }
730
+
731
+ var tableViewCell, $selectedCells = $element.children('.pp-table-view-selected-state');
732
+ for (i = 0, length = $selectedCells.length; i < length; i++) {
733
+ tableViewCell = $selectedCells[i].tableViewCell;
734
+ if (tableViewCell.getIndex() === index) {
735
+ tableViewCell.setSelected(false);
736
+ break;
737
+ }
738
+ }
739
+
740
+ $element.trigger($.Event(Pushpop.TableView.EventType.DidDeselectRowAtIndex, {
741
+ tableView: this,
742
+ index: index
743
+ }));
744
+ },
745
+
746
+ /**
747
+ De-selects all rows in the table.
748
+ */
749
+ deselectAllRows: function() {
750
+ var $element = this.$element;
751
+ var selectedRowIndexes = this._selectedRowIndexes;
752
+ for (var i = 0, length = selectedRowIndexes.length; i < length; i++) {
753
+ $element.trigger($.Event(Pushpop.TableView.EventType.DidDeselectRowAtIndex, {
754
+ tableView: this,
755
+ index: selectedRowIndexes[i]
756
+ }));
757
+ }
758
+
759
+ selectedRowIndexes.length = 0;
760
+
761
+ $element.children('.pp-table-view-selected-state').each(function(index, element) {
762
+ element.tableViewCell.setSelected(false);
763
+ });
764
+ },
765
+
766
+ _parentTableView: null,
767
+
768
+ /**
769
+ Returns the parent table view if this table view has one.
770
+ @type Pushpop.TableView
771
+ */
772
+ getParentTableView: function() { return this._parentTableView; },
773
+
774
+ /**
775
+ Sets the parent table view for this table view.
776
+ @param {Pushpop.TableView} parentTableView The table view to set as the parent for this table view.
777
+ @description NOTE: To remove this table view from its parent, call this method
778
+ and pass in a |null| value.
779
+ */
780
+ setParentTableView: function(parentTableView) { this._parentTableView = parentTableView; },
781
+
782
+ /**
783
+ Traverses the parent table views up the chain until it encounters a table view
784
+ with no parent then returns an array of Pushpop.TableView objects.
785
+ @type Array
786
+ */
787
+ getParentTableViews: function() {
788
+ var parentTableViews = [];
789
+ var currentParentTableView = this.getParentTableView();
790
+
791
+ while (currentParentTableView) {
792
+ parentTableViews.push(currentParentTableView);
793
+ currentParentTableView = currentParentTableView.getParentTableView();
794
+ }
795
+
796
+ return parentTableViews;
797
+ },
798
+
799
+ /**
800
+ Triggers the specified event on the parent table view elements and optionally
801
+ also on this own table view's element.
802
+ @param {$.Event|String} evt The event to be triggered on the table view element(s).
803
+ @param {Boolean} [includeSelf] A flag indicating whether or not the event should
804
+ also be triggered on this table view's element.
805
+ */
806
+ triggerEventOnParentTableViews: function(evt, includeSelf) {
807
+ var parentTableViews = this.getParentTableViews();
808
+ for (var i = 0, length = parentTableViews.length; i < length; i++) parentTableViews[i].$trigger(evt);
809
+ if (includeSelf) this.$trigger(evt);
810
+ },
811
+
812
+ /**
813
+ Returns the view that contains this table view.
814
+ @description NOTE: If this table view is not contained within a view, this method will return null.
815
+ @type Pushpop.View
816
+ */
817
+ getView: function() {
818
+ var parents = this.$element.parents();
819
+ var view;
820
+ for (var i = 0, length = parents.length; i < length; i++) if ((view = parents[i].view)) return view;
821
+ return null;
822
+ },
823
+
824
+ /**
825
+ Returns the view stack that contains this table view.
826
+ @description NOTE: If this table view is not contained within a view stack, this method will return null.
827
+ @type Pushpop.ViewStack
828
+ */
829
+ getViewStack: function() {
830
+ var parents = this.$element.parents();
831
+ var viewStack;
832
+ for (var i = 0, length = parents.length; i < length; i++) if ((viewStack = parents[i].viewStack)) return viewStack;
833
+ return null;
834
+ },
835
+
836
+ /**
837
+ Convenience accessor for jQuery's .bind() method.
838
+ */
839
+ $bind: function() { this.$element.bind.apply(this.$element, arguments); },
840
+
841
+ /**
842
+ Convenience accessor for jQuery's .unbind() method.
843
+ */
844
+ $unbind: function() { this.$element.unbind.apply(this.$element, arguments); },
845
+
846
+ /**
847
+ Convenience accessor for jQuery's .delegate() method.
848
+ */
849
+ $delegate: function() { this.$element.delegate.apply(this.$element, arguments); },
850
+
851
+ /**
852
+ Convenience accessor for jQuery's .undelegate() method.
853
+ */
854
+ $undelegate: function() { this.$element.undelegate.apply(this.$element, arguments); },
855
+
856
+ /**
857
+ Convenience accessor for jQuery's .trigger() method.
858
+ */
859
+ $trigger: function() { this.$element.trigger.apply(this.$element, arguments); }
860
+ };
861
+
862
+ /**
863
+ Creates a new data source for a TableView.
864
+ @param {Array} [dataSet] An optional array of data to initialize a default data source.
865
+ @param {Array} [dataSet.id] The unique identifier for a specific record.
866
+ @param {Array} [dataSet.value] The (sometimes) hidden value for a specific record.
867
+ @param {Array} [dataSet.title] The title to be displayed in a TableViewCell for a specific record.
868
+ @param {String} [defaultReuseIdentifier] The optional reuse identifier to be used for rows that do
869
+ not specify a specific reuse identifier.
870
+ @constructor
871
+ */
872
+ Pushpop.TableViewDataSource = function TableViewDataSource(dataSet, defaultReuseIdentifier) {
873
+ this.setDataSet(dataSet || []);
874
+ this.setDefaultReuseIdentifier(defaultReuseIdentifier || this.getDefaultReuseIdentifier());
875
+ };
876
+
877
+ Pushpop.TableViewDataSource.prototype = {
878
+ constructor: Pushpop.TableViewDataSource,
879
+
880
+ /**
881
+ Returns the number of rows provided by this data source.
882
+ @description NOTE: This is the default implementation and should be overridden for data
883
+ sources that are not driven directly from an in-memory data set.
884
+ @type Number
885
+ */
886
+ getNumberOfRows: function() { return this.getNumberOfFilteredItems(); },
887
+
888
+ /**
889
+ Returns a TableViewCell for the specified index.
890
+ @description NOTE: This is the default implementation and should be overridden for data
891
+ sources that are not driven directly from an in-memory data set.
892
+ @param {Pushpop.TableView} tableView The TableView the TableViewCell should be returned for.
893
+ @param {Number} index The index of the data to be used when populating the TableViewCell.
894
+ @type Pushpop.TableViewCell
895
+ */
896
+ getCellForRowAtIndex: function(tableView, index) {
897
+ var item = this.getFilteredItemAtIndex(index);
898
+ var reuseIdentifier = item.reuseIdentifier || this.getDefaultReuseIdentifier();
899
+ var accessoryType = item.accessoryType || this.getDefaultAccessoryType();
900
+ var editingAccessoryType = item.editingAccessoryType || this.getDefaultEditingAccessoryType();
901
+ var cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier);
902
+
903
+ cell.setIndex(index);
904
+ cell.setAccessoryType(accessoryType);
905
+ cell.setEditingAccessoryType(editingAccessoryType);
906
+ cell.setData(item);
907
+
908
+ return cell;
909
+ },
910
+
911
+ /**
912
+ Returns an array containing the key/value pairs for all "values" contained within the data
913
+ source. This is typically used for retrieving form fields stored within a table view and
914
+ behaves similarly to jQuery's .serializeArray() function.
915
+ @param {String} [keyFieldName] The name of the field in the data source containing the
916
+ values' keys. If not specified, the default value is 'name'.
917
+ @param {String} [valueFieldName] The name of the field in the data source containing the
918
+ values' values. If not specified, the default value is 'value.
919
+ @type Array
920
+ */
921
+ getValuesArray: function(keyFieldName, valueFieldName) {
922
+ keyFieldName = keyFieldName || 'name';
923
+ valueFieldName = valueFieldName || 'value';
924
+
925
+ var numberOfItems = this.getNumberOfItems();
926
+ var valuesArray = [];
927
+ var item, name, value;
928
+
929
+ for (var i = 0; i < numberOfItems; i++) {
930
+ item = this.getItemAtIndex(i);
931
+ name = item[keyFieldName];
932
+ value = item[valueFieldName];
933
+
934
+ if (value !== undefined) valuesArray.push({
935
+ name: item[keyFieldName] || keyFieldName,
936
+ value: item[valueFieldName]
937
+ });
938
+ }
939
+
940
+ return valuesArray;
941
+ },
942
+
943
+ /**
944
+ Returns an object containing the data for all "values" contained within the data source.
945
+ This is typically used for retrieving form fields stored within a table view.
946
+ @description NOTE: If a field name occurs more than once, its values will be put into an
947
+ array.
948
+ @param {String} [keyFieldName] The name of the field in the data source containing the
949
+ values' keys. If not specified, the default value is 'name'.
950
+ @param {String} [valueFieldName] The name of the field in the data source containing the
951
+ values' values. If not specified, the default value is 'value.
952
+ @type Object
953
+ */
954
+ getValuesObject: function(keyFieldName, valueFieldName) {
955
+ var valuesArray = this.getValuesArray(keyFieldName, valueFieldName);
956
+ var valuesObject = {};
957
+
958
+ var value;
959
+ for (var i = 0, length = valuesArray.length; i < length; i++) {
960
+ value = valuesArray[i];
961
+
962
+ if (valuesObject[value.name] !== undefined) {
963
+ if (!valuesObject[value.name].push) valuesObject[value.name] = [valuesObject[value.name]];
964
+ valuesObject[value.name].push(value.value);
965
+ } else {
966
+ valuesObject[value.name] = value.value;
967
+ }
968
+ }
969
+
970
+ return valuesObject;
971
+ },
972
+
973
+ /**
974
+ Sets the "values" for the items contained within the data source from an object containing
975
+ key/value pairs.
976
+ @param {Object} object The object to map key/value pairs from.
977
+ @param {String} [keyFieldName] The name of the field in the data source containing the
978
+ values' keys. If not specified, the default value is 'name'.
979
+ @param {String} [valueFieldName] The name of the field in the data source containing the
980
+ values' values. If not specified, the default value is 'value.
981
+ */
982
+ setValuesFromObject: function(object, keyFieldName, valueFieldName) {
983
+ if (!object) return;
984
+
985
+ keyFieldName = keyFieldName || 'name';
986
+ valueFieldName = valueFieldName || 'value';
987
+
988
+ var numberOfItems = this.getNumberOfItems();
989
+ var i, item;
990
+
991
+ for (var key in object) {
992
+ for (i = 0; i < numberOfItems; i++) {
993
+ item = this.getItemAtIndex(i);
994
+
995
+ if (item[keyFieldName] === key) {
996
+ item[valueFieldName] = object[key];
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+
1002
+ var tableView = this.getTableView();
1003
+ if (tableView) tableView.reloadData();
1004
+ },
1005
+
1006
+ clearValues: function(valueFieldName, defaultValueFieldName) {
1007
+ valueFieldName = valueFieldName || 'value';
1008
+ defaultValueFieldName = defaultValueFieldName || 'defaultValue';
1009
+
1010
+ var numberOfItems = this.getNumberOfItems();
1011
+ var item, value, defaultValue;
1012
+
1013
+ for (var i = 0; i < numberOfItems; i++) {
1014
+ item = this.getItemAtIndex(i);
1015
+ value = item[valueFieldName];
1016
+ defaultValue = item[defaultValueFieldName] || null;
1017
+
1018
+ if (value !== undefined || defaultValue) item[valueFieldName] = defaultValue;
1019
+ }
1020
+
1021
+ var tableView = this.getTableView();
1022
+ if (tableView) tableView.reloadData();
1023
+ },
1024
+
1025
+ /**
1026
+ Determines if the table should be reloaded following a change in the search string.
1027
+ @description The default implementation assumes that the data set is fully loaded into
1028
+ memory and executes the current filter function against each item in the data set. If the
1029
+ filtered data set has changed since the last reload, it will return |true| which will force
1030
+ the associated TableViewSearchBar to reload the data for the TableView. In a custom data
1031
+ source that does not use an in-memory data set (e.g.: WebSQL or HTML5 LocalStorage), it is
1032
+ recommended to override this method to perform any necessary queries asynchronously and
1033
+ immediately return |false|. Once the asynchronous queries have completed, the application
1034
+ should then manually call .reloadData() on the TableView to force an update (See the WebSQL
1035
+ demo for an example on this implementation).
1036
+ @param {String} searchString The search string to be used for matching items in the data set.
1037
+ @param {Boolean} [isCaseSensitive] An optional boolean flag for forcing a case-sensitive search.
1038
+ @type Boolean
1039
+ */
1040
+ shouldReloadTableForSearchString: function(searchString, isCaseSensitive) {
1041
+ var dataSet = this.getDataSet();
1042
+ if (!dataSet) return false;
1043
+
1044
+ var filterFunction = this.getFilterFunction();
1045
+ if (!filterFunction || typeof filterFunction !== 'function' || !searchString) {
1046
+ this._lastSearchString = null;
1047
+
1048
+ if (this._filteredDataSet !== dataSet) {
1049
+ this._filteredDataSet = dataSet;
1050
+ return true;
1051
+ }
1052
+
1053
+ return false;
1054
+ }
1055
+
1056
+ var filteredDataSet = [];
1057
+ var regExp = new RegExp(searchString + '+', (!isCaseSensitive ? 'i' : '') + 'm');
1058
+ var item, i, length;
1059
+
1060
+ // The search string is a continuation of the last search string (e.g.: 'ab' -> 'abc').
1061
+ if (searchString.indexOf(this._lastSearchString) === 0) {
1062
+
1063
+ // Search the previous filtered data set instead of the whole data set.
1064
+ var lastFilteredDataSet = this._filteredDataSet;
1065
+ for (i = 0, length = lastFilteredDataSet.length; i < length; i++) if (filterFunction(regExp, item = lastFilteredDataSet[i])) filteredDataSet.push(item);
1066
+ }
1067
+
1068
+ // The search string is NOT a contination of the last search string (e.g.: 'abc' -> 'ab').
1069
+ else {
1070
+
1071
+ // Search the whole data set.
1072
+ for (i = 0, length = dataSet.length; i < length; i++) if (filterFunction(regExp, item = dataSet[i])) filteredDataSet.push(item);
1073
+ }
1074
+
1075
+ this._filteredDataSet = filteredDataSet;
1076
+ this._lastSearchString = searchString;
1077
+ return true;
1078
+ },
1079
+
1080
+ _lastSearchString: null,
1081
+
1082
+ /**
1083
+ Returns a flag indicating whether or not the row at the specified index should be able
1084
+ to be selected.
1085
+ @description NOTE: This is the default implementation and should be overridden if certain
1086
+ rows should not be able to be selected.
1087
+ @param {Number} index The index of the row to determine whether or not it should be selectable.
1088
+ @type Boolean
1089
+ */
1090
+ shouldSelectRowAtIndex: function(index) { return true; },
1091
+
1092
+ /**
1093
+ Returns a flag indicating whether or not the row at the specified index contains a child
1094
+ data source.
1095
+ @description NOTE: This is the default implementation and should be overridden for data
1096
+ sources that are not driven directly from an in-memory data set. In the default implementation,
1097
+ the |dataSourceKey| that is set using the setDataSourceKey() method is used to determine if an
1098
+ array of objects exists for that key on the item at the specified index.
1099
+ @param {Number} index The index of the row to determine whether or not it contains a child data source.
1100
+ @type Boolean
1101
+ */
1102
+ rowHasChildDataSourceAtIndex: function(index) {
1103
+ var key = this.getChildDataSourceKey();
1104
+ if (!key) return;
1105
+
1106
+ var item = this.getFilteredItemAtIndex(index);
1107
+ return !!(item && item[key] && item[key] instanceof Array);
1108
+ },
1109
+
1110
+ /**
1111
+ Creates and returns a new data source for the row at the specified index if the item at
1112
+ that index contains a child data source as determined by the rowHadChildDataSourceAtIndex()
1113
+ method.
1114
+ @description NOTE: This is the default implementation and should be overridden for data
1115
+ sources that are not driven directly from an in-memory data set. In the default implementation,
1116
+ the |dataSourceKey| that is set using the setDataSourceKey() method is used to retrieve the
1117
+ array of objects for that key on the item at the specified index. The array of child objects are
1118
+ then used to create a new data source. The new data source is automatically given the same child
1119
+ data source key in order to continue chaining nested data n-levels deep.
1120
+ @param {Number} index The index of the row to retrieve a child data source for.
1121
+ @type Pushpop.TableViewDataSource
1122
+ */
1123
+ getChildDataSourceForRowAtIndex: function(index) {
1124
+ var key = this.getChildDataSourceKey();
1125
+ if (!key) return null;
1126
+
1127
+ var item = this.getFilteredItemAtIndex(index);
1128
+ var childDataSet = item[key];
1129
+ if (!childDataSet) return null;
1130
+
1131
+ var childDataSource = new Pushpop.TableViewDataSource(childDataSet, this.getDefaultReuseIdentifier());
1132
+ childDataSource.setChildDataSourceKey(key);
1133
+
1134
+ // Inherit properties from this parent data source for the new child data source.
1135
+ childDataSource.shouldSelectRowAtIndex = this.shouldSelectRowAtIndex;
1136
+ childDataSource.shouldReloadTableForSearchString = this.shouldReloadTableForSearchString;
1137
+
1138
+ childDataSource.setFilterFunction(this.getFilterFunction());
1139
+
1140
+ return childDataSource;
1141
+ },
1142
+
1143
+ _tableView: null,
1144
+
1145
+ /**
1146
+ Returns the TableView this data source is bound to.
1147
+ @type Pushpop.TableView
1148
+ */
1149
+ getTableView: function() { return this._tableView; },
1150
+
1151
+ /**
1152
+ Sets the TableView this data source should be bound to.
1153
+ @param {Pushpop.TableView} tableView The TableView to bind this data source to.
1154
+ */
1155
+ setTableView: function(tableView) {
1156
+ this._tableView = tableView;
1157
+
1158
+ var searchBar = tableView.getSearchBar();
1159
+ if (searchBar) searchBar.setSearchString(this._lastSearchString);
1160
+ },
1161
+
1162
+ _defaultReuseIdentifier: 'pp-table-view-cell-default',
1163
+
1164
+ /**
1165
+ Returns the default reuse identifier that this data source will use when a
1166
+ reuse identifier is not specified for a particular item.
1167
+ @type String
1168
+ */
1169
+ getDefaultReuseIdentifier: function() { return this._defaultReuseIdentifier; },
1170
+
1171
+ /**
1172
+ Sets the default reuse identifier that this data source will use when a
1173
+ reuse identifier is not specified for a particular item.
1174
+ @param {String} defaultReuseIdentifier The reuse identifier to set as default.
1175
+ */
1176
+ setDefaultReuseIdentifier: function(defaultReuseIdentifier) { this._defaultReuseIdentifier = defaultReuseIdentifier; },
1177
+
1178
+ _defaultAccessoryType: 'pp-table-view-cell-accessory-none',
1179
+
1180
+ /**
1181
+ Returns the default accessory type that this data source will use when an
1182
+ accessory type is not specified for a particular item.
1183
+ @description NOTE: The available accessory types are defined by the
1184
+ Pushpop.TableViewCell.AccessoryType singleton.
1185
+ @type String
1186
+ */
1187
+ getDefaultAccessoryType: function() { return this._defaultAccessoryType; },
1188
+
1189
+ /**
1190
+ Sets the default accessory type that this data source will use when an
1191
+ accessory type is not specified for a particular item.
1192
+ @description NOTE: The available accessory types are defined by the
1193
+ Pushpop.TableViewCell.AccessoryType singleton.
1194
+ @param {String} defaultAccessoryType The accessory type to set as default.
1195
+ */
1196
+ setDefaultAccessoryType: function(defaultAccessoryType) { this._defaultAccessoryType = defaultAccessoryType; },
1197
+
1198
+ _defaultEditingAccessoryType: 'pp-table-view-cell-editing-accessory-none',
1199
+
1200
+ /**
1201
+ Returns the default editing accessory type that this data source will use when an
1202
+ editing accessory type is not specified for a particular item.
1203
+ @description NOTE: The available editing accessory types are defined by the
1204
+ Pushpop.TableViewCell.EditingAccessoryType singleton.
1205
+ @type String
1206
+ */
1207
+ getDefaultEditingAccessoryType: function() { return this._defaultEditingAccessoryType; },
1208
+
1209
+ /**
1210
+ Sets the default editing accessory type that this data source will use when an
1211
+ editing accessory type is not specified for a particular item.
1212
+ @description NOTE: The available editing accessory types are defined by the
1213
+ Pushpop.TableViewCell.EditingAccessoryType singleton.
1214
+ @param {String} defaultEditingAccessoryType The editing accessory type to set as default.
1215
+ */
1216
+ setDefaultEditingAccessoryType: function(defaultEditingAccessoryType) { this._defaultEditingAccessoryType = defaultEditingAccessoryType; },
1217
+
1218
+ _dataSet: null,
1219
+
1220
+ /**
1221
+ Returns the in-memory data set this data source will provide to the table view.
1222
+ @description NOTE: This may not be utilized by a custom data source.
1223
+ @type Array
1224
+ */
1225
+ getDataSet: function() { return this._dataSet; },
1226
+
1227
+ /**
1228
+ Sets the in-memory data set this data source should provide to the table view.
1229
+ @description NOTE: This may not be utilized by a custom data source.
1230
+ @param {Array} dataSet The set of data that this data source should provide to the table view.
1231
+ */
1232
+ setDataSet: function(dataSet) {
1233
+ this._dataSet = dataSet;
1234
+ this.shouldReloadTableForSearchString('');
1235
+
1236
+ var tableView = this.getTableView();
1237
+ if (tableView) tableView.reloadData();
1238
+ },
1239
+
1240
+ _childDataSourceKey: null,
1241
+
1242
+ /**
1243
+ Returns a string that specifies a key on this data source's objects that may contain
1244
+ an array of data for a child data source.
1245
+ @type String
1246
+ */
1247
+ getChildDataSourceKey: function() { return this._childDataSourceKey; },
1248
+
1249
+ /**
1250
+ Sets a string that specifies a key on this data source's objects that may contain
1251
+ an array of data for a child data source.
1252
+ @param {String} childDataSourceKey A string containing a key on this data source's
1253
+ objects that may contain a child data source.
1254
+ */
1255
+ setChildDataSourceKey: function(childDataSourceKey) { this._childDataSourceKey = childDataSourceKey; },
1256
+
1257
+ _filteredDataSet: null,
1258
+
1259
+ /**
1260
+ Returns the filtered in-memory data set this data source will provide to the table view.
1261
+ @description NOTE: This may not be utilized by a custom data source.
1262
+ @type Array
1263
+ */
1264
+ getFilteredDataSet: function() { return this._filteredDataSet; },
1265
+
1266
+ /**
1267
+ Returns the total number of items contained within this data source.
1268
+ @description NOTE: This method is not typically used during the table view's rendering
1269
+ process. It is intended more for data-centric operations on this data source
1270
+ (e.g.: searching, filtering).
1271
+ IMPORTANT: When working with a data source that is driven from an in-memory data set,
1272
+ this method should ALWAYS be used to determine the length of the complete data set. It is
1273
+ NOT RECOMMENDED that the |length| property be accessed on the data set's array directly.
1274
+ @type Number
1275
+ */
1276
+ getNumberOfItems: function() { return this.getDataSet().length; },
1277
+
1278
+ /**
1279
+ Returns the item at the specified index of the complete data set contained within this
1280
+ data source.
1281
+ @description NOTE: This method is not typically used during the table view's rendering
1282
+ process. It is intended more for data-centric operations on this data source
1283
+ (e.g.: searching, filtering).
1284
+ IMPORTANT: When working with a data source that is driven from an in-memory data set,
1285
+ this method should ALWAYS be used to access elements from the complete data set. It is NOT
1286
+ RECOMMENDED that the elements be accessed using "[ ]" notation on the complete data set's
1287
+ array directly.
1288
+ @param {Number} index The index of the item in the complete data set to retrieve within
1289
+ this data source.
1290
+ @type Object
1291
+ */
1292
+ getItemAtIndex: function(index) { return this.getDataSet()[index]; },
1293
+
1294
+ /**
1295
+ Returns the number of filtered items contained within this data source.
1296
+ @description NOTE: This method is called directly by the table view's rendering process.
1297
+ It should yield the same result as the getNumberOfRows method in most cases.
1298
+ IMPORTANT: When working with a data source that is driven from an in-memory data set,
1299
+ this method should ALWAYS be used to determine the length of the filtered data set. It is
1300
+ NOT RECOMMENDED that the |length| property be accessed on the filtered data set's array
1301
+ directly.
1302
+ @type Number
1303
+ */
1304
+ getNumberOfFilteredItems: function() { return this.getFilteredDataSet().length; },
1305
+
1306
+ /**
1307
+ Returns the item at the specified index of the filtered data set contained within this
1308
+ data source.
1309
+ @description NOTE: This method is called directly by the table view's rendering process.
1310
+ It should yield the same data that is used by the getCellForRowAtIndex method in most cases.
1311
+ IMPORTANT: When working with a data source that is driven from an in-memory data set,
1312
+ this method should ALWAYS be used to access elements from the filtered data set. It is NOT
1313
+ RECOMMENDED that the elements be accessed using "[ ]" notation on the filtered data set's
1314
+ array directly.
1315
+ @param {Number} index The index of the item in the filtered data set to retrieve within
1316
+ this data source.
1317
+ @type Object
1318
+ */
1319
+ getFilteredItemAtIndex: function(index) { return this.getFilteredDataSet()[index]; },
1320
+
1321
+ setValueForKeyOnItem: function(item, key, value) {
1322
+ if (!item || !key) return;
1323
+
1324
+ var previousValue = item[key];
1325
+ if (previousValue === value) return;
1326
+
1327
+ item[key] = value;
1328
+
1329
+ var tableView = this.getTableView();
1330
+ tableView.$trigger($.Event(Pushpop.TableView.EventType.DidChangeValueForItemInDataSource, {
1331
+ tableView: tableView,
1332
+ dataSource: this,
1333
+ item: item,
1334
+ key: key,
1335
+ value: value,
1336
+ previousValue: previousValue
1337
+ }));
1338
+ },
1339
+
1340
+ _filterFunction: function(regExp, item) {
1341
+
1342
+ // Default filter function implementation that searches an item's title.
1343
+ return regExp.test(item.title);
1344
+ },
1345
+
1346
+ /**
1347
+ Returns the current filter function for searching this TableView.
1348
+ @description NOTE: This may not be utilized by a custom data source.
1349
+ @type Function
1350
+ */
1351
+ getFilterFunction: function() { return this._filterFunction; },
1352
+
1353
+ /**
1354
+ Sets a filter function to be used when searching this TableView.
1355
+ @param {Function} filterFunction The filter function to be used when searching this TableView.
1356
+ @description The filter function gets called for each item in the data set during a search. A
1357
+ valid filter function must take two parameters (regExp, item) and return a Boolean value. The
1358
+ |regExp| parameter contains a RegExp object based on the search string to be used to match items
1359
+ in the data set. The |item| parameter contains an item from the data set that the search string
1360
+ in the RegExp should be tested against. The provided filter function should return a Boolean
1361
+ value: |true| if the item should match the search string or |false| if it should be filtered out.
1362
+ NOTE: This may not be utilized by a custom data source.
1363
+ */
1364
+ setFilterFunction: function(filterFunction) { this._filterFunction = filterFunction; }
1365
+ };
1366
+
1367
+ /**
1368
+ Creates a new search bar for a TableView.
1369
+ @param {Pushpop.TableView} tableView The TableView this search bar should be attached to.
1370
+ @constructor
1371
+ */
1372
+ Pushpop.TableViewSearchBar = function TableViewSearchBar(tableView) {
1373
+ var $element = this.$element = $('<div class="pp-table-view-search-bar"/>');
1374
+ var element = this.element = $element[0];
1375
+
1376
+ var self = element.tableViewSearchBar = this;
1377
+
1378
+ var $input = this.$input = $('<input type="text" placeholder="Search"/>').appendTo($element);
1379
+ var $cancelButton = this.$cancelButton = $('<a class="pp-table-view-search-bar-button" href="#">Cancel</a>').appendTo($element);
1380
+ var $clearButton = this.$clearButton = $('<a class="pp-table-view-search-bar-clear-button" href="#"/>').appendTo($element);
1381
+ var $overlay = this.$overlay = $('<div class="pp-table-view-search-bar-overlay"/>');
1382
+
1383
+ var willClickCancel = false;
1384
+ var willClickClear = false;
1385
+ var willFocus = false;
1386
+
1387
+ $element.delegate('a', 'click', function(evt) { evt.preventDefault(); });
1388
+ $element.delegate('a', !!('ontouchstart' in window) ? 'touchstart' : 'mousedown', function(evt) {
1389
+ evt.stopImmediatePropagation();
1390
+ evt.preventDefault();
1391
+
1392
+ var $button = $(this);
1393
+ if ($button.hasClass('pp-table-view-search-bar-button')) willClickCancel = true;
1394
+ else if ($button.hasClass('pp-table-view-search-bar-clear-button')) willClickClear = true;
1395
+ });
1396
+ $element.delegate('a', !!('ontouchmove' in window) ? 'touchmove' : 'mousemove', function(evt) {
1397
+ if (willClickCancel || willClickClear) willClickCancel = willClickClear = false;
1398
+ });
1399
+ $element.delegate('a', !!('ontouchend' in window) ? 'touchend' : 'mouseup', function(evt) {
1400
+ if (willClickCancel) {
1401
+ willClickCancel = false;
1402
+ $input.val(null).trigger('keyup').trigger('blur');
1403
+ }
1404
+
1405
+ else if (willClickClear) {
1406
+ willClickClear = false;
1407
+ $input.val(null).trigger('keyup');
1408
+ }
1409
+ });
1410
+ $input.bind('mousedown touchstart', function(evt) {
1411
+ if ($input.is(':focus')) {
1412
+ evt.stopPropagation();
1413
+ return;
1414
+ }
1415
+
1416
+ evt.preventDefault();
1417
+ willFocus = true;
1418
+ });
1419
+ $input.bind('mousemove touchmove', function(evt) { willFocus = false; });
1420
+ $input.bind('mouseup touchend', function(evt) {
1421
+ if ($input.is(':focus')) {
1422
+ evt.stopPropagation();
1423
+ return;
1424
+ }
1425
+
1426
+ evt.preventDefault();
1427
+ if (willFocus) $input.trigger('focus');
1428
+ });
1429
+ $input.bind('focus', function(evt) {
1430
+ if (!willFocus) {
1431
+ $input.trigger('blur');
1432
+ return false;
1433
+ }
1434
+
1435
+ willFocus = false;
1436
+
1437
+ window.setTimeout(function() {
1438
+ $overlay.addClass('pp-active');
1439
+ if ($input.val()) $clearButton.addClass('pp-active');
1440
+ }, 1);
1441
+
1442
+ self.getTableView().scrollView.scrollToTop();
1443
+ });
1444
+ $input.bind('blur', function(evt) { $overlay.removeClass('pp-active'); $clearButton.removeClass('pp-active'); });
1445
+ $overlay.bind('mousedown touchstart', function(evt) { evt.stopPropagation(); evt.preventDefault(); });
1446
+ $overlay.bind('mouseup touchend', function(evt) { $input.trigger('blur'); });
1447
+ $input.bind('keyup', function(evt) {
1448
+
1449
+ // If 'ESC' key was pressed, cancel the search.
1450
+ if (evt.keyCode === 27) {
1451
+ $input.val(null).trigger('keyup').trigger('blur');
1452
+ return;
1453
+ }
1454
+
1455
+ var searchString = $input.val();
1456
+ var tableView = self._tableView;
1457
+
1458
+ if (!searchString) {
1459
+ $overlay.addClass('pp-active');
1460
+ $clearButton.removeClass('pp-active');
1461
+ } else {
1462
+ $overlay.removeClass('pp-active');
1463
+ $clearButton.addClass('pp-active');
1464
+ }
1465
+
1466
+ if (tableView.getDataSource().shouldReloadTableForSearchString(searchString)) tableView.reloadData();
1467
+ });
1468
+
1469
+ this.attachToTableView(tableView);
1470
+ };
1471
+
1472
+ Pushpop.TableViewSearchBar.prototype = {
1473
+ constructor: Pushpop.TableViewSearchBar,
1474
+
1475
+ element: null,
1476
+ $element: null,
1477
+ $input: null,
1478
+ $cancelButton: null,
1479
+ $clearButton: null,
1480
+ $overlay: null,
1481
+
1482
+ _tableView: null,
1483
+
1484
+ /**
1485
+
1486
+ */
1487
+ getTableView: function() { return this._tableView; },
1488
+
1489
+ /**
1490
+ Attaches this TableViewSearchBar to a TableView.
1491
+ @param {Pushpop.TableView} tableView A TableView to attach this search bar to.
1492
+ */
1493
+ attachToTableView: function(tableView) {
1494
+ this._tableView = tableView;
1495
+ this.$overlay.appendTo(tableView.scrollView.$element);
1496
+ tableView.$element.before(this.$element);
1497
+ },
1498
+
1499
+ /**
1500
+ Returns the current search string entered in the search bar.
1501
+ @type String
1502
+ */
1503
+ getSearchString: function() { return this.$input.val(); },
1504
+
1505
+ /**
1506
+ Sets the current search string that should appear in the search bar.
1507
+ @param {String} searchString The search string that should appear in the search bar.
1508
+ */
1509
+ setSearchString: function(searchString) { this.$input.val(searchString); }
1510
+ };
1511
+
1512
+ /**
1513
+ Creates a new default table view cell for a TableView with bold black title text.
1514
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1515
+ to the group of cells that this cell should belong to. This reuse identifier is
1516
+ used by the TableView to recycle TableViewCells of the same style and type.
1517
+ @constructor
1518
+ */
1519
+ Pushpop.TableViewCell = function TableViewCell(reuseIdentifier) {
1520
+ reuseIdentifier = this._reuseIdentifier = reuseIdentifier || this._reuseIdentifier;
1521
+
1522
+ var $element = this.$element = $('<li data-reuse-identifier="' + reuseIdentifier + '"/>');
1523
+ var element = this.element = $element[0];
1524
+
1525
+ element.tableViewCell = this;
1526
+
1527
+ $element.addClass(reuseIdentifier);
1528
+ };
1529
+
1530
+ Pushpop.TableViewCell.AccessoryType = {
1531
+ None: 'pp-table-view-cell-accessory-none',
1532
+ DisclosureIndicator: 'pp-table-view-cell-accessory-disclosure-indicator',
1533
+ DetailDisclosureButton: 'pp-table-view-cell-accessory-detail-disclosure-button',
1534
+ Checkmark: 'pp-table-view-cell-accessory-checkmark',
1535
+ ConfirmDeleteButton: 'pp-table-view-cell-accessory-confirm-delete-button'
1536
+ };
1537
+
1538
+ Pushpop.TableViewCell.EditingAccessoryType = {
1539
+ None: 'pp-table-view-cell-editing-accessory-none',
1540
+ AddButton: 'pp-table-view-cell-editing-accessory-add-button',
1541
+ DeleteButton: 'pp-table-view-cell-editing-accessory-delete-button'
1542
+ };
1543
+
1544
+ Pushpop.TableViewCell.prototype = {
1545
+ constructor: Pushpop.TableViewCell,
1546
+
1547
+ element: null,
1548
+ $element: null,
1549
+
1550
+ tableView: null,
1551
+
1552
+ _reuseIdentifier: 'pp-table-view-cell-default',
1553
+
1554
+ /**
1555
+ Returns a string containing this cell's reuse identifier.
1556
+ @type String
1557
+ */
1558
+ getReuseIdentifier: function() { return this._reuseIdentifier; },
1559
+
1560
+ /**
1561
+ Returns a string containing HTML to be used to render the cell's contents based
1562
+ on the cell's data.
1563
+ @description NOTE: When creating a custom cell class, this method should be
1564
+ overridden to provide the appropriate HTML markup for the cell.
1565
+ @type String
1566
+ */
1567
+ getHtml: function() {
1568
+ var data = this.getData();
1569
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1570
+ return '<h1>' + title + '</h1>';
1571
+ },
1572
+
1573
+ /**
1574
+ Returns a string containing HTML to be used to render the cell's accessories
1575
+ based on the cell's accessory type.
1576
+ @type String
1577
+ */
1578
+ getEditingAccessoryHtml: function() {
1579
+ var editingAccessoryType = this.getEditingAccessoryType();
1580
+ if (!editingAccessoryType || editingAccessoryType === Pushpop.TableViewCell.EditingAccessoryType.None) return '';
1581
+ return '<span class="pp-table-view-cell-editing-accessory ' + editingAccessoryType + '"/>';
1582
+ },
1583
+
1584
+ /**
1585
+ Returns a string containing HTML to be used to render the cell's accessories
1586
+ based on the cell's accessory type.
1587
+ @type String
1588
+ */
1589
+ getAccessoryHtml: function() {
1590
+ var accessoryType = this.getAccessoryType();
1591
+ if (!accessoryType || accessoryType === Pushpop.TableViewCell.AccessoryType.None) return '';
1592
+ return '<span class="pp-table-view-cell-accessory ' + accessoryType + '"/>';
1593
+ },
1594
+
1595
+ /**
1596
+ Renders the cell using HTML provided by the getHtml() and getAccessoryHtml()
1597
+ methods.
1598
+ @description NOTE: In most circumstances, this method shouldn't need to be
1599
+ overridden when creating a custom cell class. Typically, when creating a custom
1600
+ cell class, only the getHtml() method should need to be overridden.
1601
+ */
1602
+ draw: function() {
1603
+ this.$element.html(this.getEditingAccessoryHtml() + this.getHtml() + this.getAccessoryHtml());
1604
+ },
1605
+
1606
+ forceReflow: function() { var doNothing = this.element.offsetWidth; },
1607
+
1608
+ /**
1609
+ Performs any necessary actions when this cell has been tapped.
1610
+ @description NOTE: The default implementation does nothing. This method is
1611
+ intended to be overridden by custom table view cells that require an action
1612
+ to be taken upon tapping the cell (e.g.: pushing a new view).
1613
+ */
1614
+ didReceiveTap: function() {},
1615
+
1616
+ /**
1617
+ Removes this TableViewCell from the TableView's visible cells, resets its
1618
+ data and prepares it to be reused by the TableView by placing it in the
1619
+ reusable cells queue.
1620
+ */
1621
+ prepareForReuse: function() {
1622
+
1623
+ // Detach the TableViewCell from the DOM.
1624
+ // NOTE: Using .detach() will preserve any attached event handlers.
1625
+ this.$element.detach();
1626
+
1627
+ var tableView = this.tableView;
1628
+ var reuseIdentifier = this.getReuseIdentifier();
1629
+ var renderedCells = tableView.getRenderedCells();
1630
+ var reusableCells = tableView.getReusableCells();
1631
+ reusableCells = reusableCells[reuseIdentifier] || (reusableCells[reuseIdentifier] = []);
1632
+
1633
+ reusableCells.push(this);
1634
+
1635
+ for (var i = 0, length = renderedCells.length; i < length; i++) {
1636
+ if (renderedCells[i] === this) {
1637
+ renderedCells.splice(i, 1);
1638
+ break;
1639
+ }
1640
+ }
1641
+
1642
+ this.setSelected(false);
1643
+ this.setIndex(-1);
1644
+ this.setAccessoryType(null);
1645
+ this.setEditingAccessoryType(null);
1646
+ this.setData(null);
1647
+ },
1648
+
1649
+ _data: null,
1650
+
1651
+ /**
1652
+ Returns the data of the item in the data source that corresponds to this cell.
1653
+ @type Object
1654
+ */
1655
+ getData: function() { return this._data; },
1656
+
1657
+ /**
1658
+ Sets the data of this cell that corresponds to an item in the data source.
1659
+ @description NOTE: This method will set the cell's value to the |value| property
1660
+ of the provided data.
1661
+ @param {Object} data The data of an item in the data source to assign to this cell.
1662
+ */
1663
+ setData: function(data) {
1664
+ this._data = data;
1665
+ if (!data) return;
1666
+ if (data.value) {
1667
+ this.setValue(data.value);
1668
+ return;
1669
+ }
1670
+
1671
+ this.draw();
1672
+ },
1673
+
1674
+ _accessoryType: null,
1675
+
1676
+ /**
1677
+ Returns the type of accessory to render for this cell. The types of available
1678
+ accessories are specified in Pushpop.TableViewCell.AccessoryType.
1679
+ @description NOTE: Table view cell accessories are rendered on the right-hand
1680
+ side of the cell.
1681
+ @type String
1682
+ */
1683
+ getAccessoryType: function() { return this._accessoryType; },
1684
+
1685
+ /**
1686
+ Sets the type of accessory to render for this cell. The types of available
1687
+ accessories are specified in Pushpop.TableViewCell.AccessoryType.
1688
+ @description NOTE: Table view cell accessories are rendered on the right-hand
1689
+ side of the cell.
1690
+ @param {String} accessoryType The type of accessory to render for this cell.
1691
+ */
1692
+ setAccessoryType: function(accessoryType) { this._accessoryType = (accessoryType !== Pushpop.TableViewCell.AccessoryType.None) ? accessoryType : null; },
1693
+
1694
+ _editingAccessoryType: null,
1695
+
1696
+ /**
1697
+ Returns the type of editing accessory to render for this cell. The types of available
1698
+ editing accessories are specified in Pushpop.TableViewCell.EditingAccessoryType.
1699
+ @description NOTE: Table view cell editing accessories are rendered on the left-hand
1700
+ side of the cell.
1701
+ @type String
1702
+ */
1703
+ getEditingAccessoryType: function() { return this._editingAccessoryType; },
1704
+
1705
+ /**
1706
+ Sets the type of editing accessory to render for this cell. The types of available
1707
+ editing accessories are specified in Pushpop.TableViewCell.EditingAccessoryType.
1708
+ @description NOTE: Table view cell editing accessories are rendered on the left-hand
1709
+ side of the cell.
1710
+ @param {String} editingAccessoryType The type of editing accessory to render for this cell.
1711
+ */
1712
+ setEditingAccessoryType: function(editingAccessoryType) { this._editingAccessoryType = (editingAccessoryType !== Pushpop.TableViewCell.EditingAccessoryType.None) ? editingAccessoryType : null; },
1713
+
1714
+ _value: null,
1715
+
1716
+ /**
1717
+ Returns the value of the item in the data source that corresponds to this cell.
1718
+ @description NOTE: This method is typically only used by "input" cell types. When
1719
+ setData() is called, the cell's value will be set to the |value| property of the
1720
+ cell's data (e.g.: this.getData().value). The value that is returned by this method
1721
+ originates from the |value| property of the cell's data.
1722
+ @type Number|String|Object
1723
+ */
1724
+ getValue: function() { return this._value; },
1725
+
1726
+ /**
1727
+ Sets the value of this cell that corresponds to an item in the data source.
1728
+ @description NOTE: This method is typically only used by "input" cell types. When
1729
+ setData() is called, this method is called to set the |value| property of the
1730
+ cell's data (e.g.: this.getData().value). The value that is set by this method
1731
+ will also replace the value of the |value| property of the cell's data.
1732
+ @param {Number|String|Object} value The value of an item in the data source to assign to this cell.
1733
+ */
1734
+ setValue: function(value) {
1735
+ var data = this.getData();
1736
+ var dataSource = this.tableView.getDataSource();
1737
+ dataSource.setValueForKeyOnItem(data, 'value', value);
1738
+
1739
+ this._value = value;
1740
+ this.draw();
1741
+ },
1742
+
1743
+ _index: -1,
1744
+
1745
+ /**
1746
+ Returns the index of the item in the data source that corresponds to this cell.
1747
+ @type Number
1748
+ */
1749
+ getIndex: function() { return this._index; },
1750
+
1751
+ /**
1752
+ Sets the index of this cell that corresponds to an item in the data source.
1753
+ @param {Number} index The index of an item in the data source to assign to this cell.
1754
+ */
1755
+ setIndex: function(index) { this._index = index; },
1756
+
1757
+ _isSelected: false,
1758
+
1759
+ /**
1760
+ Returns a flag indicating whether or not this TableViewCell is currently selected.
1761
+ @type Boolean
1762
+ */
1763
+ getSelected: function() { return this._isSelected; },
1764
+
1765
+ /**
1766
+ Sets a flag to indicate if this TableViewCell should be selected.
1767
+ @param {Boolean} value A boolean value to determine if this cell should be selected.
1768
+ */
1769
+ setSelected: function(value) {
1770
+ if ((this._isSelected = value)) {
1771
+ this.$element.addClass('pp-table-view-selected-state');
1772
+ } else {
1773
+ this.$element.removeClass('pp-table-view-selected-state');
1774
+ }
1775
+ }
1776
+ };
1777
+
1778
+ // Register the prototype for Pushpop.TableViewCell as a reusable cell type.
1779
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.TableViewCell.prototype);
1780
+
1781
+ /**
1782
+ Creates a new table view cell for a TableView with bold black title text and grey
1783
+ subtitle text.
1784
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1785
+ to the group of cells that this cell should belong to.
1786
+ @constructor
1787
+ @extends Pushpop.TableViewCell
1788
+ */
1789
+ Pushpop.SubtitleTableViewCell = function SubtitleTableViewCell(reuseIdentifier) {
1790
+
1791
+ // Call the "super" constructor.
1792
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1793
+ };
1794
+
1795
+ Pushpop.SubtitleTableViewCell.prototype = new Pushpop.TableViewCell('pp-subtitle-table-view-cell');
1796
+ Pushpop.SubtitleTableViewCell.prototype.constructor = Pushpop.SubtitleTableViewCell;
1797
+
1798
+ Pushpop.SubtitleTableViewCell.prototype.getHtml = function() {
1799
+ var data = this.getData();
1800
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1801
+ var subtitle = $.trim((data && data.subtitle) ? data.subtitle : '&nbsp;');
1802
+ return '<h1>' + title + '</h1><h2>' + subtitle + '</h2>';
1803
+ };
1804
+
1805
+ // Register the prototype for Pushpop.SubtitleTableViewCell as a reusable cell type.
1806
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.SubtitleTableViewCell.prototype);
1807
+
1808
+ /**
1809
+ Creates a new table view cell for a TableView with a bold black text label and a
1810
+ blue text value.
1811
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1812
+ to the group of cells that this cell should belong to.
1813
+ @constructor
1814
+ @extends Pushpop.TableViewCell
1815
+ */
1816
+ Pushpop.ValueTableViewCell = function ValueTableViewCell(reuseIdentifier) {
1817
+
1818
+ // Call the "super" constructor.
1819
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1820
+ };
1821
+
1822
+ Pushpop.ValueTableViewCell.prototype = new Pushpop.TableViewCell('pp-value-table-view-cell');
1823
+ Pushpop.ValueTableViewCell.prototype.constructor = Pushpop.ValueTableViewCell;
1824
+
1825
+ Pushpop.ValueTableViewCell.prototype.getHtml = function() {
1826
+ var data = this.getData();
1827
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1828
+ var value = $.trim((data && data.value) ? data.value : '&nbsp;');
1829
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
1830
+ };
1831
+
1832
+ // Register the prototype for Pushpop.ValueTableViewCell as a reusable cell type.
1833
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.ValueTableViewCell.prototype);
1834
+
1835
+ /**
1836
+ Creates a new table view cell for a TableView with a small bold blue text label
1837
+ and a long black bold text value.
1838
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1839
+ to the group of cells that this cell should belong to.
1840
+ @constructor
1841
+ @extends Pushpop.TableViewCell
1842
+ */
1843
+ Pushpop.Value2TableViewCell = function Value2TableViewCell(reuseIdentifier) {
1844
+
1845
+ // Call the "super" constructor.
1846
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1847
+ };
1848
+
1849
+ Pushpop.Value2TableViewCell.prototype = new Pushpop.TableViewCell('pp-value2-table-view-cell');
1850
+ Pushpop.Value2TableViewCell.prototype.constructor = Pushpop.Value2TableViewCell;
1851
+
1852
+ Pushpop.Value2TableViewCell.prototype.getHtml = function() {
1853
+ var data = this.getData();
1854
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1855
+ var value = $.trim((data && data.value) ? data.value : '&nbsp;');
1856
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
1857
+ };
1858
+
1859
+ // Register the prototype for Pushpop.Value2TableViewCell as a reusable cell type.
1860
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.Value2TableViewCell.prototype);
1861
+
1862
+ /**
1863
+ Creates a new table view cell for a TableView with a small bold blue text label
1864
+ and an inline text input field.
1865
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1866
+ to the group of cells that this cell should belong to.
1867
+ @constructor
1868
+ @extends Pushpop.TableViewCell
1869
+ */
1870
+ Pushpop.InlineTextInputTableViewCell = function InlineTextInputTableViewCell(reuseIdentifier) {
1871
+
1872
+ // Call the "super" constructor.
1873
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1874
+
1875
+ var self = this;
1876
+
1877
+ // Attach an event handler to this cell to update its value when the input changes.
1878
+ this.$element.delegate('input', 'keyup change', function(evt) {
1879
+ var data = self.getData();
1880
+ var value = $(this).val();
1881
+ var dataSource = self.tableView.getDataSource();
1882
+ dataSource.setValueForKeyOnItem(data, 'value', value);
1883
+
1884
+ this._value = value;
1885
+ });
1886
+
1887
+ this.$element.bind(!!('ontouchstart' in window) ? 'touchend' : 'mouseup', function(evt) {
1888
+ evt.preventDefault();
1889
+ });
1890
+ };
1891
+
1892
+ Pushpop.InlineTextInputTableViewCell.prototype = new Pushpop.TableViewCell('pp-inline-text-input-table-view-cell');
1893
+ Pushpop.InlineTextInputTableViewCell.prototype.constructor = Pushpop.InlineTextInputTableViewCell;
1894
+
1895
+ Pushpop.InlineTextInputTableViewCell.prototype.getHtml = function() {
1896
+ var data = this.getData();
1897
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1898
+ var name = $.trim((data && data.name) ? data.name : '');
1899
+ var value = $.trim((data && data.value) ? data.value : '');
1900
+ var autoCapitalize = (data) ? (data.autoCapitalize === false) ? 'off' : (data.autoCapitalize === true) ? 'on' : (data.autoCapitalize) ? data.autoCapitalize : 'on' : 'on';
1901
+ var autoCorrect = (data) ? (data.autoCorrect + '') : 'on';
1902
+ autoCorrect = !!(autoCorrect !== 'false' && autoCorrect !== 'off');
1903
+ var isPassword = (data) ? (data.password || 'false') : 'false';
1904
+ isPassword = isPassword !== 'false';
1905
+ return '<h1>' + title + '</h1><h2><input type="' + (isPassword ? 'password' : 'text') + '" name="' + name + '" value="' + value + '" autocapitalize="' + autoCapitalize + '" autocorrect="' + (autoCorrect ? 'on' : 'off') +'"/></h2>';
1906
+ };
1907
+
1908
+ Pushpop.InlineTextInputTableViewCell.prototype.didReceiveTap = function() {
1909
+ var $element = this.$element;
1910
+ $element.find('input').trigger('focus');
1911
+ window.setTimeout(function() { $element.removeClass('pp-table-view-selected-state'); }, 100);
1912
+ };
1913
+
1914
+ // Register the prototype for Pushpop.InlineTextInputTableViewCell as a reusable cell type.
1915
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.InlineTextInputTableViewCell.prototype);
1916
+
1917
+ /**
1918
+ Creates a new table view cell for a TableView with a small bold blue text label
1919
+ and a long black bold text value. When this type of cell is tapped, a new view
1920
+ is presented with a large text area for entering long strings of text.
1921
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1922
+ to the group of cells that this cell should belong to.
1923
+ @constructor
1924
+ @extends Pushpop.TableViewCell
1925
+ */
1926
+ Pushpop.TextAreaInputTableViewCell = function TextAreaInputTableViewCell(reuseIdentifier) {
1927
+
1928
+ // Call the "super" constructor.
1929
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1930
+ };
1931
+
1932
+ Pushpop.TextAreaInputTableViewCell.prototype = new Pushpop.TableViewCell('pp-text-area-input-table-view-cell');
1933
+ Pushpop.TextAreaInputTableViewCell.prototype.constructor = Pushpop.TextAreaInputTableViewCell;
1934
+
1935
+ Pushpop.TextAreaInputTableViewCell.prototype.getHtml = function() {
1936
+ var data = this.getData();
1937
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1938
+ var value = $.trim((data && data.value) ? data.value : '&nbsp;');
1939
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
1940
+ };
1941
+
1942
+ Pushpop.TextAreaInputTableViewCell.prototype.getAccessoryType = function() { return this._accessoryType || Pushpop.TableViewCell.AccessoryType.DisclosureIndicator; };
1943
+
1944
+ Pushpop.TextAreaInputTableViewCell.prototype.didReceiveTap = function() {
1945
+ var tableView = this.tableView;
1946
+ var viewStack = tableView.getViewStack();
1947
+ if (!viewStack) return;
1948
+
1949
+ var data = this.getData();
1950
+ if (!data) return;
1951
+
1952
+ var title = data.title || '';
1953
+ var name = data.name || '';
1954
+ var value = data.value || '';
1955
+
1956
+ var self = this;
1957
+
1958
+ // Push a new view with a large text area input.
1959
+ viewStack.pushNewView(function(newView) {
1960
+ var $textarea = $('<textarea class="pp-text-area-input-table-view-cell-textarea" name="' + name + '">' + value + '</textarea>').appendTo(newView.$element);
1961
+
1962
+ newView.setTitle(title);
1963
+ newView.addBarButtonItem(new Pushpop.Button('Done', function(button) {
1964
+ self.setValue($textarea.val());
1965
+ tableView.reloadData();
1966
+ viewStack.pop();
1967
+ }, Pushpop.Button.ButtonAlignmentType.Right, Pushpop.Button.ButtonStyleType.Blue));
1968
+ });
1969
+ };
1970
+
1971
+ // Register the prototype for Pushpop.TextAreaInputTableViewCell as a reusable cell type.
1972
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.TextAreaInputTableViewCell.prototype);
1973
+
1974
+ /**
1975
+ Creates a new table view cell for a TableView with a small bold blue text label
1976
+ and a long black bold text value. When this type of cell is tapped, a table view
1977
+ is presented that contains the cell's "child" data source with a list of options
1978
+ to pick from.
1979
+ @description NOTE: The data for the "child" data source must be contained in a
1980
+ property in this cell's data called |childDataSource|.
1981
+ @param {String} reuseIdentifier A string containing an identifier that is unique
1982
+ to the group of cells that this cell should belong to.
1983
+ @constructor
1984
+ @extends Pushpop.TableViewCell
1985
+ */
1986
+ Pushpop.SelectInputTableViewCell = function SelectInputTableViewCell(reuseIdentifier) {
1987
+
1988
+ // Call the "super" constructor.
1989
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
1990
+ };
1991
+
1992
+ Pushpop.SelectInputTableViewCell.prototype = new Pushpop.TableViewCell('pp-select-input-table-view-cell');
1993
+ Pushpop.SelectInputTableViewCell.prototype.constructor = Pushpop.SelectInputTableViewCell;
1994
+
1995
+ Pushpop.SelectInputTableViewCell.prototype.getHtml = function() {
1996
+ var data = this.getData();
1997
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
1998
+ var value = $.trim((data && data.value) ? ((data.value.title) ? data.value.title : data.value) : '&nbsp;');
1999
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
2000
+ };
2001
+
2002
+ Pushpop.SelectInputTableViewCell.prototype.getAccessoryType = function() { return this._accessoryType || Pushpop.TableViewCell.AccessoryType.DisclosureIndicator; };
2003
+
2004
+ Pushpop.SelectInputTableViewCell.prototype.didReceiveTap = function() {
2005
+ var tableView = this.tableView;
2006
+
2007
+ var viewStack = tableView.getViewStack();
2008
+ if (!viewStack) return;
2009
+
2010
+ var view = tableView.getView();
2011
+ if (!view) return;
2012
+
2013
+ var data = this.getData();
2014
+ if (!data) return;
2015
+
2016
+ var childDataSource = new Pushpop.TableViewDataSource(data.childDataSource);
2017
+
2018
+ var self = this;
2019
+
2020
+ // Push a new view with a large text area input.
2021
+ viewStack.pushNewTableView(function(newTableView) {
2022
+ newTableView.setSearchBar(new Pushpop.TableViewSearchBar(newTableView));
2023
+ newTableView.setDataSource(childDataSource);
2024
+
2025
+ newTableView.$bind(Pushpop.TableView.EventType.DidSelectRowAtIndex, function(evt) {
2026
+ if (evt.hasChildDataSource) return;
2027
+
2028
+ var tableView = evt.tableView;
2029
+ var dataSource = tableView.getDataSource();
2030
+ var item = dataSource.getFilteredItemAtIndex(evt.index);
2031
+
2032
+ self.setValue(item);
2033
+ viewStack.pop(view);
2034
+ });
2035
+ });
2036
+ };
2037
+
2038
+ Pushpop.SelectInputTableViewCell.prototype.setValue = function(value) {
2039
+ var data = this.getData();
2040
+ var childDataSource;
2041
+
2042
+ if (data) {
2043
+ if (!(value instanceof Object) && (childDataSource = data.childDataSource)) {
2044
+ for (var i = 0, length = childDataSource.length; i < length; i++) {
2045
+ if (childDataSource[i].value === value) {
2046
+ value = childDataSource[i];
2047
+ break;
2048
+ }
2049
+ }
2050
+ }
2051
+
2052
+ var dataSource = this.tableView.getDataSource();
2053
+ dataSource.setValueForKeyOnItem(data, 'value', value);
2054
+ }
2055
+
2056
+ this._value = value;
2057
+ this.draw();
2058
+ };
2059
+
2060
+ // Register the prototype for Pushpop.SelectInputTableViewCell as a reusable cell type.
2061
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.SelectInputTableViewCell.prototype);
2062
+
2063
+ /**
2064
+ Creates a new table view cell for a TableView with a small bold blue text label
2065
+ and a black bold date value. When this type of cell is tapped, a table view is
2066
+ presented that allows the user to select a date.
2067
+ @param {String} reuseIdentifier A string containing an identifier that is unique
2068
+ to the group of cells that this cell should belong to.
2069
+ @constructor
2070
+ @extends Pushpop.TableViewCell
2071
+ */
2072
+ Pushpop.DateInputTableViewCell = function DateInputTableViewCell(reuseIdentifier) {
2073
+
2074
+ // Call the "super" constructor.
2075
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
2076
+ };
2077
+
2078
+ Pushpop.DateInputTableViewCell.prototype = new Pushpop.TableViewCell('pp-date-input-table-view-cell');
2079
+ Pushpop.DateInputTableViewCell.prototype.constructor = Pushpop.DateInputTableViewCell;
2080
+
2081
+ Pushpop.DateInputTableViewCell.prototype.getHtml = function() {
2082
+ var data = this.getData();
2083
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
2084
+ var value = $.trim((data && data.value) ? data.value : '&nbsp;');
2085
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
2086
+ };
2087
+
2088
+ Pushpop.DateInputTableViewCell.prototype.getAccessoryType = function() { return this._accessoryType || Pushpop.TableViewCell.AccessoryType.DisclosureIndicator; };
2089
+
2090
+ Pushpop.DateInputTableViewCell.prototype.didReceiveTap = function() {
2091
+ var tableView = this.tableView;
2092
+
2093
+ var viewStack = tableView.getViewStack();
2094
+ if (!viewStack) return;
2095
+
2096
+ var data = this.getData();
2097
+ if (!data) return;
2098
+
2099
+ var i, dayDataSource = [], yearDataSource = [];
2100
+ for (i = 1; i <= 31; i++) dayDataSource.push({ value: i, title: i + '' });
2101
+ for (i = 1970; i <= 2100; i++) yearDataSource.push({ value: i, title: i + '' });
2102
+
2103
+ var monthDataSource = [
2104
+ { value: 1, title: 'January' }, { value: 2, title: 'February' },
2105
+ { value: 3, title: 'March' }, { value: 4, title: 'April' },
2106
+ { value: 5, title: 'May' }, { value: 6, title: 'June' },
2107
+ { value: 7, title: 'July' }, { value: 8, title: 'August' },
2108
+ { value: 9, title: 'September' }, { value: 10, title: 'October' },
2109
+ { value: 11, title: 'November' }, { value: 12, title: 'December' }
2110
+ ];
2111
+
2112
+ var dateParts = this.getValue(), currentDate = new Date();
2113
+ if (!dateParts || (typeof dateParts !== 'string')) dateParts = currentDate.getFullYear() + '-' + (currentDate.getMonth() + 1) + '-' + currentDate.getDate();
2114
+ dateParts = dateParts.split('-');
2115
+
2116
+ var year = window.parseInt(dateParts[0], 10);
2117
+ var month = window.parseInt(dateParts[1], 10);
2118
+ var day = window.parseInt(dateParts[2], 10);
2119
+
2120
+ year = { value: year, title: year + '' };
2121
+ day = { value: day, title: day + '' };
2122
+
2123
+ for (i = 0; i < 12; i++) if (monthDataSource[i].value === month) {
2124
+ month = monthDataSource[i];
2125
+ break;
2126
+ }
2127
+
2128
+ if (!month || !month.value) month = monthDataSource[0];
2129
+
2130
+ var dataSource = new Pushpop.TableViewDataSource([
2131
+ {
2132
+ reuseIdentifier: 'pp-select-input-table-view-cell',
2133
+ title: 'Month',
2134
+ name: 'month',
2135
+ value: month,
2136
+ childDataSource: monthDataSource
2137
+ },
2138
+ {
2139
+ reuseIdentifier: 'pp-select-input-table-view-cell',
2140
+ title: 'Day',
2141
+ name: 'day',
2142
+ value: day,
2143
+ childDataSource: dayDataSource
2144
+ },
2145
+ {
2146
+ reuseIdentifier: 'pp-select-input-table-view-cell',
2147
+ title: 'Year',
2148
+ name: 'year',
2149
+ value: year,
2150
+ childDataSource: yearDataSource
2151
+ }
2152
+ ]);
2153
+
2154
+ var self = this;
2155
+
2156
+ // Push a new view with a large text area input.
2157
+ viewStack.pushNewTableView(function(newTableView) {
2158
+ newTableView.setDataSource(dataSource);
2159
+
2160
+ var newView = newTableView.getView();
2161
+ newView.setTitle($.trim((data && data.title) ? data.title : 'Date'));
2162
+ newView.addBarButtonItem(new Pushpop.Button('Done', function(button) {
2163
+ var value = dataSource.getValuesObject();
2164
+ var year = value.year.value;
2165
+ var month = value.month.value;
2166
+ var day = value.day.value;
2167
+
2168
+ self.setValue(year + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day);
2169
+ tableView.reloadData();
2170
+ viewStack.pop();
2171
+ }, Pushpop.Button.ButtonAlignmentType.Right, Pushpop.Button.ButtonStyleType.Blue));
2172
+ });
2173
+ };
2174
+
2175
+ // Register the prototype for Pushpop.DateInputTableViewCell as a reusable cell type.
2176
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.DateInputTableViewCell.prototype);
2177
+
2178
+ /**
2179
+ Creates a new table view cell for a TableView with a small bold blue text label
2180
+ and a black bold time value. When this type of cell is tapped, a table view is
2181
+ presented that allows the user to select a time.
2182
+ @param {String} reuseIdentifier A string containing an identifier that is unique
2183
+ to the group of cells that this cell should belong to.
2184
+ @constructor
2185
+ @extends Pushpop.TableViewCell
2186
+ */
2187
+ Pushpop.TimeInputTableViewCell = function TimeInputTableViewCell(reuseIdentifier) {
2188
+
2189
+ // Call the "super" constructor.
2190
+ Pushpop.TableViewCell.prototype.constructor.apply(this, arguments);
2191
+ };
2192
+
2193
+ Pushpop.TimeInputTableViewCell.prototype = new Pushpop.TableViewCell('pp-time-input-table-view-cell');
2194
+ Pushpop.TimeInputTableViewCell.prototype.constructor = Pushpop.TimeInputTableViewCell;
2195
+
2196
+ Pushpop.TimeInputTableViewCell.prototype.getHtml = function() {
2197
+ var data = this.getData();
2198
+ var title = $.trim((data && data.title) ? data.title : '&nbsp;');
2199
+ var value = $.trim((data && data.value) ? data.value : '&nbsp;');
2200
+ return '<h1>' + title + '</h1><h2>' + value + '</h2>';
2201
+ };
2202
+
2203
+ Pushpop.TimeInputTableViewCell.prototype.getAccessoryType = function() { return this._accessoryType || Pushpop.TableViewCell.AccessoryType.DisclosureIndicator; };
2204
+
2205
+ Pushpop.TimeInputTableViewCell.prototype.didReceiveTap = function() {
2206
+ var tableView = this.tableView;
2207
+
2208
+ var viewStack = tableView.getViewStack();
2209
+ if (!viewStack) return;
2210
+
2211
+ var data = this.getData();
2212
+ if (!data) return;
2213
+
2214
+ var i, hourDataSource = [], minuteDataSource = [];
2215
+ for (i = 0; i <= 23; i++) hourDataSource.push({ value: (i < 10 ? '0' : '') + i, title: (i < 10 ? '0' : '') + i });
2216
+ for (i = 0; i <= 59; i++) minuteDataSource.push({ value: (i < 10 ? '0' : '') + i, title: (i < 10 ? '0' : '') + i });
2217
+
2218
+ var timeParts = this.getValue(), currentTime = new Date();
2219
+ if (!timeParts || (typeof timeParts !== 'string')) timeParts = currentTime.getHours() + ':' + currentTime.getMinutes();
2220
+ timeParts = timeParts.split(':');
2221
+
2222
+ var hour = window.parseInt(timeParts[0], 10);
2223
+ var minute = window.parseInt(timeParts[1], 10);
2224
+
2225
+ hour = { value: (hour < 10 ? '0' : '') + hour, title: (hour < 10 ? '0' : '') + hour };
2226
+ minute = { value: (minute < 10 ? '0' : '') + minute, title: (minute < 10 ? '0' : '') + minute };
2227
+
2228
+ var dataSource = new Pushpop.TableViewDataSource([
2229
+ {
2230
+ reuseIdentifier: 'pp-select-input-table-view-cell',
2231
+ title: 'Hour',
2232
+ name: 'hour',
2233
+ value: hour,
2234
+ childDataSource: hourDataSource
2235
+ },
2236
+ {
2237
+ reuseIdentifier: 'pp-select-input-table-view-cell',
2238
+ title: 'Minute',
2239
+ name: 'minute',
2240
+ value: minute,
2241
+ childDataSource: minuteDataSource
2242
+ }
2243
+ ]);
2244
+
2245
+ var self = this;
2246
+
2247
+ // Push a new view with a large text area input.
2248
+ viewStack.pushNewTableView(function(newTableView) {
2249
+ newTableView.setDataSource(dataSource);
2250
+
2251
+ var newView = newTableView.getView();
2252
+ newView.setTitle($.trim((data && data.title) ? data.title : 'Time'));
2253
+ newView.addBarButtonItem(new Pushpop.Button('Done', function(button) {
2254
+ var value = dataSource.getValuesObject();
2255
+ var hour = value.hour.value;
2256
+ var minute = value.minute.value;
2257
+
2258
+ self.setValue(hour + ':' + minute);
2259
+ tableView.reloadData();
2260
+ viewStack.pop();
2261
+ }, Pushpop.Button.ButtonAlignmentType.Right, Pushpop.Button.ButtonStyleType.Blue));
2262
+ });
2263
+ };
2264
+
2265
+ // Register the prototype for Pushpop.TimeInputTableViewCell as a reusable cell type.
2266
+ Pushpop.TableView.registerReusableCellPrototype(Pushpop.TimeInputTableViewCell.prototype);
2267
+
2268
+ $(function() {
2269
+ var tableViews = Pushpop.tableViews = Pushpop.tableViews || {};
2270
+
2271
+ $('.pp-table-view').each(function(index, element) {
2272
+ var tableView = new Pushpop.TableView(element);
2273
+ if (element.id) tableViews[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = tableView;
2274
+ });
2275
+ });