mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
FIX: List views in admin were broken
This commit is contained in:
parent
a74689932e
commit
0d51c1f0a0
21 changed files with 1474 additions and 1942 deletions
|
@ -3,8 +3,8 @@ export default Ember.ObjectController.extend({
|
|||
savedIpAddress: null,
|
||||
|
||||
isRange: function() {
|
||||
return this.get("ip_address").indexOf("/") > 0;
|
||||
}.property("ip_address"),
|
||||
return this.get("model.ip_address").indexOf("/") > 0;
|
||||
}.property("model.ip_address"),
|
||||
|
||||
actions: {
|
||||
allow: function(record) {
|
||||
|
@ -19,14 +19,14 @@ export default Ember.ObjectController.extend({
|
|||
|
||||
edit: function() {
|
||||
if (!this.get('editing')) {
|
||||
this.savedIpAddress = this.get('ip_address');
|
||||
this.savedIpAddress = this.get('model.ip_address');
|
||||
}
|
||||
this.set('editing', true);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (this.get('savedIpAddress') && this.get('editing')) {
|
||||
this.set('ip_address', this.get('savedIpAddress'));
|
||||
this.set('model.ip_address', this.get('savedIpAddress'));
|
||||
}
|
||||
this.set('editing', false);
|
||||
},
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
/**
|
||||
Represents an IP address that is watched for during account registration
|
||||
(and possibly other times), and an action is taken.
|
||||
|
||||
@class ScreenedIpAddress
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ScreenedIpAddress = Discourse.Model.extend({
|
||||
actionName: function() {
|
||||
|
@ -17,21 +12,9 @@ Discourse.ScreenedIpAddress = Discourse.Model.extend({
|
|||
}.property('action_name'),
|
||||
|
||||
actionIcon: function() {
|
||||
if (this.get('action_name') === 'block') {
|
||||
return this.get('blockIcon');
|
||||
} else {
|
||||
return this.get('doNothingIcon');
|
||||
}
|
||||
return (this.get('action_name') === 'block') ? 'ban' : 'check';
|
||||
}.property('action_name'),
|
||||
|
||||
blockIcon: function() {
|
||||
return 'fa-ban';
|
||||
}.property(),
|
||||
|
||||
doNothingIcon: function() {
|
||||
return 'fa-check';
|
||||
}.property(),
|
||||
|
||||
save: function() {
|
||||
return Discourse.ajax("/admin/logs/screened_ip_addresses" + (this.id ? '/' + this.id : '') + ".json", {
|
||||
type: this.id ? 'PUT' : 'POST',
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<div class="col first ip_address">
|
||||
{{#if editing}}
|
||||
{{text-field value=ip_address autofocus="autofocus"}}
|
||||
{{text-field value=model.ip_address autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "edit" this}}>
|
||||
{{#if isRange}}
|
||||
<strong>{{ip_address}}</strong>
|
||||
<strong>{{model.ip_address}}</strong>
|
||||
{{else}}
|
||||
{{ip_address}}
|
||||
{{model.ip_address}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col action">
|
||||
<i {{bind-attr class=":fa actionIcon"}}></i>
|
||||
{{actionName}}
|
||||
{{fa-icon model.actionIcon}}
|
||||
{{model.actionName}}
|
||||
</div>
|
||||
<div class="col match_count">{{match_count}}</div>
|
||||
<div class="col match_count">{{model.match_count}}</div>
|
||||
<div class="col last_match_at">
|
||||
{{#if last_match_at}}
|
||||
{{age-with-tooltip last_match_at}}
|
||||
{{#if model.last_match_at}}
|
||||
{{age-with-tooltip model.last_match_at}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col created_at">{{age-with-tooltip created_at}}</div>
|
||||
<div class="col created_at">{{age-with-tooltip model.created_at}}</div>
|
||||
<div class="col actions">
|
||||
{{#unless editing}}
|
||||
<button class="btn btn-danger" {{action "destroy" this}}><i class="fa fa-trash-o"></i></button>
|
||||
<button class="btn" {{action "edit" this}}><i class="fa fa-pencil"></i></button>
|
||||
{{#if isBlocked}}
|
||||
<button class="btn" {{action "allow" this}}><i {{bind-attr class=":fa doNothingIcon"}}></i> {{i18n 'admin.logs.screened_ips.actions.do_nothing'}}</button>
|
||||
{{#if model.isBlocked}}
|
||||
<button class="btn" {{action "allow" this}}>{{fa-icon "check"}} {{i18n 'admin.logs.screened_ips.actions.do_nothing'}}</button>
|
||||
{{else}}
|
||||
<button class="btn" {{action "block" this}}><i {{bind-attr class=":fa blockIcon"}}></i> {{i18n 'admin.logs.screened_ips.actions.block'}}</button>
|
||||
<button class="btn" {{action "block" this}}>{{fa-icon "ban"}} {{i18n 'admin.logs.screened_ips.actions.block'}}</button>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<button class="btn" {{action "save" this}}>{{i18n 'admin.logs.save'}}</button>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
Discourse.ScreenedEmailsListView = Ember.ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "admin/templates/logs/screened_emails_list_item"})
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
Discourse.ScreenedIpAddressesListView = Ember.ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "admin/templates/logs/screened_ip_addresses_list_item"})
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
Discourse.ScreenedUrlsListView = Ember.ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 32,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "admin/templates/logs/screened_urls_list_item"})
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
Discourse.StaffActionLogsListView = Ember.ListView.extend({
|
||||
height: 700,
|
||||
rowHeight: 75,
|
||||
itemViewClass: Ember.ListItemView.extend({templateName: "admin/templates/logs/staff_action_logs_list_item"})
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
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"})
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
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"})
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
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"})
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
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,6 +1,11 @@
|
|||
/*global Favcount:true*/
|
||||
var DiscourseResolver = require('discourse/ember/resolver').default;
|
||||
|
||||
// Allow us to import Ember
|
||||
define('ember', ['exports'], function(__exports__) {
|
||||
__exports__["default"] = Ember;
|
||||
});
|
||||
|
||||
window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||
rootElement: '#main',
|
||||
_docTitle: document.title,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
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();
|
||||
}
|
||||
});
|
57
app/assets/javascripts/ember-addons/list-item-view.js.es6
Normal file
57
app/assets/javascripts/ember-addons/list-item-view.js.es6
Normal file
|
@ -0,0 +1,57 @@
|
|||
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')
|
||||
});
|
94
app/assets/javascripts/ember-addons/list-view-helper.js.es6
Normal file
94
app/assets/javascripts/ember-addons/list-view-helper.js.es6
Normal file
|
@ -0,0 +1,94 @@
|
|||
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();
|
||||
})()
|
||||
};
|
886
app/assets/javascripts/ember-addons/list-view-mixin.js.es6
Normal file
886
app/assets/javascripts/ember-addons/list-view-mixin.js.es6
Normal file
|
@ -0,0 +1,886 @@
|
|||
// 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;
|
||||
}
|
||||
});
|
167
app/assets/javascripts/ember-addons/list-view.js.es6
Normal file
167
app/assets/javascripts/ember-addons/list-view.js.es6
Normal file
|
@ -0,0 +1,167 @@
|
|||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
//= require list-view
|
||||
//= require_tree ./ember-addons
|
||||
//= require admin/models/user-field
|
||||
//= require admin/models/site-setting
|
||||
//= require admin/controllers/admin-email-skipped
|
||||
|
|
1851
vendor/assets/javascripts/list-view.js
vendored
1851
vendor/assets/javascripts/list-view.js
vendored
File diff suppressed because it is too large
Load diff
168
vendor/assets/javascripts/loader.js
vendored
168
vendor/assets/javascripts/loader.js
vendored
|
@ -1,69 +1,166 @@
|
|||
var define, requireModule, require, requirejs, hasModule;
|
||||
var define, requireModule, require, requirejs;
|
||||
|
||||
(function() {
|
||||
var registry = {}, seen = {}, state = {};
|
||||
|
||||
var _isArray;
|
||||
if (!Array.isArray) {
|
||||
_isArray = function (x) {
|
||||
return Object.prototype.toString.call(x) === "[object Array]";
|
||||
};
|
||||
} else {
|
||||
_isArray = Array.isArray;
|
||||
}
|
||||
|
||||
var registry = {};
|
||||
var seen = {};
|
||||
var FAILED = false;
|
||||
|
||||
var uuid = 0;
|
||||
|
||||
function tryFinally(tryable, finalizer) {
|
||||
try {
|
||||
return tryable();
|
||||
} finally {
|
||||
finalizer();
|
||||
}
|
||||
}
|
||||
|
||||
function unsupportedModule(length) {
|
||||
throw new Error("an unsupported module was defined, expected `define(name, deps, module)` instead got: `" + length + "` arguments to define`");
|
||||
}
|
||||
|
||||
var defaultDeps = ['require', 'exports', 'module'];
|
||||
|
||||
function Module(name, deps, callback, exports) {
|
||||
this.id = uuid++;
|
||||
this.name = name;
|
||||
this.deps = !deps.length && callback.length ? defaultDeps : deps;
|
||||
this.exports = exports || { };
|
||||
this.callback = callback;
|
||||
this.state = undefined;
|
||||
this._require = undefined;
|
||||
}
|
||||
|
||||
|
||||
Module.prototype.makeRequire = function() {
|
||||
var name = this.name;
|
||||
|
||||
return this._require || (this._require = function(dep) {
|
||||
return require(resolve(dep, name));
|
||||
});
|
||||
}
|
||||
|
||||
define = function(name, deps, callback) {
|
||||
registry[name] = {
|
||||
deps: deps,
|
||||
callback: callback
|
||||
};
|
||||
if (arguments.length < 2) {
|
||||
unsupportedModule(arguments.length);
|
||||
}
|
||||
|
||||
if (!_isArray(deps)) {
|
||||
callback = deps;
|
||||
deps = [];
|
||||
}
|
||||
|
||||
registry[name] = new Module(name, deps, callback);
|
||||
};
|
||||
|
||||
function reify(deps, name, seen) {
|
||||
// we don't support all of AMD
|
||||
// define.amd = {};
|
||||
// we will support petals...
|
||||
define.petal = { };
|
||||
|
||||
function Alias(path) {
|
||||
this.name = path;
|
||||
}
|
||||
|
||||
define.alias = function(path) {
|
||||
return new Alias(path);
|
||||
};
|
||||
|
||||
function reify(mod, name, seen) {
|
||||
var deps = mod.deps;
|
||||
var length = deps.length;
|
||||
var reified = new Array(length);
|
||||
var dep;
|
||||
var exports;
|
||||
// TODO: new Module
|
||||
// TODO: seen refactor
|
||||
var module = { };
|
||||
|
||||
for (var i = 0, l = length; i < l; i++) {
|
||||
dep = deps[i];
|
||||
if (dep === 'exports') {
|
||||
exports = reified[i] = seen;
|
||||
module.exports = reified[i] = seen;
|
||||
} else if (dep === 'require') {
|
||||
reified[i] = mod.makeRequire();
|
||||
} else if (dep === 'module') {
|
||||
mod.exports = seen;
|
||||
module = reified[i] = mod;
|
||||
} else {
|
||||
reified[i] = require(resolve(dep, name));
|
||||
reified[i] = requireFrom(resolve(dep, name), name);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deps: reified,
|
||||
exports: exports
|
||||
module: module
|
||||
};
|
||||
}
|
||||
|
||||
hasModule = function(name){
|
||||
return !!registry[name];
|
||||
};
|
||||
function requireFrom(name, origin) {
|
||||
var mod = registry[name];
|
||||
if (!mod) {
|
||||
throw new Error('Could not find module `' + name + '` imported from `' + origin + '`');
|
||||
}
|
||||
return require(name);
|
||||
}
|
||||
|
||||
function missingModule(name) {
|
||||
throw new Error('Could not find module ' + name);
|
||||
}
|
||||
requirejs = require = requireModule = function(name) {
|
||||
if (state[name] !== FAILED &&
|
||||
var mod = registry[name];
|
||||
|
||||
|
||||
if (mod && mod.callback instanceof Alias) {
|
||||
mod = registry[mod.callback.name];
|
||||
}
|
||||
|
||||
if (!mod) { missingModule(name); }
|
||||
|
||||
if (mod.state !== FAILED &&
|
||||
seen.hasOwnProperty(name)) {
|
||||
return seen[name];
|
||||
}
|
||||
|
||||
if (!registry[name]) {
|
||||
throw new Error('Could not find module ' + name);
|
||||
}
|
||||
|
||||
var mod = registry[name];
|
||||
var reified;
|
||||
var module;
|
||||
var loaded = false;
|
||||
|
||||
seen[name] = { }; // placeholder for run-time cycles
|
||||
|
||||
try {
|
||||
reified = reify(mod.deps, name, seen[name]);
|
||||
tryFinally(function() {
|
||||
reified = reify(mod, name, seen[name]);
|
||||
module = mod.callback.apply(this, reified.deps);
|
||||
loaded = true;
|
||||
} finally {
|
||||
}, function() {
|
||||
if (!loaded) {
|
||||
state[name] = FAILED;
|
||||
mod.state = FAILED;
|
||||
}
|
||||
});
|
||||
|
||||
var obj;
|
||||
if (module === undefined && reified.module.exports) {
|
||||
obj = reified.module.exports;
|
||||
} else {
|
||||
obj = seen[name] = module;
|
||||
}
|
||||
|
||||
return reified.exports ? seen[name] : (seen[name] = module);
|
||||
if (obj !== null &&
|
||||
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||
obj['default'] === undefined) {
|
||||
obj['default'] = obj;
|
||||
}
|
||||
|
||||
return (seen[name] = obj);
|
||||
};
|
||||
|
||||
function resolve(child, name) {
|
||||
|
@ -71,27 +168,26 @@ var define, requireModule, require, requirejs, hasModule;
|
|||
|
||||
var parts = child.split('/');
|
||||
var nameParts = name.split('/');
|
||||
var parentBase;
|
||||
|
||||
if (nameParts.length === 1) {
|
||||
parentBase = nameParts;
|
||||
} else {
|
||||
parentBase = nameParts.slice(0, -1);
|
||||
}
|
||||
var parentBase = nameParts.slice(0, -1);
|
||||
|
||||
for (var i = 0, l = parts.length; i < l; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if (part === '..') { parentBase.pop(); }
|
||||
else if (part === '.') { continue; }
|
||||
else { parentBase.push(part); }
|
||||
if (part === '..') {
|
||||
if (parentBase.length === 0) {
|
||||
throw new Error('Cannot access parent module of root');
|
||||
}
|
||||
parentBase.pop();
|
||||
} else if (part === '.') {
|
||||
continue;
|
||||
} else { parentBase.push(part); }
|
||||
}
|
||||
|
||||
return parentBase.join('/');
|
||||
}
|
||||
|
||||
requirejs.entries = requirejs._eak_seen = registry;
|
||||
requirejs.clear = function(){
|
||||
requirejs.clear = function() {
|
||||
requirejs.entries = requirejs._eak_seen = registry = {};
|
||||
seen = state = {};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue