capybara-ember-inspector 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +2 -0
- data/capybara-ember-inspector.gemspec +26 -0
- data/lib/capybara/ember/inspector.rb +12 -0
- data/lib/capybara/ember/inspector/extension/.gitignore +0 -0
- data/lib/capybara/ember/inspector/extension/background-script.js +74 -0
- data/lib/capybara/ember/inspector/extension/content-script.js +58 -0
- data/lib/capybara/ember/inspector/extension/devtools.html +24 -0
- data/lib/capybara/ember/inspector/extension/devtools.js +5 -0
- data/lib/capybara/ember/inspector/extension/ember_debug/ember_debug.js +3173 -0
- data/lib/capybara/ember/inspector/extension/images/arrow_down.svg +52 -0
- data/lib/capybara/ember/inspector/extension/images/calculate.svg +15 -0
- data/lib/capybara/ember/inspector/extension/images/ember-icon-final.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/fishy_tomster.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/hamster.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/icon128.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/icon16.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/icon19.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/icon38.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/icon48.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/send.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/send_arrow.png +0 -0
- data/lib/capybara/ember/inspector/extension/images/tomster.png +0 -0
- data/lib/capybara/ember/inspector/extension/in-page-script.js +14 -0
- data/lib/capybara/ember/inspector/extension/manifest.json +39 -0
- data/lib/capybara/ember/inspector/extension/options.html +62 -0
- data/lib/capybara/ember/inspector/extension/options.js +26 -0
- data/lib/capybara/ember/inspector/extension/panes/ember_extension.css +1411 -0
- data/lib/capybara/ember/inspector/extension/panes/ember_extension.js +4687 -0
- data/lib/capybara/ember/inspector/extension/panes/index.html +17 -0
- data/lib/capybara/ember/inspector/extension/panes/start.js +3 -0
- data/lib/capybara/ember/inspector/extension/vendor/chrome-bootstrap.css +636 -0
- data/lib/capybara/ember/inspector/extension/vendor/ember.js +46943 -0
- data/lib/capybara/ember/inspector/extension/vendor/ember.prod.js +46498 -0
- data/lib/capybara/ember/inspector/extension/vendor/handlebars.js +2278 -0
- data/lib/capybara/ember/inspector/extension/vendor/jquery.js +9555 -0
- data/lib/capybara/ember/inspector/extension/vendor/list-view.prod.js +1437 -0
- data/lib/capybara/ember/inspector/extension/vendor/loader.js +41 -0
- data/lib/capybara/ember/inspector/extension/vendor/resolver.js +188 -0
- data/lib/capybara/ember/inspector/version.rb +7 -0
- metadata +158 -0
@@ -0,0 +1,1437 @@
|
|
1
|
+
// Last commit: adcb1c8 (2014-02-20 11:33:26 -0500)
|
2
|
+
|
3
|
+
|
4
|
+
// ==========================================================================
|
5
|
+
// Project: Ember ListView
|
6
|
+
// Copyright: ©2012-2013 Erik Bryn, Yapp Inc., and contributors.
|
7
|
+
// License: Licensed under MIT license
|
8
|
+
// ==========================================================================
|
9
|
+
|
10
|
+
|
11
|
+
(function() {
|
12
|
+
var get = Ember.get, set = Ember.set;
|
13
|
+
|
14
|
+
function samePosition(a, b) {
|
15
|
+
return a && b && a.x === b.x && a.y === b.y;
|
16
|
+
}
|
17
|
+
|
18
|
+
function positionElement() {
|
19
|
+
var element, position, _position;
|
20
|
+
|
21
|
+
Ember.instrument('view.updateContext.positionElement', this, function() {
|
22
|
+
element = get(this, 'element');
|
23
|
+
position = this.position;
|
24
|
+
_position = this._position;
|
25
|
+
|
26
|
+
if (!position || !element) { return; }
|
27
|
+
|
28
|
+
// TODO: avoid needing this by avoiding unnecessary
|
29
|
+
// calls to this method in the first place
|
30
|
+
if (samePosition(position, _position)) { return; }
|
31
|
+
Ember.run.schedule('render', this, this._parentView.applyTransform, element, position.x, position.y);
|
32
|
+
this._position = position;
|
33
|
+
}, this);
|
34
|
+
}
|
35
|
+
|
36
|
+
Ember.ListItemViewMixin = Ember.Mixin.create({
|
37
|
+
init: function(){
|
38
|
+
this._super();
|
39
|
+
this.one('didInsertElement', positionElement);
|
40
|
+
},
|
41
|
+
classNames: ['ember-list-item-view'],
|
42
|
+
_position: null,
|
43
|
+
updatePosition: function(position) {
|
44
|
+
this.position = position;
|
45
|
+
this._positionElement();
|
46
|
+
},
|
47
|
+
_positionElement: positionElement
|
48
|
+
});
|
49
|
+
|
50
|
+
})();
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
(function() {
|
55
|
+
var get = Ember.get, set = Ember.set;
|
56
|
+
|
57
|
+
var backportedInnerString = function(buffer) {
|
58
|
+
var content = [], childBuffers = buffer.childBuffers;
|
59
|
+
|
60
|
+
Ember.ArrayPolyfills.forEach.call(childBuffers, function(buffer) {
|
61
|
+
var stringy = typeof buffer === 'string';
|
62
|
+
if (stringy) {
|
63
|
+
content.push(buffer);
|
64
|
+
} else {
|
65
|
+
buffer.array(content);
|
66
|
+
}
|
67
|
+
});
|
68
|
+
|
69
|
+
return content.join('');
|
70
|
+
};
|
71
|
+
|
72
|
+
function willInsertElementIfNeeded(view) {
|
73
|
+
if (view.willInsertElement) {
|
74
|
+
view.willInsertElement();
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
function didInsertElementIfNeeded(view) {
|
79
|
+
if (view.didInsertElement) {
|
80
|
+
view.didInsertElement();
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
function rerender() {
|
85
|
+
var element, buffer, context, hasChildViews;
|
86
|
+
element = get(this, 'element');
|
87
|
+
|
88
|
+
if (!element) { return; }
|
89
|
+
|
90
|
+
context = get(this, 'context');
|
91
|
+
|
92
|
+
// releases action helpers in contents
|
93
|
+
// this means though that the ListViewItem itself can't use classBindings or attributeBindings
|
94
|
+
// need support for rerender contents in ember
|
95
|
+
this.triggerRecursively('willClearRender');
|
96
|
+
|
97
|
+
if (this.lengthAfterRender > this.lengthBeforeRender) {
|
98
|
+
this.clearRenderedChildren();
|
99
|
+
this._childViews.length = this.lengthBeforeRender; // triage bug in ember
|
100
|
+
}
|
101
|
+
|
102
|
+
if (context) {
|
103
|
+
buffer = Ember.RenderBuffer();
|
104
|
+
buffer = this.renderToBuffer(buffer);
|
105
|
+
|
106
|
+
// check again for childViews, since rendering may have added some
|
107
|
+
hasChildViews = this._childViews.length > 0;
|
108
|
+
|
109
|
+
if (hasChildViews) {
|
110
|
+
this.invokeRecursively(willInsertElementIfNeeded, false);
|
111
|
+
}
|
112
|
+
|
113
|
+
element.innerHTML = buffer.innerString ? buffer.innerString() : backportedInnerString(buffer);
|
114
|
+
|
115
|
+
set(this, 'element', element);
|
116
|
+
|
117
|
+
this.transitionTo('inDOM');
|
118
|
+
|
119
|
+
if (hasChildViews) {
|
120
|
+
this.invokeRecursively(didInsertElementIfNeeded, false);
|
121
|
+
}
|
122
|
+
} else {
|
123
|
+
element.innerHTML = ''; // when there is no context, this view should be completely empty
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
The `Ember.ListViewItem` view class renders a
|
129
|
+
[div](https://developer.mozilla.org/en/HTML/Element/div) HTML element
|
130
|
+
with `ember-list-item-view` class. It allows you to specify a custom item
|
131
|
+
handlebars template for `Ember.ListView`.
|
132
|
+
|
133
|
+
Example:
|
134
|
+
|
135
|
+
```handlebars
|
136
|
+
<script type="text/x-handlebars" data-template-name="row_item">
|
137
|
+
{{name}}
|
138
|
+
</script>
|
139
|
+
```
|
140
|
+
|
141
|
+
```javascript
|
142
|
+
App.ListView = Ember.ListView.extend({
|
143
|
+
height: 500,
|
144
|
+
rowHeight: 20,
|
145
|
+
itemViewClass: Ember.ListItemView.extend({templateName: "row_item"})
|
146
|
+
});
|
147
|
+
```
|
148
|
+
|
149
|
+
@extends Ember.View
|
150
|
+
@class ListItemView
|
151
|
+
@namespace Ember
|
152
|
+
*/
|
153
|
+
Ember.ListItemView = Ember.View.extend(Ember.ListItemViewMixin, {
|
154
|
+
updateContext: function(newContext){
|
155
|
+
var context = get(this, 'context');
|
156
|
+
Ember.instrument('view.updateContext.render', this, function() {
|
157
|
+
if (context !== newContext) {
|
158
|
+
set(this, 'context', newContext);
|
159
|
+
if (newContext && newContext.isController) {
|
160
|
+
set(this, 'controller', newContext);
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}, this);
|
164
|
+
},
|
165
|
+
rerender: function () { Ember.run.scheduleOnce('render', this, rerender); },
|
166
|
+
_contextDidChange: Ember.observer(rerender, 'context', 'controller')
|
167
|
+
});
|
168
|
+
|
169
|
+
})();
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
(function() {
|
174
|
+
var get = Ember.get, set = Ember.set;
|
175
|
+
|
176
|
+
Ember.ReusableListItemView = Ember.View.extend(Ember.ListItemViewMixin, {
|
177
|
+
init: function(){
|
178
|
+
this._super();
|
179
|
+
var context = Ember.ObjectProxy.create();
|
180
|
+
this.set('context', context);
|
181
|
+
this._proxyContext = context;
|
182
|
+
},
|
183
|
+
isVisible: Ember.computed('context.content', function(){
|
184
|
+
return !!this.get('context.content');
|
185
|
+
}),
|
186
|
+
updateContext: function(newContext){
|
187
|
+
var context = get(this._proxyContext, 'content');
|
188
|
+
if (context !== newContext) {
|
189
|
+
if (this.state === 'inDOM') {
|
190
|
+
this.prepareForReuse(newContext);
|
191
|
+
}
|
192
|
+
|
193
|
+
set(this._proxyContext, 'content', newContext);
|
194
|
+
|
195
|
+
if (newContext && newContext.isController) {
|
196
|
+
set(this, 'controller', newContext);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
},
|
200
|
+
prepareForReuse: Ember.K
|
201
|
+
});
|
202
|
+
|
203
|
+
})();
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
(function() {
|
208
|
+
var el = document.createElement('div'), style = el.style;
|
209
|
+
|
210
|
+
var propPrefixes = ['Webkit', 'Moz', 'O', 'ms'];
|
211
|
+
|
212
|
+
function testProp(prop) {
|
213
|
+
if (prop in style) return prop;
|
214
|
+
var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1);
|
215
|
+
for (var i=0; i<propPrefixes.length; i++) {
|
216
|
+
var prefixedProp = propPrefixes[i] + uppercaseProp;
|
217
|
+
if (prefixedProp in style) {
|
218
|
+
return prefixedProp;
|
219
|
+
}
|
220
|
+
}
|
221
|
+
return null;
|
222
|
+
}
|
223
|
+
|
224
|
+
var transformProp = testProp('transform');
|
225
|
+
var perspectiveProp = testProp('perspective');
|
226
|
+
|
227
|
+
var supports2D = transformProp !== null;
|
228
|
+
var supports3D = perspectiveProp !== null;
|
229
|
+
|
230
|
+
Ember.ListViewHelper = {
|
231
|
+
transformProp: transformProp,
|
232
|
+
applyTransform: (function(){
|
233
|
+
if (supports2D) {
|
234
|
+
return function(element, x, y){
|
235
|
+
element.style[transformProp] = 'translate(' + x + 'px, ' + y + 'px)';
|
236
|
+
};
|
237
|
+
} else {
|
238
|
+
return function(element, x, y){
|
239
|
+
element.style.top = y + 'px';
|
240
|
+
element.style.left = x + 'px';
|
241
|
+
};
|
242
|
+
}
|
243
|
+
})(),
|
244
|
+
apply3DTransform: (function(){
|
245
|
+
if (supports3D) {
|
246
|
+
return function(element, x, y){
|
247
|
+
element.style[transformProp] = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
|
248
|
+
};
|
249
|
+
} else if (supports2D) {
|
250
|
+
return function(element, x, y){
|
251
|
+
element.style[transformProp] = 'translate(' + x + 'px, ' + y + 'px)';
|
252
|
+
};
|
253
|
+
} else {
|
254
|
+
return function(element, x, y){
|
255
|
+
element.style.top = y + 'px';
|
256
|
+
element.style.left = x + 'px';
|
257
|
+
};
|
258
|
+
}
|
259
|
+
})()
|
260
|
+
};
|
261
|
+
|
262
|
+
})();
|
263
|
+
|
264
|
+
|
265
|
+
|
266
|
+
(function() {
|
267
|
+
var get = Ember.get, set = Ember.set,
|
268
|
+
min = Math.min, max = Math.max, floor = Math.floor,
|
269
|
+
ceil = Math.ceil,
|
270
|
+
forEach = Ember.ArrayPolyfills.forEach;
|
271
|
+
|
272
|
+
function addContentArrayObserver() {
|
273
|
+
var content = get(this, 'content');
|
274
|
+
if (content) {
|
275
|
+
content.addArrayObserver(this);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
function removeAndDestroy(object){
|
280
|
+
this.removeObject(object);
|
281
|
+
object.destroy();
|
282
|
+
}
|
283
|
+
|
284
|
+
function syncChildViews(){
|
285
|
+
Ember.run.once(this, '_syncChildViews');
|
286
|
+
}
|
287
|
+
|
288
|
+
function sortByContentIndex (viewOne, viewTwo){
|
289
|
+
return get(viewOne, 'contentIndex') - get(viewTwo, 'contentIndex');
|
290
|
+
}
|
291
|
+
|
292
|
+
function notifyMutationListeners() {
|
293
|
+
if (Ember.View.notifyMutationListeners) {
|
294
|
+
Ember.run.once(Ember.View, 'notifyMutationListeners');
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
298
|
+
var domManager = Ember.create(Ember.ContainerView.proto().domManager);
|
299
|
+
|
300
|
+
domManager.prepend = function(view, html) {
|
301
|
+
view.$('.ember-list-container').prepend(html);
|
302
|
+
notifyMutationListeners();
|
303
|
+
};
|
304
|
+
|
305
|
+
function syncListContainerWidth(){
|
306
|
+
var elementWidth, columnCount, containerWidth, element;
|
307
|
+
|
308
|
+
elementWidth = get(this, 'elementWidth');
|
309
|
+
columnCount = get(this, 'columnCount');
|
310
|
+
containerWidth = elementWidth * columnCount;
|
311
|
+
element = this.$('.ember-list-container');
|
312
|
+
|
313
|
+
if (containerWidth && element) {
|
314
|
+
element.css('width', containerWidth);
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
function enableProfilingOutput() {
|
319
|
+
function before(name, time, payload) {
|
320
|
+
console.time(name);
|
321
|
+
}
|
322
|
+
|
323
|
+
function after (name, time, payload) {
|
324
|
+
console.timeEnd(name);
|
325
|
+
}
|
326
|
+
|
327
|
+
if (Ember.ENABLE_PROFILING) {
|
328
|
+
Ember.subscribe('view._scrollContentTo', {
|
329
|
+
before: before,
|
330
|
+
after: after
|
331
|
+
});
|
332
|
+
Ember.subscribe('view.updateContext', {
|
333
|
+
before: before,
|
334
|
+
after: after
|
335
|
+
});
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
339
|
+
/**
|
340
|
+
@class Ember.ListViewMixin
|
341
|
+
@namespace Ember
|
342
|
+
*/
|
343
|
+
Ember.ListViewMixin = Ember.Mixin.create({
|
344
|
+
itemViewClass: Ember.ReusableListItemView,
|
345
|
+
emptyViewClass: Ember.View,
|
346
|
+
classNames: ['ember-list-view'],
|
347
|
+
attributeBindings: ['style'],
|
348
|
+
domManager: domManager,
|
349
|
+
scrollTop: 0,
|
350
|
+
bottomPadding: 0,
|
351
|
+
_lastEndingIndex: 0,
|
352
|
+
paddingCount: 1,
|
353
|
+
|
354
|
+
/**
|
355
|
+
@private
|
356
|
+
|
357
|
+
Setup a mixin.
|
358
|
+
- adding observer to content array
|
359
|
+
- creating child views based on height and length of the content array
|
360
|
+
|
361
|
+
@method init
|
362
|
+
*/
|
363
|
+
init: function() {
|
364
|
+
this._super();
|
365
|
+
this.on('didInsertElement', syncListContainerWidth);
|
366
|
+
this.columnCountDidChange();
|
367
|
+
this._syncChildViews();
|
368
|
+
this._addContentArrayObserver();
|
369
|
+
},
|
370
|
+
|
371
|
+
_addContentArrayObserver: Ember.beforeObserver(function() {
|
372
|
+
addContentArrayObserver.call(this);
|
373
|
+
}, 'content'),
|
374
|
+
|
375
|
+
/**
|
376
|
+
Called on your view when it should push strings of HTML into a
|
377
|
+
`Ember.RenderBuffer`.
|
378
|
+
|
379
|
+
Adds a [div](https://developer.mozilla.org/en-US/docs/HTML/Element/div)
|
380
|
+
with a required `ember-list-container` class.
|
381
|
+
|
382
|
+
@method render
|
383
|
+
@param {Ember.RenderBuffer} buffer The render buffer
|
384
|
+
*/
|
385
|
+
render: function(buffer) {
|
386
|
+
buffer.push('<div class="ember-list-container">');
|
387
|
+
this._super(buffer);
|
388
|
+
buffer.push('</div>');
|
389
|
+
},
|
390
|
+
|
391
|
+
willInsertElement: function() {
|
392
|
+
if (!this.get("height") || !this.get("rowHeight")) {
|
393
|
+
throw new Error("A ListView must be created with a height and a rowHeight.");
|
394
|
+
}
|
395
|
+
this._super();
|
396
|
+
},
|
397
|
+
|
398
|
+
/**
|
399
|
+
@private
|
400
|
+
|
401
|
+
Sets inline styles of the view:
|
402
|
+
- height
|
403
|
+
- width
|
404
|
+
- position
|
405
|
+
- overflow
|
406
|
+
- -webkit-overflow
|
407
|
+
- overflow-scrolling
|
408
|
+
|
409
|
+
Called while attributes binding.
|
410
|
+
|
411
|
+
@property {Ember.ComputedProperty} style
|
412
|
+
*/
|
413
|
+
style: Ember.computed('height', 'width', function() {
|
414
|
+
var height, width, style, css;
|
415
|
+
|
416
|
+
height = get(this, 'height');
|
417
|
+
width = get(this, 'width');
|
418
|
+
css = get(this, 'css');
|
419
|
+
|
420
|
+
style = '';
|
421
|
+
|
422
|
+
if (height) { style += 'height:' + height + 'px;'; }
|
423
|
+
if (width) { style += 'width:' + width + 'px;'; }
|
424
|
+
|
425
|
+
for ( var rule in css ){
|
426
|
+
if (css.hasOwnProperty(rule)) {
|
427
|
+
style += rule + ':' + css[rule] + ';';
|
428
|
+
}
|
429
|
+
}
|
430
|
+
|
431
|
+
return style;
|
432
|
+
}),
|
433
|
+
|
434
|
+
/**
|
435
|
+
@private
|
436
|
+
|
437
|
+
Performs visual scrolling. Is overridden in Ember.ListView.
|
438
|
+
|
439
|
+
@method scrollTo
|
440
|
+
*/
|
441
|
+
scrollTo: function(y) {
|
442
|
+
throw new Error('must override to perform the visual scroll and effectively delegate to _scrollContentTo');
|
443
|
+
},
|
444
|
+
|
445
|
+
/**
|
446
|
+
@private
|
447
|
+
|
448
|
+
Internal method used to force scroll position
|
449
|
+
|
450
|
+
@method scrollTo
|
451
|
+
*/
|
452
|
+
_scrollTo: Ember.K,
|
453
|
+
|
454
|
+
/**
|
455
|
+
@private
|
456
|
+
@method _scrollContentTo
|
457
|
+
*/
|
458
|
+
_scrollContentTo: function(y) {
|
459
|
+
var startingIndex, endingIndex,
|
460
|
+
contentIndex, visibleEndingIndex, maxContentIndex,
|
461
|
+
contentIndexEnd, contentLength, scrollTop, content;
|
462
|
+
|
463
|
+
scrollTop = max(0, y);
|
464
|
+
|
465
|
+
if (this.scrollTop === scrollTop) {
|
466
|
+
return;
|
467
|
+
}
|
468
|
+
|
469
|
+
// allow a visual overscroll, but don't scroll the content. As we are doing needless
|
470
|
+
// recycyling, and adding unexpected nodes to the DOM.
|
471
|
+
var maxScrollTop = max(0, get(this, 'totalHeight') - get(this, 'height'));
|
472
|
+
scrollTop = min(scrollTop, maxScrollTop);
|
473
|
+
|
474
|
+
content = get(this, 'content');
|
475
|
+
contentLength = get(content, 'length');
|
476
|
+
startingIndex = this._startingIndex(contentLength);
|
477
|
+
|
478
|
+
Ember.instrument('view._scrollContentTo', {
|
479
|
+
scrollTop: scrollTop,
|
480
|
+
content: content,
|
481
|
+
startingIndex: startingIndex,
|
482
|
+
endingIndex: min(max(contentLength - 1, 0), startingIndex + this._numChildViewsForViewport())
|
483
|
+
}, function () {
|
484
|
+
this.scrollTop = scrollTop;
|
485
|
+
|
486
|
+
maxContentIndex = max(contentLength - 1, 0);
|
487
|
+
|
488
|
+
startingIndex = this._startingIndex();
|
489
|
+
visibleEndingIndex = startingIndex + this._numChildViewsForViewport();
|
490
|
+
|
491
|
+
endingIndex = min(maxContentIndex, visibleEndingIndex);
|
492
|
+
|
493
|
+
if (startingIndex === this._lastStartingIndex &&
|
494
|
+
endingIndex === this._lastEndingIndex) {
|
495
|
+
|
496
|
+
this.trigger('scrollYChanged', y);
|
497
|
+
return;
|
498
|
+
} else {
|
499
|
+
|
500
|
+
Ember.run(this, function(){
|
501
|
+
this._reuseChildren();
|
502
|
+
|
503
|
+
this._lastStartingIndex = startingIndex;
|
504
|
+
this._lastEndingIndex = endingIndex;
|
505
|
+
this.trigger('scrollYChanged', y);
|
506
|
+
});
|
507
|
+
}
|
508
|
+
}, this);
|
509
|
+
|
510
|
+
},
|
511
|
+
|
512
|
+
/**
|
513
|
+
@private
|
514
|
+
|
515
|
+
Computes the height for a `Ember.ListView` scrollable container div.
|
516
|
+
You must specify `rowHeight` parameter for the height to be computed properly.
|
517
|
+
|
518
|
+
@property {Ember.ComputedProperty} totalHeight
|
519
|
+
*/
|
520
|
+
totalHeight: Ember.computed('content.length', 'rowHeight', 'columnCount', 'bottomPadding', function() {
|
521
|
+
var contentLength, rowHeight, columnCount, bottomPadding;
|
522
|
+
|
523
|
+
contentLength = get(this, 'content.length');
|
524
|
+
rowHeight = get(this, 'rowHeight');
|
525
|
+
columnCount = get(this, 'columnCount');
|
526
|
+
bottomPadding = get(this, 'bottomPadding');
|
527
|
+
|
528
|
+
return ((ceil(contentLength / columnCount)) * rowHeight) + bottomPadding;
|
529
|
+
}),
|
530
|
+
|
531
|
+
/**
|
532
|
+
@private
|
533
|
+
@method _prepareChildForReuse
|
534
|
+
*/
|
535
|
+
_prepareChildForReuse: function(childView) {
|
536
|
+
childView.prepareForReuse();
|
537
|
+
},
|
538
|
+
|
539
|
+
/**
|
540
|
+
@private
|
541
|
+
@method _reuseChildForContentIndex
|
542
|
+
*/
|
543
|
+
_reuseChildForContentIndex: function(childView, contentIndex) {
|
544
|
+
var content, context, newContext, childsCurrentContentIndex, position, enableProfiling;
|
545
|
+
|
546
|
+
content = get(this, 'content');
|
547
|
+
enableProfiling = get(this, 'enableProfiling');
|
548
|
+
position = this.positionForIndex(contentIndex);
|
549
|
+
childView.updatePosition(position);
|
550
|
+
|
551
|
+
set(childView, 'contentIndex', contentIndex);
|
552
|
+
|
553
|
+
if (enableProfiling) {
|
554
|
+
Ember.instrument('view._reuseChildForContentIndex', position, function(){}, this);
|
555
|
+
}
|
556
|
+
|
557
|
+
newContext = content.objectAt(contentIndex);
|
558
|
+
childView.updateContext(newContext);
|
559
|
+
},
|
560
|
+
|
561
|
+
/**
|
562
|
+
@private
|
563
|
+
@method positionForIndex
|
564
|
+
*/
|
565
|
+
positionForIndex: function(index){
|
566
|
+
var elementWidth, width, columnCount, rowHeight, y, x;
|
567
|
+
|
568
|
+
elementWidth = get(this, 'elementWidth') || 1;
|
569
|
+
width = get(this, 'width') || 1;
|
570
|
+
columnCount = get(this, 'columnCount');
|
571
|
+
rowHeight = get(this, 'rowHeight');
|
572
|
+
|
573
|
+
y = (rowHeight * floor(index/columnCount));
|
574
|
+
x = (index % columnCount) * elementWidth;
|
575
|
+
|
576
|
+
return {
|
577
|
+
y: y,
|
578
|
+
x: x
|
579
|
+
};
|
580
|
+
},
|
581
|
+
|
582
|
+
/**
|
583
|
+
@private
|
584
|
+
@method _childViewCount
|
585
|
+
*/
|
586
|
+
_childViewCount: function() {
|
587
|
+
var contentLength, childViewCountForHeight;
|
588
|
+
|
589
|
+
contentLength = get(this, 'content.length');
|
590
|
+
childViewCountForHeight = this._numChildViewsForViewport();
|
591
|
+
|
592
|
+
return min(contentLength, childViewCountForHeight);
|
593
|
+
},
|
594
|
+
|
595
|
+
/**
|
596
|
+
@private
|
597
|
+
|
598
|
+
Returns a number of columns in the Ember.ListView (for grid layout).
|
599
|
+
|
600
|
+
If you want to have a multi column layout, you need to specify both
|
601
|
+
`width` and `elementWidth`.
|
602
|
+
|
603
|
+
If no `elementWidth` is specified, it returns `1`. Otherwise, it will
|
604
|
+
try to fit as many columns as possible for a given `width`.
|
605
|
+
|
606
|
+
@property {Ember.ComputedProperty} columnCount
|
607
|
+
*/
|
608
|
+
columnCount: Ember.computed('width', 'elementWidth', function() {
|
609
|
+
var elementWidth, width, count;
|
610
|
+
|
611
|
+
elementWidth = get(this, 'elementWidth');
|
612
|
+
width = get(this, 'width');
|
613
|
+
|
614
|
+
if (elementWidth) {
|
615
|
+
count = floor(width / elementWidth);
|
616
|
+
} else {
|
617
|
+
count = 1;
|
618
|
+
}
|
619
|
+
|
620
|
+
return count;
|
621
|
+
}),
|
622
|
+
|
623
|
+
/**
|
624
|
+
@private
|
625
|
+
|
626
|
+
Fires every time column count is changed.
|
627
|
+
|
628
|
+
@event columnCountDidChange
|
629
|
+
*/
|
630
|
+
columnCountDidChange: Ember.observer(function(){
|
631
|
+
var ratio, currentScrollTop, proposedScrollTop, maxScrollTop,
|
632
|
+
scrollTop, lastColumnCount, newColumnCount, element;
|
633
|
+
|
634
|
+
lastColumnCount = this._lastColumnCount;
|
635
|
+
|
636
|
+
currentScrollTop = this.scrollTop;
|
637
|
+
newColumnCount = get(this, 'columnCount');
|
638
|
+
maxScrollTop = get(this, 'maxScrollTop');
|
639
|
+
element = get(this, 'element');
|
640
|
+
|
641
|
+
this._lastColumnCount = newColumnCount;
|
642
|
+
|
643
|
+
if (lastColumnCount) {
|
644
|
+
ratio = (lastColumnCount / newColumnCount);
|
645
|
+
proposedScrollTop = currentScrollTop * ratio;
|
646
|
+
scrollTop = min(maxScrollTop, proposedScrollTop);
|
647
|
+
|
648
|
+
this._scrollTo(scrollTop);
|
649
|
+
this.scrollTop = scrollTop;
|
650
|
+
}
|
651
|
+
|
652
|
+
if (arguments.length > 0) {
|
653
|
+
// invoked by observer
|
654
|
+
Ember.run.schedule('afterRender', this, syncListContainerWidth);
|
655
|
+
}
|
656
|
+
}, 'columnCount'),
|
657
|
+
|
658
|
+
/**
|
659
|
+
@private
|
660
|
+
|
661
|
+
Computes max possible scrollTop value given the visible viewport
|
662
|
+
and scrollable container div height.
|
663
|
+
|
664
|
+
@property {Ember.ComputedProperty} maxScrollTop
|
665
|
+
*/
|
666
|
+
maxScrollTop: Ember.computed('height', 'totalHeight', function(){
|
667
|
+
var totalHeight, viewportHeight;
|
668
|
+
|
669
|
+
totalHeight = get(this, 'totalHeight');
|
670
|
+
viewportHeight = get(this, 'height');
|
671
|
+
|
672
|
+
return max(0, totalHeight - viewportHeight);
|
673
|
+
}),
|
674
|
+
|
675
|
+
/**
|
676
|
+
@private
|
677
|
+
|
678
|
+
Computes the number of views that would fit in the viewport area.
|
679
|
+
You must specify `height` and `rowHeight` parameters for the number of
|
680
|
+
views to be computed properly.
|
681
|
+
|
682
|
+
@method _numChildViewsForViewport
|
683
|
+
*/
|
684
|
+
_numChildViewsForViewport: function() {
|
685
|
+
var height, rowHeight, paddingCount, columnCount;
|
686
|
+
|
687
|
+
height = get(this, 'height');
|
688
|
+
rowHeight = get(this, 'rowHeight');
|
689
|
+
paddingCount = get(this, 'paddingCount');
|
690
|
+
columnCount = get(this, 'columnCount');
|
691
|
+
|
692
|
+
return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount);
|
693
|
+
},
|
694
|
+
|
695
|
+
/**
|
696
|
+
@private
|
697
|
+
|
698
|
+
Computes the starting index of the item views array.
|
699
|
+
Takes `scrollTop` property of the element into account.
|
700
|
+
|
701
|
+
Is used in `_syncChildViews`.
|
702
|
+
|
703
|
+
@method _startingIndex
|
704
|
+
*/
|
705
|
+
_startingIndex: function(_contentLength) {
|
706
|
+
var scrollTop, rowHeight, columnCount, calculatedStartingIndex,
|
707
|
+
contentLength, largestStartingIndex;
|
708
|
+
|
709
|
+
if (_contentLength === undefined) {
|
710
|
+
contentLength = get(this, 'content.length');
|
711
|
+
} else {
|
712
|
+
contentLength = _contentLength;
|
713
|
+
}
|
714
|
+
|
715
|
+
scrollTop = this.scrollTop;
|
716
|
+
rowHeight = get(this, 'rowHeight');
|
717
|
+
columnCount = get(this, 'columnCount');
|
718
|
+
|
719
|
+
calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount;
|
720
|
+
|
721
|
+
largestStartingIndex = max(contentLength - 1, 0);
|
722
|
+
|
723
|
+
return min(calculatedStartingIndex, largestStartingIndex);
|
724
|
+
},
|
725
|
+
|
726
|
+
/**
|
727
|
+
@private
|
728
|
+
@event contentWillChange
|
729
|
+
*/
|
730
|
+
contentWillChange: Ember.beforeObserver(function() {
|
731
|
+
var content;
|
732
|
+
|
733
|
+
content = get(this, 'content');
|
734
|
+
|
735
|
+
if (content) {
|
736
|
+
content.removeArrayObserver(this);
|
737
|
+
}
|
738
|
+
}, 'content'),
|
739
|
+
|
740
|
+
/**),
|
741
|
+
@private
|
742
|
+
@event contentDidChange
|
743
|
+
*/
|
744
|
+
contentDidChange: Ember.observer(function() {
|
745
|
+
addContentArrayObserver.call(this);
|
746
|
+
syncChildViews.call(this);
|
747
|
+
}, 'content'),
|
748
|
+
|
749
|
+
/**
|
750
|
+
@private
|
751
|
+
@property {Function} needsSyncChildViews
|
752
|
+
*/
|
753
|
+
needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'),
|
754
|
+
|
755
|
+
/**
|
756
|
+
@private
|
757
|
+
|
758
|
+
Returns a new item view. Takes `contentIndex` to set the context
|
759
|
+
of the returned view properly.
|
760
|
+
|
761
|
+
@param {Number} contentIndex item index in the content array
|
762
|
+
@method _addItemView
|
763
|
+
*/
|
764
|
+
_addItemView: function(contentIndex){
|
765
|
+
var itemViewClass, childView;
|
766
|
+
|
767
|
+
itemViewClass = get(this, 'itemViewClass');
|
768
|
+
childView = this.createChildView(itemViewClass);
|
769
|
+
|
770
|
+
this.pushObject(childView);
|
771
|
+
},
|
772
|
+
|
773
|
+
/**
|
774
|
+
@private
|
775
|
+
|
776
|
+
Intelligently manages the number of childviews.
|
777
|
+
|
778
|
+
@method _syncChildViews
|
779
|
+
**/
|
780
|
+
_syncChildViews: function(){
|
781
|
+
var itemViewClass, startingIndex, childViewCount,
|
782
|
+
endingIndex, numberOfChildViews, numberOfChildViewsNeeded,
|
783
|
+
childViews, count, delta, index, childViewsLength, contentIndex;
|
784
|
+
|
785
|
+
if (get(this, 'isDestroyed') || get(this, 'isDestroying')) {
|
786
|
+
return;
|
787
|
+
}
|
788
|
+
|
789
|
+
childViewCount = this._childViewCount();
|
790
|
+
childViews = this.positionOrderedChildViews();
|
791
|
+
|
792
|
+
startingIndex = this._startingIndex();
|
793
|
+
endingIndex = startingIndex + childViewCount;
|
794
|
+
|
795
|
+
numberOfChildViewsNeeded = childViewCount;
|
796
|
+
numberOfChildViews = childViews.length;
|
797
|
+
|
798
|
+
delta = numberOfChildViewsNeeded - numberOfChildViews;
|
799
|
+
|
800
|
+
if (delta === 0) {
|
801
|
+
// no change
|
802
|
+
} else if (delta > 0) {
|
803
|
+
// more views are needed
|
804
|
+
contentIndex = this._lastEndingIndex;
|
805
|
+
|
806
|
+
for (count = 0; count < delta; count++, contentIndex++) {
|
807
|
+
this._addItemView(contentIndex);
|
808
|
+
}
|
809
|
+
|
810
|
+
} else {
|
811
|
+
// less views are needed
|
812
|
+
forEach.call(
|
813
|
+
childViews.splice(numberOfChildViewsNeeded, numberOfChildViews),
|
814
|
+
removeAndDestroy,
|
815
|
+
this
|
816
|
+
);
|
817
|
+
}
|
818
|
+
|
819
|
+
this._reuseChildren();
|
820
|
+
|
821
|
+
this._lastStartingIndex = startingIndex;
|
822
|
+
this._lastEndingIndex = this._lastEndingIndex + delta;
|
823
|
+
},
|
824
|
+
|
825
|
+
/**
|
826
|
+
@private
|
827
|
+
@method _reuseChildren
|
828
|
+
*/
|
829
|
+
_reuseChildren: function(){
|
830
|
+
var contentLength, childViews, childViewsLength,
|
831
|
+
startingIndex, endingIndex, childView, attrs,
|
832
|
+
contentIndex, visibleEndingIndex, maxContentIndex,
|
833
|
+
contentIndexEnd, scrollTop;
|
834
|
+
|
835
|
+
scrollTop = this.scrollTop;
|
836
|
+
contentLength = get(this, 'content.length');
|
837
|
+
maxContentIndex = max(contentLength - 1, 0);
|
838
|
+
childViews = this.getReusableChildViews();
|
839
|
+
childViewsLength = childViews.length;
|
840
|
+
|
841
|
+
startingIndex = this._startingIndex();
|
842
|
+
visibleEndingIndex = startingIndex + this._numChildViewsForViewport();
|
843
|
+
|
844
|
+
endingIndex = min(maxContentIndex, visibleEndingIndex);
|
845
|
+
|
846
|
+
contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength);
|
847
|
+
|
848
|
+
for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) {
|
849
|
+
childView = childViews[contentIndex % childViewsLength];
|
850
|
+
this._reuseChildForContentIndex(childView, contentIndex);
|
851
|
+
}
|
852
|
+
},
|
853
|
+
|
854
|
+
/**
|
855
|
+
@private
|
856
|
+
@method getReusableChildViews
|
857
|
+
*/
|
858
|
+
getReusableChildViews: function() {
|
859
|
+
return this._childViews;
|
860
|
+
},
|
861
|
+
|
862
|
+
/**
|
863
|
+
@private
|
864
|
+
@method positionOrderedChildViews
|
865
|
+
*/
|
866
|
+
positionOrderedChildViews: function() {
|
867
|
+
return this.getReusableChildViews().sort(sortByContentIndex);
|
868
|
+
},
|
869
|
+
|
870
|
+
arrayWillChange: Ember.K,
|
871
|
+
|
872
|
+
/**
|
873
|
+
@private
|
874
|
+
@event arrayDidChange
|
875
|
+
*/
|
876
|
+
// TODO: refactor
|
877
|
+
arrayDidChange: function(content, start, removedCount, addedCount) {
|
878
|
+
var index, contentIndex;
|
879
|
+
|
880
|
+
if (this.state === 'inDOM') {
|
881
|
+
// ignore if all changes are out of the visible change
|
882
|
+
if( start >= this._lastStartingIndex || start < this._lastEndingIndex) {
|
883
|
+
index = 0;
|
884
|
+
// ignore all changes not in the visible range
|
885
|
+
// this can re-position many, rather then causing a cascade of re-renders
|
886
|
+
forEach.call(
|
887
|
+
this.positionOrderedChildViews(),
|
888
|
+
function(childView) {
|
889
|
+
contentIndex = this._lastStartingIndex + index;
|
890
|
+
this._reuseChildForContentIndex(childView, contentIndex);
|
891
|
+
index++;
|
892
|
+
},
|
893
|
+
this
|
894
|
+
);
|
895
|
+
}
|
896
|
+
|
897
|
+
syncChildViews.call(this);
|
898
|
+
}
|
899
|
+
}
|
900
|
+
});
|
901
|
+
|
902
|
+
})();
|
903
|
+
|
904
|
+
|
905
|
+
|
906
|
+
(function() {
|
907
|
+
var get = Ember.get, set = Ember.set;
|
908
|
+
|
909
|
+
/**
|
910
|
+
The `Ember.ListView` view class renders a
|
911
|
+
[div](https://developer.mozilla.org/en/HTML/Element/div) HTML element,
|
912
|
+
with `ember-list-view` class.
|
913
|
+
|
914
|
+
The context of each item element within the `Ember.ListView` are populated
|
915
|
+
from the objects in the `Element.ListView`'s `content` property.
|
916
|
+
|
917
|
+
### `content` as an Array of Objects
|
918
|
+
|
919
|
+
The simplest version of an `Ember.ListView` takes an array of object as its
|
920
|
+
`content` property. The object will be used as the `context` each item element
|
921
|
+
inside the rendered `div`.
|
922
|
+
|
923
|
+
Example:
|
924
|
+
|
925
|
+
```javascript
|
926
|
+
App.ContributorsRoute = Ember.Route.extend({
|
927
|
+
model: function() {
|
928
|
+
return [{ name: 'Stefan Penner' }, { name: 'Alex Navasardyan' }, { name: 'Ray Cohen'}];
|
929
|
+
}
|
930
|
+
});
|
931
|
+
```
|
932
|
+
|
933
|
+
```handlebars
|
934
|
+
{{#ember-list items=contributors height=500 rowHeight=50}}
|
935
|
+
{{name}}
|
936
|
+
{{/ember-list}}
|
937
|
+
```
|
938
|
+
|
939
|
+
Would result in the following HTML:
|
940
|
+
|
941
|
+
```html
|
942
|
+
<div id="ember181" class="ember-view ember-list-view" style="height:500px;width:500px;position:relative;overflow:scroll;-webkit-overflow-scrolling:touch;overflow-scrolling:touch;">
|
943
|
+
<div class="ember-list-container">
|
944
|
+
<div id="ember186" class="ember-view ember-list-item-view" style="-webkit-transform: translate3d(0px, 0px, 0);">
|
945
|
+
<script id="metamorph-0-start" type="text/x-placeholder"></script>Stefan Penner<script id="metamorph-0-end" type="text/x-placeholder"></script>
|
946
|
+
</div>
|
947
|
+
<div id="ember187" class="ember-view ember-list-item-view" style="-webkit-transform: translate3d(0px, 50px, 0);">
|
948
|
+
<script id="metamorph-1-start" type="text/x-placeholder"></script>Alex Navasardyan<script id="metamorph-1-end" type="text/x-placeholder"></script>
|
949
|
+
</div>
|
950
|
+
<div id="ember188" class="ember-view ember-list-item-view" style="-webkit-transform: translate3d(0px, 100px, 0);">
|
951
|
+
<script id="metamorph-2-start" type="text/x-placeholder"></script>Rey Cohen<script id="metamorph-2-end" type="text/x-placeholder"></script>
|
952
|
+
</div>
|
953
|
+
<div id="ember189" class="ember-view ember-list-scrolling-view" style="height: 150px"></div>
|
954
|
+
</div>
|
955
|
+
</div>
|
956
|
+
```
|
957
|
+
|
958
|
+
By default `Ember.ListView` provides support for `height`,
|
959
|
+
`rowHeight`, `width`, `elementWidth`, `scrollTop` parameters.
|
960
|
+
|
961
|
+
Note, that `height` and `rowHeight` are required parameters.
|
962
|
+
|
963
|
+
```handlebars
|
964
|
+
{{#ember-list items=this height=500 rowHeight=50}}
|
965
|
+
{{name}}
|
966
|
+
{{/ember-list}}
|
967
|
+
```
|
968
|
+
|
969
|
+
If you would like to have multiple columns in your view layout, you can
|
970
|
+
set `width` and `elementWidth` parameters respectively.
|
971
|
+
|
972
|
+
```handlebars
|
973
|
+
{{#ember-list items=this height=500 rowHeight=50 width=500 elementWidth=80}}
|
974
|
+
{{name}}
|
975
|
+
{{/ember-list}}
|
976
|
+
```
|
977
|
+
|
978
|
+
### extending `Ember.ListView`
|
979
|
+
|
980
|
+
Example:
|
981
|
+
|
982
|
+
```handlebars
|
983
|
+
{{view App.ListView contentBinding="content"}}
|
984
|
+
|
985
|
+
<script type="text/x-handlebars" data-template-name="row_item">
|
986
|
+
{{name}}
|
987
|
+
</script>
|
988
|
+
```
|
989
|
+
|
990
|
+
```javascript
|
991
|
+
App.ListView = Ember.ListView.extend({
|
992
|
+
height: 500,
|
993
|
+
width: 500,
|
994
|
+
elementWidth: 80,
|
995
|
+
rowHeight: 20,
|
996
|
+
itemViewClass: Ember.ListItemView.extend({templateName: "row_item"})
|
997
|
+
});
|
998
|
+
```
|
999
|
+
|
1000
|
+
@extends Ember.ContainerView
|
1001
|
+
@class ListView
|
1002
|
+
@namespace Ember
|
1003
|
+
*/
|
1004
|
+
Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
|
1005
|
+
css: {
|
1006
|
+
position: 'relative',
|
1007
|
+
overflow: 'scroll',
|
1008
|
+
'-webkit-overflow-scrolling': 'touch',
|
1009
|
+
'overflow-scrolling': 'touch'
|
1010
|
+
},
|
1011
|
+
|
1012
|
+
applyTransform: Ember.ListViewHelper.applyTransform,
|
1013
|
+
|
1014
|
+
_scrollTo: function(scrollTop) {
|
1015
|
+
var element = get(this, 'element');
|
1016
|
+
|
1017
|
+
if (element) { element.scrollTop = scrollTop; }
|
1018
|
+
},
|
1019
|
+
|
1020
|
+
didInsertElement: function() {
|
1021
|
+
var that = this,
|
1022
|
+
element = get(this, 'element');
|
1023
|
+
|
1024
|
+
this._updateScrollableHeight();
|
1025
|
+
|
1026
|
+
this._scroll = function(e) { that.scroll(e); };
|
1027
|
+
|
1028
|
+
Ember.$(element).on('scroll', this._scroll);
|
1029
|
+
},
|
1030
|
+
|
1031
|
+
willDestroyElement: function() {
|
1032
|
+
var element;
|
1033
|
+
|
1034
|
+
element = get(this, 'element');
|
1035
|
+
|
1036
|
+
Ember.$(element).off('scroll', this._scroll);
|
1037
|
+
},
|
1038
|
+
|
1039
|
+
scroll: function(e) {
|
1040
|
+
this.scrollTo(e.target.scrollTop);
|
1041
|
+
},
|
1042
|
+
|
1043
|
+
scrollTo: function(y){
|
1044
|
+
var element = get(this, 'element');
|
1045
|
+
this._scrollTo(y);
|
1046
|
+
this._scrollContentTo(y);
|
1047
|
+
},
|
1048
|
+
|
1049
|
+
totalHeightDidChange: Ember.observer(function () {
|
1050
|
+
Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight);
|
1051
|
+
}, 'totalHeight'),
|
1052
|
+
|
1053
|
+
_updateScrollableHeight: function () {
|
1054
|
+
if (this.state === 'inDOM') {
|
1055
|
+
this.$('.ember-list-container').css({
|
1056
|
+
height: get(this, 'totalHeight')
|
1057
|
+
});
|
1058
|
+
}
|
1059
|
+
}
|
1060
|
+
});
|
1061
|
+
|
1062
|
+
})();
|
1063
|
+
|
1064
|
+
|
1065
|
+
|
1066
|
+
(function() {
|
1067
|
+
var fieldRegex = /input|textarea|select/i,
|
1068
|
+
hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch,
|
1069
|
+
handleStart, handleMove, handleEnd, handleCancel,
|
1070
|
+
startEvent, moveEvent, endEvent, cancelEvent;
|
1071
|
+
if (hasTouch) {
|
1072
|
+
startEvent = 'touchstart';
|
1073
|
+
handleStart = function (e) {
|
1074
|
+
var touch = e.touches[0],
|
1075
|
+
target = touch && touch.target;
|
1076
|
+
// avoid e.preventDefault() on fields
|
1077
|
+
if (target && fieldRegex.test(target.tagName)) {
|
1078
|
+
return;
|
1079
|
+
}
|
1080
|
+
bindWindow(this.scrollerEventHandlers);
|
1081
|
+
this.willBeginScroll(e.touches, e.timeStamp);
|
1082
|
+
e.preventDefault();
|
1083
|
+
};
|
1084
|
+
moveEvent = 'touchmove';
|
1085
|
+
handleMove = function (e) {
|
1086
|
+
this.continueScroll(e.touches, e.timeStamp);
|
1087
|
+
};
|
1088
|
+
endEvent = 'touchend';
|
1089
|
+
handleEnd = function (e) {
|
1090
|
+
// if we didn't end up scrolling we need to
|
1091
|
+
// synthesize click since we did e.preventDefault()
|
1092
|
+
// on touchstart
|
1093
|
+
if (!this._isScrolling) {
|
1094
|
+
synthesizeClick(e);
|
1095
|
+
}
|
1096
|
+
unbindWindow(this.scrollerEventHandlers);
|
1097
|
+
this.endScroll(e.timeStamp);
|
1098
|
+
};
|
1099
|
+
cancelEvent = 'touchcancel';
|
1100
|
+
handleCancel = function (e) {
|
1101
|
+
unbindWindow(this.scrollerEventHandlers);
|
1102
|
+
this.endScroll(e.timeStamp);
|
1103
|
+
};
|
1104
|
+
} else {
|
1105
|
+
startEvent = 'mousedown';
|
1106
|
+
handleStart = function (e) {
|
1107
|
+
if (e.which !== 1) return;
|
1108
|
+
var target = e.target;
|
1109
|
+
// avoid e.preventDefault() on fields
|
1110
|
+
if (target && fieldRegex.test(target.tagName)) {
|
1111
|
+
return;
|
1112
|
+
}
|
1113
|
+
bindWindow(this.scrollerEventHandlers);
|
1114
|
+
this.willBeginScroll([e], e.timeStamp);
|
1115
|
+
e.preventDefault();
|
1116
|
+
};
|
1117
|
+
moveEvent = 'mousemove';
|
1118
|
+
handleMove = function (e) {
|
1119
|
+
this.continueScroll([e], e.timeStamp);
|
1120
|
+
};
|
1121
|
+
endEvent = 'mouseup';
|
1122
|
+
handleEnd = function (e) {
|
1123
|
+
unbindWindow(this.scrollerEventHandlers);
|
1124
|
+
this.endScroll(e.timeStamp);
|
1125
|
+
};
|
1126
|
+
cancelEvent = 'mouseout';
|
1127
|
+
handleCancel = function (e) {
|
1128
|
+
if (e.relatedTarget) return;
|
1129
|
+
unbindWindow(this.scrollerEventHandlers);
|
1130
|
+
this.endScroll(e.timeStamp);
|
1131
|
+
};
|
1132
|
+
}
|
1133
|
+
|
1134
|
+
function handleWheel(e) {
|
1135
|
+
this.mouseWheel(e);
|
1136
|
+
e.preventDefault();
|
1137
|
+
}
|
1138
|
+
|
1139
|
+
function bindElement(el, handlers) {
|
1140
|
+
el.addEventListener(startEvent, handlers.start, false);
|
1141
|
+
el.addEventListener('mousewheel', handlers.wheel, false);
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
function unbindElement(el, handlers) {
|
1145
|
+
el.removeEventListener(startEvent, handlers.start, false);
|
1146
|
+
el.removeEventListener('mousewheel', handlers.wheel, false);
|
1147
|
+
}
|
1148
|
+
|
1149
|
+
function bindWindow(handlers) {
|
1150
|
+
window.addEventListener(moveEvent, handlers.move, true);
|
1151
|
+
window.addEventListener(endEvent, handlers.end, true);
|
1152
|
+
window.addEventListener(cancelEvent, handlers.cancel, true);
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
function unbindWindow(handlers) {
|
1156
|
+
window.removeEventListener(moveEvent, handlers.move, true);
|
1157
|
+
window.removeEventListener(endEvent, handlers.end, true);
|
1158
|
+
window.removeEventListener(cancelEvent, handlers.cancel, true);
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
Ember.VirtualListScrollerEvents = Ember.Mixin.create({
|
1162
|
+
init: function() {
|
1163
|
+
this.on('didInsertElement', this, 'bindScrollerEvents');
|
1164
|
+
this.on('willDestroyElement', this, 'unbindScrollerEvents');
|
1165
|
+
this.scrollerEventHandlers = {
|
1166
|
+
start: bind(this, handleStart),
|
1167
|
+
move: bind(this, handleMove),
|
1168
|
+
end: bind(this, handleEnd),
|
1169
|
+
cancel: bind(this, handleCancel),
|
1170
|
+
wheel: bind(this, handleWheel)
|
1171
|
+
};
|
1172
|
+
return this._super();
|
1173
|
+
},
|
1174
|
+
bindScrollerEvents: function() {
|
1175
|
+
var el = this.get('element'),
|
1176
|
+
handlers = this.scrollerEventHandlers;
|
1177
|
+
bindElement(el, handlers);
|
1178
|
+
},
|
1179
|
+
unbindScrollerEvents: function() {
|
1180
|
+
var el = this.get('element'),
|
1181
|
+
handlers = this.scrollerEventHandlers;
|
1182
|
+
unbindElement(el, handlers);
|
1183
|
+
unbindWindow(handlers);
|
1184
|
+
}
|
1185
|
+
});
|
1186
|
+
|
1187
|
+
function bind(view, handler) {
|
1188
|
+
return function (evt) {
|
1189
|
+
handler.call(view, evt);
|
1190
|
+
};
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
function synthesizeClick(e) {
|
1194
|
+
var point = e.changedTouches[0],
|
1195
|
+
target = point.target,
|
1196
|
+
ev;
|
1197
|
+
if (target && fieldRegex.test(target.tagName)) {
|
1198
|
+
ev = document.createEvent('MouseEvents');
|
1199
|
+
ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
|
1200
|
+
return target.dispatchEvent(ev);
|
1201
|
+
}
|
1202
|
+
}
|
1203
|
+
|
1204
|
+
})();
|
1205
|
+
|
1206
|
+
|
1207
|
+
|
1208
|
+
(function() {
|
1209
|
+
/*global Scroller*/
|
1210
|
+
var max = Math.max, get = Ember.get, set = Ember.set;
|
1211
|
+
|
1212
|
+
function updateScrollerDimensions(target) {
|
1213
|
+
var width, height, totalHeight;
|
1214
|
+
|
1215
|
+
target = target || this;
|
1216
|
+
|
1217
|
+
width = get(target, 'width');
|
1218
|
+
height = get(target, 'height');
|
1219
|
+
totalHeight = get(target, 'totalHeight');
|
1220
|
+
|
1221
|
+
target.scroller.setDimensions(width, height, width, totalHeight);
|
1222
|
+
target.trigger('scrollerDimensionsDidChange');
|
1223
|
+
}
|
1224
|
+
|
1225
|
+
/**
|
1226
|
+
VirtualListView
|
1227
|
+
|
1228
|
+
@class VirtualListView
|
1229
|
+
@namespace Ember
|
1230
|
+
*/
|
1231
|
+
Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, Ember.VirtualListScrollerEvents, {
|
1232
|
+
_isScrolling: false,
|
1233
|
+
_mouseWheel: null,
|
1234
|
+
css: {
|
1235
|
+
position: 'relative',
|
1236
|
+
overflow: 'hidden'
|
1237
|
+
},
|
1238
|
+
|
1239
|
+
init: function(){
|
1240
|
+
this._super();
|
1241
|
+
this.setupScroller();
|
1242
|
+
this.setupPullToRefresh();
|
1243
|
+
},
|
1244
|
+
_scrollerTop: 0,
|
1245
|
+
applyTransform: Ember.ListViewHelper.apply3DTransform,
|
1246
|
+
|
1247
|
+
setupScroller: function(){
|
1248
|
+
var view, y;
|
1249
|
+
|
1250
|
+
view = this;
|
1251
|
+
|
1252
|
+
view.scroller = new Scroller(function(left, top, zoom) {
|
1253
|
+
if (view.state !== 'inDOM') { return; }
|
1254
|
+
|
1255
|
+
if (view.listContainerElement) {
|
1256
|
+
view._scrollerTop = top;
|
1257
|
+
view._scrollContentTo(top);
|
1258
|
+
view.applyTransform(view.listContainerElement, 0, -top);
|
1259
|
+
}
|
1260
|
+
}, {
|
1261
|
+
scrollingX: false,
|
1262
|
+
scrollingComplete: function(){
|
1263
|
+
view.trigger('scrollingDidComplete');
|
1264
|
+
}
|
1265
|
+
});
|
1266
|
+
|
1267
|
+
view.trigger('didInitializeScroller');
|
1268
|
+
updateScrollerDimensions(view);
|
1269
|
+
},
|
1270
|
+
setupPullToRefresh: function() {
|
1271
|
+
if (!this.pullToRefreshViewClass) { return; }
|
1272
|
+
this._insertPullToRefreshView();
|
1273
|
+
this._activateScrollerPullToRefresh();
|
1274
|
+
},
|
1275
|
+
_insertPullToRefreshView: function(){
|
1276
|
+
this.pullToRefreshView = this.createChildView(this.pullToRefreshViewClass);
|
1277
|
+
this.insertAt(0, this.pullToRefreshView);
|
1278
|
+
var view = this;
|
1279
|
+
this.pullToRefreshView.on('didInsertElement', function(){
|
1280
|
+
Ember.run.schedule('afterRender', this, function(){
|
1281
|
+
view.applyTransform(this.get('element'), 0, -1 * view.pullToRefreshViewHeight);
|
1282
|
+
});
|
1283
|
+
});
|
1284
|
+
},
|
1285
|
+
_activateScrollerPullToRefresh: function(){
|
1286
|
+
var view = this;
|
1287
|
+
function activatePullToRefresh(){
|
1288
|
+
view.pullToRefreshView.set('active', true);
|
1289
|
+
view.trigger('activatePullToRefresh');
|
1290
|
+
}
|
1291
|
+
function deactivatePullToRefresh() {
|
1292
|
+
view.pullToRefreshView.set('active', false);
|
1293
|
+
view.trigger('deactivatePullToRefresh');
|
1294
|
+
}
|
1295
|
+
function startPullToRefresh() {
|
1296
|
+
Ember.run(function(){
|
1297
|
+
view.pullToRefreshView.set('refreshing', true);
|
1298
|
+
|
1299
|
+
function finishRefresh(){
|
1300
|
+
if (view && !view.get('isDestroyed') && !view.get('isDestroying')) {
|
1301
|
+
view.scroller.finishPullToRefresh();
|
1302
|
+
view.pullToRefreshView.set('refreshing', false);
|
1303
|
+
}
|
1304
|
+
}
|
1305
|
+
view.startRefresh(finishRefresh);
|
1306
|
+
});
|
1307
|
+
}
|
1308
|
+
this.scroller.activatePullToRefresh(
|
1309
|
+
this.pullToRefreshViewHeight,
|
1310
|
+
activatePullToRefresh,
|
1311
|
+
deactivatePullToRefresh,
|
1312
|
+
startPullToRefresh
|
1313
|
+
);
|
1314
|
+
},
|
1315
|
+
|
1316
|
+
getReusableChildViews: function(){
|
1317
|
+
var firstView = this._childViews[0];
|
1318
|
+
if (firstView && firstView === this.pullToRefreshView) {
|
1319
|
+
return this._childViews.slice(1);
|
1320
|
+
} else {
|
1321
|
+
return this._childViews;
|
1322
|
+
}
|
1323
|
+
},
|
1324
|
+
|
1325
|
+
scrollerDimensionsNeedToChange: Ember.observer(function() {
|
1326
|
+
Ember.run.once(this, updateScrollerDimensions);
|
1327
|
+
}, 'width', 'height', 'totalHeight'),
|
1328
|
+
|
1329
|
+
didInsertElement: function() {
|
1330
|
+
this.listContainerElement = this.$('> .ember-list-container')[0];
|
1331
|
+
},
|
1332
|
+
|
1333
|
+
willBeginScroll: function(touches, timeStamp) {
|
1334
|
+
this._isScrolling = false;
|
1335
|
+
this.trigger('scrollingDidStart');
|
1336
|
+
|
1337
|
+
this.scroller.doTouchStart(touches, timeStamp);
|
1338
|
+
},
|
1339
|
+
|
1340
|
+
continueScroll: function(touches, timeStamp) {
|
1341
|
+
var startingScrollTop, endingScrollTop, event;
|
1342
|
+
|
1343
|
+
if (this._isScrolling) {
|
1344
|
+
this.scroller.doTouchMove(touches, timeStamp);
|
1345
|
+
} else {
|
1346
|
+
startingScrollTop = this._scrollerTop;
|
1347
|
+
|
1348
|
+
this.scroller.doTouchMove(touches, timeStamp);
|
1349
|
+
|
1350
|
+
endingScrollTop = this._scrollerTop;
|
1351
|
+
|
1352
|
+
if (startingScrollTop !== endingScrollTop) {
|
1353
|
+
event = Ember.$.Event("scrollerstart");
|
1354
|
+
Ember.$(touches[0].target).trigger(event);
|
1355
|
+
|
1356
|
+
this._isScrolling = true;
|
1357
|
+
}
|
1358
|
+
}
|
1359
|
+
},
|
1360
|
+
|
1361
|
+
endScroll: function(timeStamp) {
|
1362
|
+
this.scroller.doTouchEnd(timeStamp);
|
1363
|
+
},
|
1364
|
+
|
1365
|
+
// api
|
1366
|
+
scrollTo: function(y, animate) {
|
1367
|
+
if (animate === undefined) {
|
1368
|
+
animate = true;
|
1369
|
+
}
|
1370
|
+
|
1371
|
+
this.scroller.scrollTo(0, y, animate, 1);
|
1372
|
+
},
|
1373
|
+
|
1374
|
+
// events
|
1375
|
+
mouseWheel: function(e){
|
1376
|
+
var inverted, delta, candidatePosition;
|
1377
|
+
|
1378
|
+
inverted = e.webkitDirectionInvertedFromDevice;
|
1379
|
+
delta = e.wheelDeltaY * (inverted ? 0.8 : -0.8);
|
1380
|
+
candidatePosition = this.scroller.__scrollTop + delta;
|
1381
|
+
|
1382
|
+
if ((candidatePosition >= 0) && (candidatePosition <= this.scroller.__maxScrollTop)) {
|
1383
|
+
this.scroller.scrollBy(0, delta, true);
|
1384
|
+
}
|
1385
|
+
|
1386
|
+
return false;
|
1387
|
+
}
|
1388
|
+
});
|
1389
|
+
|
1390
|
+
})();
|
1391
|
+
|
1392
|
+
|
1393
|
+
|
1394
|
+
(function() {
|
1395
|
+
Ember.Handlebars.registerHelper('ember-list', function emberList(options) {
|
1396
|
+
var hash = options.hash;
|
1397
|
+
var types = options.hashTypes;
|
1398
|
+
|
1399
|
+
hash.content = hash.items;
|
1400
|
+
delete hash.items;
|
1401
|
+
|
1402
|
+
types.content = types.items;
|
1403
|
+
delete types.items;
|
1404
|
+
|
1405
|
+
if (!hash.content) {
|
1406
|
+
hash.content = "this";
|
1407
|
+
types.content = "ID";
|
1408
|
+
}
|
1409
|
+
|
1410
|
+
for (var prop in hash) {
|
1411
|
+
if (/-/.test(prop)) {
|
1412
|
+
var camelized = Ember.String.camelize(prop);
|
1413
|
+
hash[camelized] = hash[prop];
|
1414
|
+
types[camelized] = types[prop];
|
1415
|
+
delete hash[prop];
|
1416
|
+
delete types[prop];
|
1417
|
+
}
|
1418
|
+
}
|
1419
|
+
|
1420
|
+
return Ember.Handlebars.helpers.collection.call(this, 'Ember.ListView', options);
|
1421
|
+
});
|
1422
|
+
|
1423
|
+
|
1424
|
+
})();
|
1425
|
+
|
1426
|
+
|
1427
|
+
|
1428
|
+
(function() {
|
1429
|
+
|
1430
|
+
})();
|
1431
|
+
|
1432
|
+
|
1433
|
+
|
1434
|
+
if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
|
1435
|
+
Ember.Logger.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+
|
1436
|
+
"If you want full error messages please use the non-minified build provided on the Ember website.");
|
1437
|
+
}
|