Migrate deprecated Ember.ListView to regular components.
This is slower for scrolling but it actually feels reasonably fast to me? If we find it's a problem we can revisit once Ember 2.4 is fully in place.
This commit is contained in:
parent
af387edeb0
commit
bb0ab289b9
|
@ -1,8 +0,0 @@
|
|||
import ListView from 'ember-addons/list-view';
|
||||
import ListItemView from 'ember-addons/list-item-view';
|
||||
|
||||
export default ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: ListItemView.extend({templateName: "admin/templates/permalinks-list-item"})
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
import ListView from 'ember-addons/list-view';
|
||||
import ListItemView from 'ember-addons/list-item-view';
|
||||
|
||||
export default ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: ListItemView.extend({templateName: "admin/templates/logs/screened-emails-list-item"})
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
import ListView from 'ember-addons/list-view';
|
||||
import ListItemView from 'ember-addons/list-item-view';
|
||||
|
||||
export default ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: ListItemView.extend({templateName: "admin/templates/logs/screened-ip-addresses-list-item"})
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
import ListView from 'ember-addons/list-view';
|
||||
import ListItemView from 'ember-addons/list-item-view';
|
||||
|
||||
export default ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: ListItemView.extend({templateName: "admin/templates/logs/screened-urls-list-item"})
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
import ListView from 'ember-addons/list-view';
|
||||
import ListItemView from 'ember-addons/list-item-view';
|
||||
|
||||
export default ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 75,
|
||||
itemViewClass: ListItemView.extend({templateName: "admin/templates/logs/staff-action-logs-list-item"})
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
<div class="col first email">
|
||||
<div class="overflow-ellipsis" title={{email}}>{{email}}</div>
|
||||
</div>
|
||||
<div class="col action">{{actionName}}</div>
|
||||
<div class="col match_count">{{match_count}}</div>
|
||||
<div class="col last_match_at">{{age-with-tooltip last_match_at}}</div>
|
||||
<div class="col created_at">{{age-with-tooltip created_at}}</div>
|
||||
<div class="col ip_address">{{ip_address}}</div>
|
||||
<div class="col action"><button class="btn" {{action "clearBlock" this}}><i class='fa fa-check'></i> {{i18n 'admin.logs.screened_emails.actions.allow'}}</button></div>
|
||||
<div class="clearfix"></div>
|
|
@ -19,7 +19,22 @@
|
|||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
{{screened-emails-list content=model}}
|
||||
{{#each model as |item|}}
|
||||
<div class="admin-list-item">
|
||||
<div class="col first email">
|
||||
<div class="overflow-ellipsis" title={{item.email}}>{{item.email}}</div>
|
||||
</div>
|
||||
<div class="col action">{{item.actionName}}</div>
|
||||
<div class="col match_count">{{item.match_count}}</div>
|
||||
<div class="col last_match_at">{{age-with-tooltip item.last_match_at}}</div>
|
||||
<div class="col created_at">{{age-with-tooltip item.created_at}}</div>
|
||||
<div class="col ip_address">{{item.ip_address}}</div>
|
||||
<div class="col action">
|
||||
{{d-button action="clearBlock" actionParam=item icon="check" label="admin.logs.screened_emails.actions.allow"}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<div class="col first ip_address">
|
||||
{{#if editing}}
|
||||
{{text-field value=ip_address autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "edit" this}}>
|
||||
{{#if isRange}}
|
||||
<strong>{{ip_address}}</strong>
|
||||
{{else}}
|
||||
{{ip_address}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col action">
|
||||
{{#if isBlocked}}
|
||||
{{fa-icon "ban"}}
|
||||
{{else}}
|
||||
{{fa-icon "check"}}
|
||||
{{/if}}
|
||||
{{actionName}}
|
||||
</div>
|
||||
<div class="col match_count">{{match_count}}</div>
|
||||
<div class="col last_match_at">
|
||||
{{#if last_match_at}}
|
||||
{{age-with-tooltip last_match_at}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col created_at">{{age-with-tooltip created_at}}</div>
|
||||
<div class="col actions">
|
||||
{{#unless editing}}
|
||||
{{d-button action="destroy" actionParam=this icon="trash-o" class="btn-danger"}}
|
||||
{{d-button action="edit" actionParam=this icon="pencil"}}
|
||||
{{#if isBlocked}}
|
||||
{{d-button action="allow" actionParam=this icon="check" label="admin.logs.screened_ips.actions.do_nothing"}}
|
||||
{{else}}
|
||||
{{d-button action="block" actionParam=this icon="ban" label="admin.logs.screened_ips.actions.block"}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{d-button action="save" actionParam=this label="admin.logs.save"}}
|
||||
<a {{action "cancel" this}}>{{i18n 'cancel'}}</a>
|
||||
{{/unless}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
|
@ -24,7 +24,53 @@
|
|||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
{{screened-ip-addresses-list content=model}}
|
||||
{{#each model as |item|}}
|
||||
<div class="admin-list-item">
|
||||
<div class="col first ip_address">
|
||||
{{#if item.editing}}
|
||||
{{text-field value=item.ip_address autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "edit" item}}>
|
||||
{{#if item.isRange}}
|
||||
<strong>{{item.ip_address}}</strong>
|
||||
{{else}}
|
||||
{{item.ip_address}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col action">
|
||||
{{#if item.isBlocked}}
|
||||
{{fa-icon "ban"}}
|
||||
{{else}}
|
||||
{{fa-icon "check"}}
|
||||
{{/if}}
|
||||
{{item.actionName}}
|
||||
</div>
|
||||
<div class="col match_count">{{item.match_count}}</div>
|
||||
<div class="col last_match_at">
|
||||
{{#if item.last_match_at}}
|
||||
{{age-with-tooltip item.last_match_at}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col created_at">{{age-with-tooltip item.created_at}}</div>
|
||||
<div class="col actions">
|
||||
{{#unless item.editing}}
|
||||
{{d-button action="destroy" actionParam=item icon="trash-o" class="btn-danger"}}
|
||||
{{d-button action="edit" actionParam=item icon="pencil"}}
|
||||
{{#if isBlocked}}
|
||||
{{d-button action="allow" actionParam=item icon="check" label="admin.logs.screened_ips.actions.do_nothing"}}
|
||||
{{else}}
|
||||
{{d-button action="block" actionParam=item icon="ban" label="admin.logs.screened_ips.actions.block"}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{d-button action="save" actionParam=item label="admin.logs.save"}}
|
||||
<a {{action "cancel" item}}>{{i18n 'cancel'}}</a>
|
||||
{{/unless}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<div class="col first domain">
|
||||
<div class="overflow-ellipsis" title={{domain}}>{{domain}}</div>
|
||||
</div>
|
||||
<div class="col action">{{actionName}}</div>
|
||||
<div class="col match_count">{{match_count}}</div>
|
||||
<div class="col last_match_at">{{age-with-tooltip last_match_at}}</div>
|
||||
<div class="col created_at">{{age-with-tooltip created_at}}</div>
|
||||
<div class="clearfix"></div>
|
|
@ -16,7 +16,18 @@
|
|||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
{{screened-urls-list content=model}}
|
||||
{{#each model as |url|}}
|
||||
<div class="admin-list-item">
|
||||
<div class="col first domain">
|
||||
<div class="overflow-ellipsis" title={{url.domain}}>{{url.domain}}</div>
|
||||
</div>
|
||||
<div class="col action">{{url.actionName}}</div>
|
||||
<div class="col match_count">{{url.match_count}}</div>
|
||||
<div class="col last_match_at">{{age-with-tooltip url.last_match_at}}</div>
|
||||
<div class="col created_at">{{age-with-tooltip url.created_at}}</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{i18n 'search.no_results'}}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<div class="col value first staff_user">
|
||||
{{#link-to 'adminUser' acting_user}}{{avatar acting_user imageSize="tiny"}}{{/link-to}}
|
||||
<a {{action "filterByStaffUser" acting_user}} class="btn btn-small">{{acting_user.username}}</a>
|
||||
</div>
|
||||
<div class="col value action">
|
||||
<a {{action "filterByAction" this}} class="btn btn-small">{{actionName}}</a>
|
||||
</div>
|
||||
<div class="col value subject">
|
||||
{{#if target_user}}
|
||||
{{#link-to 'adminUser' target_user}}{{avatar target_user imageSize="tiny"}}{{/link-to}}
|
||||
<a {{action "filterByTargetUser" target_user}} class="btn btn-small">{{target_user.username}}</a>
|
||||
{{/if}}
|
||||
{{#if subject}}
|
||||
<a {{action "filterBySubject" subject}} title={{subject}} class="btn btn-small">{{subject}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col value created_at">{{age-with-tooltip created_at}}</div>
|
||||
<div class="col value details">
|
||||
{{{formattedDetails}}}
|
||||
{{#if useCustomModalForDetails}}
|
||||
<a {{action "showCustomDetailsModal" this}}>{{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||
{{/if}}
|
||||
{{#if useModalForDetails}}
|
||||
<a {{action "showDetailsModal" this}}>{{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col value context">{{context}}</div>
|
||||
<div class="clearfix"></div>
|
|
@ -49,10 +49,39 @@
|
|||
</div>
|
||||
|
||||
{{#conditional-loading-spinner condition=loading}}
|
||||
{{#if model.length}}
|
||||
{{staff-action-logs-list content=model}}
|
||||
{{#each model as |item|}}
|
||||
<div class='admin-list-item'>
|
||||
<div class="col value first staff_user">
|
||||
{{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}}
|
||||
<a {{action "filterByStaffUser" item.acting_user}} class="btn btn-small">{{item.acting_user.username}}</a>
|
||||
</div>
|
||||
<div class="col value action">
|
||||
<a {{action "filterByAction" item}} class="btn btn-small">{{item.actionName}}</a>
|
||||
</div>
|
||||
<div class="col value subject">
|
||||
{{#if item.target_user}}
|
||||
{{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}}
|
||||
<a {{action "filterByTargetUser" item.target_user}} class="btn btn-small">{{item.target_user.username}}</a>
|
||||
{{/if}}
|
||||
{{#if item.subject}}
|
||||
<a {{action "filterBySubject" item.subject}} title={{item.subject}} class="btn btn-small">{{item.subject}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col value created_at">{{age-with-tooltip item.created_at}}</div>
|
||||
<div class="col value details">
|
||||
{{{item.formattedDetails}}}
|
||||
{{#if item.useCustomModalForDetails}}
|
||||
<a {{action "showCustomDetailsModal" item}}>{{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||
{{/if}}
|
||||
{{#if item.useModalForDetails}}
|
||||
<a {{action "showDetailsModal" item}}>{{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col value context">{{item.context}}</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{i18n 'search.no_results'}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/conditional-loading-spinner}}
|
||||
</div>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<div class="col first url">{{url}}</div>
|
||||
<div class="col topic">
|
||||
{{#if topic_id}}
|
||||
<a href='{{unbound topic_url}}'>{{topic_title}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col post">
|
||||
{{#if post_id}}
|
||||
<a href='{{unbound post_url}}'>#{{post_number}} {{post_topic_title}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col category">
|
||||
{{#if category_id}}
|
||||
<a href='{{unbound category_url}}'>{{category_name}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col external_url">
|
||||
{{#if external_url}}
|
||||
<a href='{{unbound external_url}}'>{{external_url}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col action"><button class="btn btn-danger" {{action "destroy" this}}><i class="fa fa-trash-o"></i></button></div>
|
||||
<div class="clearfix"></div>
|
|
@ -17,7 +17,35 @@
|
|||
<div class="col heading actions"></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{permalinks-list content=model}}
|
||||
{{#each model as |pl|}}
|
||||
<div class="admin-list-item">
|
||||
<div class="col first url">{{pl.url}}</div>
|
||||
<div class="col topic">
|
||||
{{#if pl.topic_id}}
|
||||
<a href='{{unbound pl.topic_url}}'>{{pl.topic_title}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col post">
|
||||
{{#if pl.post_id}}
|
||||
<a href='{{unbound pl.post_url}}'>#{{pl.post_number}} {{pl.post_topic_title}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col category">
|
||||
{{#if pl.category_id}}
|
||||
<a href='{{unbound pl.category_url}}'>{{pl.category_name}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col external_url">
|
||||
{{#if pl.external_url}}
|
||||
<a href='{{unbound pl.external_url}}'>{{pl.external_url}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col action">
|
||||
{{d-button action="destroy" actionParam=pl icon="trash-o" class="btn-danger"}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{i18n 'search.no_results'}}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
function samePosition(a, b) {
|
||||
return a && b && a.x === b.x && a.y === b.y;
|
||||
}
|
||||
|
||||
function positionElement() {
|
||||
var element, position, _position;
|
||||
|
||||
Ember.instrument('view.updateContext.positionElement', this, function() {
|
||||
element = this.element;
|
||||
position = this.position;
|
||||
_position = this._position;
|
||||
|
||||
if (!position || !element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// // TODO: avoid needing this by avoiding unnecessary
|
||||
// // calls to this method in the first place
|
||||
if (samePosition(position, _position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ember.run.schedule('render', this, this._parentView.applyTransform, this, position.x, position.y);
|
||||
this._position = position;
|
||||
}, this);
|
||||
}
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
classNames: ['ember-list-item-view'],
|
||||
style: Ember.String.htmlSafe(''),
|
||||
attributeBindings: ['style'],
|
||||
_position: null,
|
||||
_positionElement: positionElement,
|
||||
|
||||
positionElementWhenInserted: Ember.on('init', function(){
|
||||
this.one('didInsertElement', positionElement);
|
||||
}),
|
||||
|
||||
updatePosition: function(position) {
|
||||
this.position = position;
|
||||
this._positionElement();
|
||||
}
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
import Ember from 'ember';
|
||||
import ListItemViewMixin from './list-item-view-mixin';
|
||||
|
||||
var get = Ember.get, set = Ember.set;
|
||||
|
||||
/**
|
||||
The `Ember.ListItemView` view class renders a
|
||||
[div](https://developer.mozilla.org/en/HTML/Element/div) HTML element
|
||||
with `ember-list-item-view` class. It allows you to specify a custom item
|
||||
handlebars template for `Ember.ListView`.
|
||||
|
||||
Example:
|
||||
|
||||
```handlebars
|
||||
<script type="text/x-handlebars" data-template-name="row_item">
|
||||
{{name}}
|
||||
</script>
|
||||
```
|
||||
|
||||
```javascript
|
||||
App.ListView = Ember.ListView.extend({
|
||||
height: 500,
|
||||
rowHeight: 20,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "row_item"})
|
||||
});
|
||||
```
|
||||
|
||||
@extends Ember.View
|
||||
@class ListItemView
|
||||
@namespace Ember
|
||||
*/
|
||||
export default Ember.View.extend(ListItemViewMixin, {
|
||||
updateContext: function(newContext) {
|
||||
var context = get(this, 'context');
|
||||
|
||||
Ember.instrument('view.updateContext.render', this, function() {
|
||||
if (context !== newContext) {
|
||||
set(this, 'context', newContext);
|
||||
if (newContext && newContext.isController) {
|
||||
set(this, 'controller', newContext);
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
rerender: function () {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
_contextDidChange: Ember.observer(function () {
|
||||
Ember.run.once(this, this.rerender);
|
||||
}, 'context', 'controller')
|
||||
});
|
|
@ -1,94 +0,0 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
// TODO - remove this!
|
||||
var el = document.body || document.createElement('div');
|
||||
var style = el.style;
|
||||
var set = Ember.set;
|
||||
|
||||
function getElementStyle (prop) {
|
||||
var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1);
|
||||
|
||||
var props = [
|
||||
prop,
|
||||
'webkit' + prop,
|
||||
'webkit' + uppercaseProp,
|
||||
'Moz' + uppercaseProp,
|
||||
'moz' + uppercaseProp,
|
||||
'ms' + uppercaseProp,
|
||||
'ms' + prop
|
||||
];
|
||||
|
||||
for (var i=0; i < props.length; i++) {
|
||||
var property = props[i];
|
||||
|
||||
if (property in style) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCSSStyle (attr) {
|
||||
var styleName = getElementStyle(attr);
|
||||
var prefix = styleName.toLowerCase().replace(attr, '');
|
||||
|
||||
var dic = {
|
||||
webkit: '-webkit-' + attr,
|
||||
moz: '-moz-' + attr,
|
||||
ms: '-ms-' + attr
|
||||
};
|
||||
|
||||
if (prefix && dic[prefix]) {
|
||||
return dic[prefix];
|
||||
}
|
||||
|
||||
return styleName;
|
||||
}
|
||||
|
||||
var styleAttributeName = getElementStyle('transform');
|
||||
var transformProp = getCSSStyle('transform');
|
||||
var perspectiveProp = getElementStyle('perspective');
|
||||
var supports2D = !!transformProp;
|
||||
var supports3D = !!perspectiveProp;
|
||||
|
||||
function setStyle (optionalStyleString) {
|
||||
return function (obj, x, y) {
|
||||
var isElement = obj instanceof Element;
|
||||
|
||||
if (optionalStyleString && (supports2D || supports3D)) {
|
||||
var style = Ember.String.fmt(optionalStyleString, x, y);
|
||||
|
||||
if (isElement) {
|
||||
obj.style[styleAttributeName] = Ember.String.htmlSafe(style);
|
||||
} else {
|
||||
set(obj, 'style', Ember.String.htmlSafe(transformProp + ': ' + style));
|
||||
}
|
||||
} else {
|
||||
if (isElement) {
|
||||
obj.style.top = y;
|
||||
obj.style.left = x;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
transformProp: transformProp,
|
||||
applyTransform: (function () {
|
||||
if (supports2D) {
|
||||
return setStyle('translate(%@px, %@px)');
|
||||
}
|
||||
|
||||
return setStyle();
|
||||
})(),
|
||||
apply3DTransform: (function () {
|
||||
if (supports3D) {
|
||||
return setStyle('translate3d(%@px, %@px, 0)');
|
||||
} else if (supports2D) {
|
||||
return setStyle('translate(%@px, %@px)');
|
||||
}
|
||||
|
||||
return setStyle();
|
||||
})()
|
||||
};
|
|
@ -1,886 +0,0 @@
|
|||
// TODO: remove unused: false
|
||||
/* jshint unused: false*/
|
||||
import Ember from 'ember';
|
||||
import ReusableListItemView from './reusable-list-item-view';
|
||||
|
||||
var get = Ember.get;
|
||||
var set = Ember.set;
|
||||
var min = Math.min;
|
||||
var max = Math.max;
|
||||
var floor = Math.floor;
|
||||
var ceil = Math.ceil;
|
||||
var forEach = Ember.ArrayPolyfills.forEach;
|
||||
|
||||
function addContentArrayObserver() {
|
||||
var content = get(this, 'content');
|
||||
if (content) {
|
||||
content.addArrayObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
function removeAndDestroy(object) {
|
||||
this.removeObject(object);
|
||||
object.destroy();
|
||||
}
|
||||
|
||||
function syncChildViews() {
|
||||
Ember.run.once(this, '_syncChildViews');
|
||||
}
|
||||
|
||||
function sortByContentIndex (viewOne, viewTwo) {
|
||||
return get(viewOne, 'contentIndex') - get(viewTwo, 'contentIndex');
|
||||
}
|
||||
|
||||
function removeEmptyView() {
|
||||
var emptyView = get(this, 'emptyView');
|
||||
if (emptyView && emptyView instanceof Ember.View) {
|
||||
emptyView.removeFromParent();
|
||||
if (this.totalHeightDidChange !== undefined) {
|
||||
this.totalHeightDidChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addEmptyView() {
|
||||
var emptyView = get(this, 'emptyView');
|
||||
|
||||
if (!emptyView) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('string' === typeof emptyView) {
|
||||
emptyView = get(emptyView) || emptyView;
|
||||
}
|
||||
|
||||
emptyView = this.createChildView(emptyView);
|
||||
set(this, 'emptyView', emptyView);
|
||||
|
||||
if (Ember.CoreView.detect(emptyView)) {
|
||||
this._createdEmptyView = emptyView;
|
||||
}
|
||||
|
||||
this.unshiftObject(emptyView);
|
||||
}
|
||||
|
||||
function enableProfilingOutput() {
|
||||
function before(name, time/*, payload*/) {
|
||||
console.time(name);
|
||||
}
|
||||
|
||||
function after (name, time/*, payload*/) {
|
||||
console.timeEnd(name);
|
||||
}
|
||||
|
||||
if (Ember.ENABLE_PROFILING) {
|
||||
Ember.subscribe('view._scrollContentTo', {
|
||||
before: before,
|
||||
after: after
|
||||
});
|
||||
Ember.subscribe('view.updateContext', {
|
||||
before: before,
|
||||
after: after
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@class Ember.ListViewMixin
|
||||
@namespace Ember
|
||||
*/
|
||||
export default Ember.Mixin.create({
|
||||
itemViewClass: ReusableListItemView,
|
||||
emptyViewClass: Ember.View,
|
||||
classNames: ['ember-list-view'],
|
||||
attributeBindings: ['style'],
|
||||
classNameBindings: ['_isGrid:ember-list-view-grid:ember-list-view-list'],
|
||||
scrollTop: 0,
|
||||
bottomPadding: 0, // TODO: maybe this can go away
|
||||
_lastEndingIndex: 0,
|
||||
paddingCount: 1,
|
||||
_cachedPos: 0,
|
||||
|
||||
_isGrid: Ember.computed.gt('columnCount', 1).readOnly(),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Setup a mixin.
|
||||
- adding observer to content array
|
||||
- creating child views based on height and length of the content array
|
||||
|
||||
@method init
|
||||
*/
|
||||
init: function() {
|
||||
this._super();
|
||||
this._cachedHeights = [0];
|
||||
this.on('didInsertElement', this._syncListContainerWidth);
|
||||
this.columnCountDidChange();
|
||||
this._syncChildViews();
|
||||
this._addContentArrayObserver();
|
||||
},
|
||||
|
||||
_addContentArrayObserver: Ember.beforeObserver(function() {
|
||||
addContentArrayObserver.call(this);
|
||||
}, 'content'),
|
||||
|
||||
/**
|
||||
Called on your view when it should push strings of HTML into a
|
||||
`Ember.RenderBuffer`.
|
||||
|
||||
Adds a [div](https://developer.mozilla.org/en-US/docs/HTML/Element/div)
|
||||
with a required `ember-list-container` class.
|
||||
|
||||
@method render
|
||||
@param {Ember.RenderBuffer} buffer The render buffer
|
||||
*/
|
||||
render: function (buffer) {
|
||||
var element = buffer.element();
|
||||
var dom = buffer.dom;
|
||||
var container = dom.createElement('div');
|
||||
container.className = 'ember-list-container';
|
||||
element.appendChild(container);
|
||||
|
||||
this._childViewsMorph = dom.appendMorph(container, container, null);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
createChildViewsMorph: function (element) {
|
||||
this._childViewsMorph = this._renderer._dom.createMorph(element.lastChild, element.lastChild, null);
|
||||
return element;
|
||||
},
|
||||
|
||||
willInsertElement: function() {
|
||||
if (!this.get('height') || !this.get('rowHeight')) {
|
||||
throw new Error('A ListView must be created with a height and a rowHeight.');
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Sets inline styles of the view:
|
||||
- height
|
||||
- width
|
||||
- position
|
||||
- overflow
|
||||
- -webkit-overflow
|
||||
- overflow-scrolling
|
||||
|
||||
Called while attributes binding.
|
||||
|
||||
@property {Ember.ComputedProperty} style
|
||||
*/
|
||||
style: Ember.computed('height', 'width', function() {
|
||||
var height, width, style, css;
|
||||
|
||||
height = get(this, 'height');
|
||||
width = get(this, 'width');
|
||||
css = get(this, 'css');
|
||||
|
||||
style = '';
|
||||
|
||||
if (height) {
|
||||
style += 'height:' + height + 'px;';
|
||||
}
|
||||
|
||||
if (width) {
|
||||
style += 'width:' + width + 'px;';
|
||||
}
|
||||
|
||||
for ( var rule in css ) {
|
||||
if (css.hasOwnProperty(rule)) {
|
||||
style += rule + ':' + css[rule] + ';';
|
||||
}
|
||||
}
|
||||
|
||||
return Ember.String.htmlSafe(style);
|
||||
}),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Performs visual scrolling. Is overridden in Ember.ListView.
|
||||
|
||||
@method scrollTo
|
||||
*/
|
||||
scrollTo: function(y) {
|
||||
throw new Error('must override to perform the visual scroll and effectively delegate to _scrollContentTo');
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Internal method used to force scroll position
|
||||
|
||||
@method scrollTo
|
||||
*/
|
||||
_scrollTo: Ember.K,
|
||||
|
||||
/**
|
||||
@private
|
||||
@method _scrollContentTo
|
||||
*/
|
||||
_scrollContentTo: function(y) {
|
||||
var startingIndex, endingIndex,
|
||||
contentIndex, visibleEndingIndex, maxContentIndex,
|
||||
contentIndexEnd, contentLength, scrollTop, content;
|
||||
|
||||
scrollTop = max(0, y);
|
||||
|
||||
if (this.scrollTop === scrollTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
// allow a visual overscroll, but don't scroll the content. As we are doing needless
|
||||
// recycyling, and adding unexpected nodes to the DOM.
|
||||
var maxScrollTop = max(0, get(this, 'totalHeight') - get(this, 'height'));
|
||||
scrollTop = min(scrollTop, maxScrollTop);
|
||||
|
||||
content = get(this, 'content');
|
||||
contentLength = get(content, 'length');
|
||||
startingIndex = this._startingIndex(contentLength);
|
||||
|
||||
Ember.instrument('view._scrollContentTo', {
|
||||
scrollTop: scrollTop,
|
||||
content: content,
|
||||
startingIndex: startingIndex,
|
||||
endingIndex: min(max(contentLength - 1, 0), startingIndex + this._numChildViewsForViewport())
|
||||
}, function () {
|
||||
this.scrollTop = scrollTop;
|
||||
|
||||
maxContentIndex = max(contentLength - 1, 0);
|
||||
|
||||
startingIndex = this._startingIndex();
|
||||
visibleEndingIndex = startingIndex + this._numChildViewsForViewport();
|
||||
|
||||
endingIndex = min(maxContentIndex, visibleEndingIndex);
|
||||
|
||||
if (startingIndex === this._lastStartingIndex &&
|
||||
endingIndex === this._lastEndingIndex) {
|
||||
|
||||
this.trigger('scrollYChanged', y);
|
||||
return;
|
||||
} else {
|
||||
|
||||
Ember.run(this, function() {
|
||||
this._reuseChildren();
|
||||
|
||||
this._lastStartingIndex = startingIndex;
|
||||
this._lastEndingIndex = endingIndex;
|
||||
this.trigger('scrollYChanged', y);
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Computes the height for a `Ember.ListView` scrollable container div.
|
||||
You must specify `rowHeight` parameter for the height to be computed properly.
|
||||
|
||||
@property {Ember.ComputedProperty} totalHeight
|
||||
*/
|
||||
totalHeight: Ember.computed('content.length',
|
||||
'rowHeight',
|
||||
'columnCount',
|
||||
'bottomPadding', function() {
|
||||
if (typeof this.heightForIndex === 'function') {
|
||||
return this._totalHeightWithHeightForIndex();
|
||||
} else {
|
||||
return this._totalHeightWithStaticRowHeight();
|
||||
}
|
||||
}),
|
||||
|
||||
_doRowHeightDidChange: function() {
|
||||
this._cachedHeights = [0];
|
||||
this._cachedPos = 0;
|
||||
this._syncChildViews();
|
||||
},
|
||||
|
||||
_rowHeightDidChange: Ember.observer('rowHeight', function() {
|
||||
Ember.run.once(this, this._doRowHeightDidChange);
|
||||
}),
|
||||
|
||||
_totalHeightWithHeightForIndex: function() {
|
||||
var length = this.get('content.length');
|
||||
return this._cachedHeightLookup(length);
|
||||
},
|
||||
|
||||
_totalHeightWithStaticRowHeight: function() {
|
||||
var contentLength, rowHeight, columnCount, bottomPadding;
|
||||
|
||||
contentLength = get(this, 'content.length');
|
||||
rowHeight = get(this, 'rowHeight');
|
||||
columnCount = get(this, 'columnCount');
|
||||
bottomPadding = get(this, 'bottomPadding');
|
||||
|
||||
return ((ceil(contentLength / columnCount)) * rowHeight) + bottomPadding;
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method _prepareChildForReuse
|
||||
*/
|
||||
_prepareChildForReuse: function(childView) {
|
||||
childView.prepareForReuse();
|
||||
},
|
||||
|
||||
createChildView: function (_view) {
|
||||
return this._super(_view, this._itemViewProps || {});
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method _reuseChildForContentIndex
|
||||
*/
|
||||
_reuseChildForContentIndex: function(childView, contentIndex) {
|
||||
var content, context, newContext, childsCurrentContentIndex, position, enableProfiling, oldChildView;
|
||||
|
||||
var contentViewClass = this.itemViewForIndex(contentIndex);
|
||||
|
||||
if (childView.constructor !== contentViewClass) {
|
||||
// rather then associative arrays, lets move childView + contentEntry maping to a Map
|
||||
var i = this._childViews.indexOf(childView);
|
||||
childView.destroy();
|
||||
childView = this.createChildView(contentViewClass);
|
||||
this.insertAt(i, childView);
|
||||
}
|
||||
|
||||
content = get(this, 'content');
|
||||
enableProfiling = get(this, 'enableProfiling');
|
||||
position = this.positionForIndex(contentIndex);
|
||||
childView.updatePosition(position);
|
||||
|
||||
set(childView, 'contentIndex', contentIndex);
|
||||
|
||||
if (enableProfiling) {
|
||||
Ember.instrument('view._reuseChildForContentIndex', position, function() {
|
||||
|
||||
}, this);
|
||||
}
|
||||
|
||||
newContext = content.objectAt(contentIndex);
|
||||
childView.updateContext(newContext);
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method positionForIndex
|
||||
*/
|
||||
positionForIndex: function(index) {
|
||||
if (typeof this.heightForIndex !== 'function') {
|
||||
return this._singleHeightPosForIndex(index);
|
||||
}
|
||||
else {
|
||||
return this._multiHeightPosForIndex(index);
|
||||
}
|
||||
},
|
||||
|
||||
_singleHeightPosForIndex: function(index) {
|
||||
var elementWidth, width, columnCount, rowHeight, y, x;
|
||||
|
||||
elementWidth = get(this, 'elementWidth') || 1;
|
||||
width = get(this, 'width') || 1;
|
||||
columnCount = get(this, 'columnCount');
|
||||
rowHeight = get(this, 'rowHeight');
|
||||
|
||||
y = (rowHeight * floor(index/columnCount));
|
||||
x = (index % columnCount) * elementWidth;
|
||||
|
||||
return {
|
||||
y: y,
|
||||
x: x
|
||||
};
|
||||
},
|
||||
|
||||
// 0 maps to 0, 1 maps to heightForIndex(i)
|
||||
_multiHeightPosForIndex: function(index) {
|
||||
var elementWidth, width, columnCount, rowHeight, y, x;
|
||||
|
||||
elementWidth = get(this, 'elementWidth') || 1;
|
||||
width = get(this, 'width') || 1;
|
||||
columnCount = get(this, 'columnCount');
|
||||
|
||||
x = (index % columnCount) * elementWidth;
|
||||
y = this._cachedHeightLookup(index);
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
},
|
||||
|
||||
_cachedHeightLookup: function(index) {
|
||||
for (var i = this._cachedPos; i < index; i++) {
|
||||
this._cachedHeights[i + 1] = this._cachedHeights[i] + this.heightForIndex(i);
|
||||
}
|
||||
this._cachedPos = i;
|
||||
return this._cachedHeights[index];
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method _childViewCount
|
||||
*/
|
||||
_childViewCount: function() {
|
||||
var contentLength, childViewCountForHeight;
|
||||
|
||||
contentLength = get(this, 'content.length');
|
||||
childViewCountForHeight = this._numChildViewsForViewport();
|
||||
|
||||
return min(contentLength, childViewCountForHeight);
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Returns a number of columns in the Ember.ListView (for grid layout).
|
||||
|
||||
If you want to have a multi column layout, you need to specify both
|
||||
`width` and `elementWidth`.
|
||||
|
||||
If no `elementWidth` is specified, it returns `1`. Otherwise, it will
|
||||
try to fit as many columns as possible for a given `width`.
|
||||
|
||||
@property {Ember.ComputedProperty} columnCount
|
||||
*/
|
||||
columnCount: Ember.computed('width', 'elementWidth', function() {
|
||||
var elementWidth, width, count;
|
||||
|
||||
elementWidth = get(this, 'elementWidth');
|
||||
width = get(this, 'width');
|
||||
|
||||
if (elementWidth && width > elementWidth) {
|
||||
count = floor(width / elementWidth);
|
||||
} else {
|
||||
count = 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Fires every time column count is changed.
|
||||
|
||||
@event columnCountDidChange
|
||||
*/
|
||||
columnCountDidChange: Ember.observer(function() {
|
||||
var ratio, currentScrollTop, proposedScrollTop, maxScrollTop,
|
||||
scrollTop, lastColumnCount, newColumnCount, element;
|
||||
|
||||
lastColumnCount = this._lastColumnCount;
|
||||
|
||||
currentScrollTop = this.scrollTop;
|
||||
newColumnCount = get(this, 'columnCount');
|
||||
maxScrollTop = get(this, 'maxScrollTop');
|
||||
element = this.element;
|
||||
|
||||
this._lastColumnCount = newColumnCount;
|
||||
|
||||
if (lastColumnCount) {
|
||||
ratio = (lastColumnCount / newColumnCount);
|
||||
proposedScrollTop = currentScrollTop * ratio;
|
||||
scrollTop = min(maxScrollTop, proposedScrollTop);
|
||||
|
||||
this._scrollTo(scrollTop);
|
||||
this.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
if (arguments.length > 0) {
|
||||
// invoked by observer
|
||||
Ember.run.schedule('afterRender', this, this._syncListContainerWidth);
|
||||
}
|
||||
}, 'columnCount'),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Computes max possible scrollTop value given the visible viewport
|
||||
and scrollable container div height.
|
||||
|
||||
@property {Ember.ComputedProperty} maxScrollTop
|
||||
*/
|
||||
maxScrollTop: Ember.computed('height', 'totalHeight', function(){
|
||||
var totalHeight, viewportHeight;
|
||||
|
||||
totalHeight = get(this, 'totalHeight');
|
||||
viewportHeight = get(this, 'height');
|
||||
|
||||
return max(0, totalHeight - viewportHeight);
|
||||
}),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Determines whether the emptyView is the current childView.
|
||||
|
||||
@method _isChildEmptyView
|
||||
*/
|
||||
_isChildEmptyView: function() {
|
||||
var emptyView = get(this, 'emptyView');
|
||||
|
||||
return emptyView && emptyView instanceof Ember.View &&
|
||||
this._childViews.length === 1 && this._childViews.indexOf(emptyView) === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Computes the number of views that would fit in the viewport area.
|
||||
You must specify `height` and `rowHeight` parameters for the number of
|
||||
views to be computed properly.
|
||||
|
||||
@method _numChildViewsForViewport
|
||||
*/
|
||||
_numChildViewsForViewport: function() {
|
||||
|
||||
if (this.heightForIndex) {
|
||||
return this._numChildViewsForViewportWithMultiHeight();
|
||||
} else {
|
||||
return this._numChildViewsForViewportWithoutMultiHeight();
|
||||
}
|
||||
},
|
||||
|
||||
_numChildViewsForViewportWithoutMultiHeight: function() {
|
||||
var height, rowHeight, paddingCount, columnCount;
|
||||
|
||||
height = get(this, 'height');
|
||||
rowHeight = get(this, 'rowHeight');
|
||||
paddingCount = get(this, 'paddingCount');
|
||||
columnCount = get(this, 'columnCount');
|
||||
|
||||
return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount);
|
||||
},
|
||||
|
||||
_numChildViewsForViewportWithMultiHeight: function() {
|
||||
var rowHeight, paddingCount, columnCount;
|
||||
var scrollTop = this.scrollTop;
|
||||
var viewportHeight = this.get('height');
|
||||
var length = this.get('content.length');
|
||||
var heightfromTop = 0;
|
||||
var padding = get(this, 'paddingCount');
|
||||
|
||||
var startingIndex = this._calculatedStartingIndex();
|
||||
var currentHeight = 0;
|
||||
|
||||
var offsetHeight = this._cachedHeightLookup(startingIndex);
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (this._cachedHeightLookup(startingIndex + i + 1) - offsetHeight > viewportHeight) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i + padding + 1;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Computes the starting index of the item views array.
|
||||
Takes `scrollTop` property of the element into account.
|
||||
|
||||
Is used in `_syncChildViews`.
|
||||
|
||||
@method _startingIndex
|
||||
*/
|
||||
_startingIndex: function(_contentLength) {
|
||||
var scrollTop, rowHeight, columnCount, calculatedStartingIndex,
|
||||
contentLength;
|
||||
|
||||
if (_contentLength === undefined) {
|
||||
contentLength = get(this, 'content.length');
|
||||
} else {
|
||||
contentLength = _contentLength;
|
||||
}
|
||||
|
||||
scrollTop = this.scrollTop;
|
||||
rowHeight = get(this, 'rowHeight');
|
||||
columnCount = get(this, 'columnCount');
|
||||
|
||||
if (this.heightForIndex) {
|
||||
calculatedStartingIndex = this._calculatedStartingIndex();
|
||||
} else {
|
||||
calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount;
|
||||
}
|
||||
|
||||
var viewsNeededForViewport = this._numChildViewsForViewport();
|
||||
var paddingCount = (1 * columnCount);
|
||||
var largestStartingIndex = max(contentLength - viewsNeededForViewport, 0);
|
||||
|
||||
return min(calculatedStartingIndex, largestStartingIndex);
|
||||
},
|
||||
|
||||
_calculatedStartingIndex: function() {
|
||||
var rowHeight, paddingCount, columnCount;
|
||||
var scrollTop = this.scrollTop;
|
||||
var viewportHeight = this.get('height');
|
||||
var length = this.get('content.length');
|
||||
var heightfromTop = 0;
|
||||
var padding = get(this, 'paddingCount');
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (this._cachedHeightLookup(i + 1) >= scrollTop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@event contentWillChange
|
||||
*/
|
||||
contentWillChange: Ember.beforeObserver(function() {
|
||||
var content = get(this, 'content');
|
||||
|
||||
if (content) {
|
||||
content.removeArrayObserver(this);
|
||||
}
|
||||
}, 'content'),
|
||||
|
||||
/**),
|
||||
@private
|
||||
@event contentDidChange
|
||||
*/
|
||||
contentDidChange: Ember.observer(function() {
|
||||
addContentArrayObserver.call(this);
|
||||
syncChildViews.call(this);
|
||||
}, 'content'),
|
||||
|
||||
/**
|
||||
@private
|
||||
@property {Function} needsSyncChildViews
|
||||
*/
|
||||
needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'),
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Returns a new item view. Takes `contentIndex` to set the context
|
||||
of the returned view properly.
|
||||
|
||||
@param {Number} contentIndex item index in the content array
|
||||
@method _addItemView
|
||||
*/
|
||||
_addItemView: function (contentIndex) {
|
||||
var itemViewClass, childView;
|
||||
|
||||
itemViewClass = this.itemViewForIndex(contentIndex);
|
||||
childView = this.createChildView(itemViewClass);
|
||||
this.pushObject(childView);
|
||||
},
|
||||
|
||||
/**
|
||||
@public
|
||||
|
||||
Returns a view class for the provided contentIndex. If the view is
|
||||
different then the one currently present it will remove the existing view
|
||||
and replace it with an instance of the class provided
|
||||
|
||||
@param {Number} contentIndex item index in the content array
|
||||
@method _addItemView
|
||||
@returns {Ember.View} ember view class for this index
|
||||
*/
|
||||
itemViewForIndex: function(contentIndex) {
|
||||
return get(this, 'itemViewClass');
|
||||
},
|
||||
|
||||
/**
|
||||
@public
|
||||
|
||||
Returns a view class for the provided contentIndex. If the view is
|
||||
different then the one currently present it will remove the existing view
|
||||
and replace it with an instance of the class provided
|
||||
|
||||
@param {Number} contentIndex item index in the content array
|
||||
@method _addItemView
|
||||
@returns {Ember.View} ember view class for this index
|
||||
*/
|
||||
heightForIndex: null,
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Intelligently manages the number of childviews.
|
||||
|
||||
@method _syncChildViews
|
||||
**/
|
||||
_syncChildViews: function () {
|
||||
var childViews, childViewCount,
|
||||
numberOfChildViews, numberOfChildViewsNeeded,
|
||||
contentIndex, startingIndex, endingIndex,
|
||||
contentLength, emptyView, count, delta;
|
||||
|
||||
if (this.isDestroyed || this.isDestroying) {
|
||||
return;
|
||||
}
|
||||
|
||||
contentLength = get(this, 'content.length');
|
||||
emptyView = get(this, 'emptyView');
|
||||
|
||||
childViewCount = this._childViewCount();
|
||||
childViews = this.positionOrderedChildViews();
|
||||
|
||||
if (this._isChildEmptyView()) {
|
||||
removeEmptyView.call(this);
|
||||
}
|
||||
|
||||
startingIndex = this._startingIndex();
|
||||
endingIndex = startingIndex + childViewCount;
|
||||
|
||||
numberOfChildViewsNeeded = childViewCount;
|
||||
numberOfChildViews = childViews.length;
|
||||
|
||||
delta = numberOfChildViewsNeeded - numberOfChildViews;
|
||||
|
||||
if (delta === 0) {
|
||||
// no change
|
||||
} else if (delta > 0) {
|
||||
// more views are needed
|
||||
contentIndex = this._lastEndingIndex;
|
||||
|
||||
for (count = 0; count < delta; count++, contentIndex++) {
|
||||
this._addItemView(contentIndex);
|
||||
}
|
||||
} else {
|
||||
// less views are needed
|
||||
forEach.call(
|
||||
childViews.splice(numberOfChildViewsNeeded, numberOfChildViews),
|
||||
removeAndDestroy,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
this._reuseChildren();
|
||||
|
||||
this._lastStartingIndex = startingIndex;
|
||||
this._lastEndingIndex = this._lastEndingIndex + delta;
|
||||
|
||||
if (contentLength === 0 || contentLength === undefined) {
|
||||
addEmptyView.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
|
||||
Applies an inline width style to the list container.
|
||||
|
||||
@method _syncListContainerWidth
|
||||
**/
|
||||
_syncListContainerWidth: function() {
|
||||
var elementWidth, columnCount, containerWidth, element;
|
||||
|
||||
elementWidth = get(this, 'elementWidth');
|
||||
columnCount = get(this, 'columnCount');
|
||||
containerWidth = elementWidth * columnCount;
|
||||
element = this.$('.ember-list-container');
|
||||
|
||||
if (containerWidth && element) {
|
||||
element.css('width', containerWidth);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method _reuseChildren
|
||||
*/
|
||||
_reuseChildren: function(){
|
||||
var contentLength, childViews, childViewsLength,
|
||||
startingIndex, endingIndex, childView, attrs,
|
||||
contentIndex, visibleEndingIndex, maxContentIndex,
|
||||
contentIndexEnd, scrollTop;
|
||||
|
||||
scrollTop = this.scrollTop;
|
||||
contentLength = get(this, 'content.length');
|
||||
maxContentIndex = max(contentLength - 1, 0);
|
||||
childViews = this.getReusableChildViews();
|
||||
childViewsLength = childViews.length;
|
||||
|
||||
startingIndex = this._startingIndex();
|
||||
visibleEndingIndex = startingIndex + this._numChildViewsForViewport();
|
||||
|
||||
endingIndex = min(maxContentIndex, visibleEndingIndex);
|
||||
|
||||
contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength);
|
||||
|
||||
for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) {
|
||||
childView = childViews[contentIndex % childViewsLength];
|
||||
this._reuseChildForContentIndex(childView, contentIndex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method getReusableChildViews
|
||||
*/
|
||||
getReusableChildViews: function() {
|
||||
return this._childViews;
|
||||
},
|
||||
|
||||
/**
|
||||
@private
|
||||
@method positionOrderedChildViews
|
||||
*/
|
||||
positionOrderedChildViews: function() {
|
||||
return this.getReusableChildViews().sort(sortByContentIndex);
|
||||
},
|
||||
|
||||
arrayWillChange: Ember.K,
|
||||
|
||||
/**
|
||||
@private
|
||||
@event arrayDidChange
|
||||
*/
|
||||
// TODO: refactor
|
||||
arrayDidChange: function(content, start, removedCount, addedCount) {
|
||||
var index, contentIndex, state;
|
||||
|
||||
if (this._isChildEmptyView()) {
|
||||
removeEmptyView.call(this);
|
||||
}
|
||||
|
||||
// Support old and new Ember versions
|
||||
state = this._state || this.state;
|
||||
|
||||
if (state === 'inDOM') {
|
||||
// ignore if all changes are out of the visible change
|
||||
if (start >= this._lastStartingIndex || start < this._lastEndingIndex) {
|
||||
index = 0;
|
||||
// ignore all changes not in the visible range
|
||||
// this can re-position many, rather then causing a cascade of re-renders
|
||||
forEach.call(
|
||||
this.positionOrderedChildViews(),
|
||||
function(childView) {
|
||||
contentIndex = this._lastStartingIndex + index;
|
||||
this._reuseChildForContentIndex(childView, contentIndex);
|
||||
index++;
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
syncChildViews.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
if (!this._super()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._createdEmptyView) {
|
||||
this._createdEmptyView.destroy();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
|
@ -1,167 +0,0 @@
|
|||
import Ember from 'ember';
|
||||
import ListViewHelper from './list-view-helper';
|
||||
import ListViewMixin from './list-view-mixin';
|
||||
|
||||
var get = Ember.get;
|
||||
|
||||
/**
|
||||
The `Ember.ListView` view class renders a
|
||||
[div](https://developer.mozilla.org/en/HTML/Element/div) HTML element,
|
||||
with `ember-list-view` class.
|
||||
|
||||
The context of each item element within the `Ember.ListView` are populated
|
||||
from the objects in the `ListView`'s `content` property.
|
||||
|
||||
### `content` as an Array of Objects
|
||||
|
||||
The simplest version of an `Ember.ListView` takes an array of object as its
|
||||
`content` property. The object will be used as the `context` each item element
|
||||
inside the rendered `div`.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
App.ContributorsRoute = Ember.Route.extend({
|
||||
model: function () {
|
||||
return [
|
||||
{ name: 'Stefan Penner' },
|
||||
{ name: 'Alex Navasardyan' },
|
||||
{ name: 'Ray Cohen'}
|
||||
];
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```handlebars
|
||||
{{#ember-list items=contributors height=500 rowHeight=50}}
|
||||
{{name}}
|
||||
{{/ember-list}}
|
||||
```
|
||||
|
||||
Would result in the following HTML:
|
||||
|
||||
```html
|
||||
<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;">
|
||||
<div class="ember-list-container">
|
||||
<div id="ember186" class="ember-view ember-list-item-view" style="transform: translate(0px, 0px)">
|
||||
Stefan Penner
|
||||
</div>
|
||||
<div id="ember187" class="ember-view ember-list-item-view" style="transform: translate(0px, 50px">
|
||||
Alex Navasardyan
|
||||
</div>
|
||||
<div id="ember188" class="ember-view ember-list-item-view" style="transform: translate(0px, 100px)">
|
||||
Ray Cohen
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
By default `Ember.ListView` provides support for `height`,
|
||||
`rowHeight`, `width`, `elementWidth`, `scrollTop` parameters.
|
||||
|
||||
Note, that `height` and `rowHeight` are required parameters.
|
||||
|
||||
```handlebars
|
||||
{{#ember-list items=this height=500 rowHeight=50}}
|
||||
{{name}}
|
||||
{{/ember-list}}
|
||||
```
|
||||
|
||||
If you would like to have multiple columns in your view layout, you can
|
||||
set `width` and `elementWidth` parameters respectively.
|
||||
|
||||
```handlebars
|
||||
{{#ember-list items=this height=500 rowHeight=50 width=500 elementWidth=80}}
|
||||
{{name}}
|
||||
{{/ember-list}}
|
||||
```
|
||||
|
||||
### Extending `Ember.ListView`
|
||||
|
||||
Example:
|
||||
|
||||
```handlebars
|
||||
{{view 'list-view' content=content}}
|
||||
|
||||
<script type="text/x-handlebars" data-template-name="row_item">
|
||||
{{name}}
|
||||
</script>
|
||||
```
|
||||
|
||||
```javascript
|
||||
App.ListView = Ember.ListView.extend({
|
||||
height: 500,
|
||||
width: 500,
|
||||
elementWidth: 80,
|
||||
rowHeight: 20,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "row_item"})
|
||||
});
|
||||
```
|
||||
|
||||
@extends Ember.ContainerView
|
||||
@class ListView
|
||||
@namespace Ember
|
||||
*/
|
||||
export default Ember.ContainerView.extend(ListViewMixin, {
|
||||
css: {
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
'-webkit-overflow-scrolling': 'touch',
|
||||
'overflow-scrolling': 'touch'
|
||||
},
|
||||
|
||||
applyTransform: ListViewHelper.applyTransform,
|
||||
|
||||
_scrollTo: function(scrollTop) {
|
||||
var element = this.element;
|
||||
|
||||
if (element) { element.scrollTop = scrollTop; }
|
||||
},
|
||||
|
||||
didInsertElement: function() {
|
||||
var that = this;
|
||||
|
||||
this._updateScrollableHeight();
|
||||
|
||||
this._scroll = function(e) { that.scroll(e); };
|
||||
|
||||
Ember.$(this.element).on('scroll', this._scroll);
|
||||
},
|
||||
|
||||
willDestroyElement: function() {
|
||||
Ember.$(this.element).off('scroll', this._scroll);
|
||||
},
|
||||
|
||||
scroll: function(e) {
|
||||
this.scrollTo(e.target.scrollTop);
|
||||
},
|
||||
|
||||
scrollTo: function(y) {
|
||||
this._scrollTo(y);
|
||||
this._scrollContentTo(y);
|
||||
},
|
||||
|
||||
totalHeightDidChange: Ember.observer(function () {
|
||||
Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight);
|
||||
}, 'totalHeight'),
|
||||
|
||||
_updateScrollableHeight: function () {
|
||||
var height, state;
|
||||
|
||||
// Support old and new Ember versions
|
||||
state = this._state || this.state;
|
||||
|
||||
if (state === 'inDOM') {
|
||||
// if the list is currently displaying the emptyView, remove the height
|
||||
if (this._isChildEmptyView()) {
|
||||
height = '';
|
||||
} else {
|
||||
height = get(this, 'totalHeight');
|
||||
}
|
||||
|
||||
this.$('.ember-list-container').css({
|
||||
height: height
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
import Ember from 'ember';
|
||||
import ListItemViewMixin from './list-item-view-mixin';
|
||||
|
||||
var get = Ember.get, set = Ember.set;
|
||||
|
||||
export default Ember.View.extend(ListItemViewMixin, {
|
||||
prepareForReuse: Ember.K,
|
||||
|
||||
init: function () {
|
||||
this._super();
|
||||
var context = Ember.ObjectProxy.create();
|
||||
this.set('context', context);
|
||||
this._proxyContext = context;
|
||||
},
|
||||
|
||||
isVisible: Ember.computed('context.content', function () {
|
||||
return !!this.get('context.content');
|
||||
}),
|
||||
|
||||
updateContext: function (newContext) {
|
||||
var context = get(this._proxyContext, 'content');
|
||||
|
||||
// Support old and new Ember versions
|
||||
var state = this._state || this.state;
|
||||
|
||||
if (context !== newContext) {
|
||||
if (state === 'inDOM') {
|
||||
this.prepareForReuse(newContext);
|
||||
}
|
||||
|
||||
set(this._proxyContext, 'content', newContext);
|
||||
|
||||
if (newContext && newContext.isController) {
|
||||
set(this, 'controller', newContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1832,6 +1832,12 @@ table#user-badges {
|
|||
}
|
||||
}
|
||||
|
||||
.admin-list-item {
|
||||
width: 100%;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
// Webhook
|
||||
|
||||
.web-hook-container {
|
||||
|
|
Loading…
Reference in New Issue