pushpop-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ });