oxjs/source/UI/js/List/List.js

1864 lines
62 KiB
JavaScript
Raw Normal View History

2011-11-05 16:46:53 +00:00
'use strict';
2011-05-05 18:02:56 +00:00
/*@
2012-05-31 10:32:54 +00:00
Ox.List <f> List constructor
2011-05-16 08:24:46 +00:00
options <o> Options object
2012-05-29 22:28:52 +00:00
centered <b|false> if true, and orientation is 'horizontal', then keep the selected item centered
2011-05-16 08:24:46 +00:00
construct <f|null> (data) returns the list item HTML
disableHorizontalScrolling <b|false> If true, disable scrolling
2011-09-03 23:04:18 +00:00
draggable <b|false> If true, items can be dragged
2011-05-16 08:24:46 +00:00
format <[]> ???
itemHeight <n|16> item height
2012-05-29 22:28:52 +00:00
items <a|f|null> <a> list of items, <f> (data) returns {items, size, ...}, (data, callback) returns [items]
2011-05-16 08:24:46 +00:00
itemWidth <n|16> item width
keys <a|[]> keys of the list items
2011-09-03 23:04:18 +00:00
max <n|-1> Maximum number of items that can be selected (-1 for all)
min <n|0> Minimum number of items that must be selected
2011-09-29 17:25:50 +00:00
orientation <s|vertical> 'horizontal', 'vertical' or 'both'
2011-05-16 08:24:46 +00:00
pageLength <n|100> number of items per page
query <o> Query
conditions <[o]> Conditions
key <s> Key
operator <s> Operator (like `'='` or `'!='`)
Can be `'='` (contains) `'=='` (is), `'^'` (starts with),
`'$'` (ends with), `'<'`, `'<='`, `'>'`, `'>='`, optionally
prefixed with `'!'` (not)
value <*> Value
operator <s> Operator (`'&'` or `'|'`)
2012-06-29 12:19:34 +00:00
selectAsYouType <s|''> If set to a key, enables select-as-you-type
2011-05-16 08:24:46 +00:00
selected <a|[]> ids of the selected elements
sort <a|[]> sort order
2011-09-03 23:04:18 +00:00
sortable <b|false> If true, items can be re-ordered
2012-05-29 22:28:52 +00:00
sums <[s]|[]> sums to be included in totals
type <s|'text'> type
2011-05-16 08:24:46 +00:00
unique <s|''> name of the key that acts as unique id
self <o> shared private variable
2012-05-31 10:32:54 +00:00
([options[, self]]) -> <o:Ox.Container> List object
2012-06-17 22:38:26 +00:00
init <!> init
2012-05-29 22:28:52 +00:00
add <!> item added
closepreview <!> preview closed
copy <!> copy and replace clipboard
copyadd <!> copy and add to clipboard
cut <!> cut and replace clipboard
cutadd <!> cut and add to clipboard
2012-05-29 22:28:52 +00:00
delete <!> item removed
draganddrop <!> Fires during drag
draganddropend <!> Fires on drop
2014-02-06 10:27:09 +00:00
draganddropenter <!> Fires when entering an element during drag
draganddropleave <!> Fires when leaving an element during drag
draganddroppause <!> Fires when the mouse stops moving during drag
2012-06-02 13:07:29 +00:00
draganddropstart <!> Fires when drag starts
2012-05-29 22:28:52 +00:00
paste <!> paste
load <!> list loaded
move <!> move item
2012-05-29 22:28:52 +00:00
openpreview <!> preview of selected item opened
select <!> select item
selectnext <!> selectnext
selectprevious <!> selectprevious
2012-06-17 22:38:26 +00:00
toggle <!> toggle
2011-05-05 18:02:56 +00:00
@*/
// fixme: rename the add event to new, or the delete event to remove
2011-05-05 18:02:56 +00:00
2011-04-22 22:03:10 +00:00
Ox.List = function(options, self) {
self = self || {};
var that = Ox.Container({}, self)
2011-04-22 22:03:10 +00:00
.defaults({
_tree: false,
2011-05-16 08:24:46 +00:00
centered: false,
construct: null,
disableHorizonzalScrolling: false,
2011-05-16 08:24:46 +00:00
draggable: false,
format: [],
itemHeight: 16,
items: null,
itemWidth: 16,
keys: [],
max: -1,
min: 0,
orientation: 'vertical',
pageLength: 100,
query: {conditions: [], operator: '&'},
2011-05-16 08:24:46 +00:00
selected: [],
sort: [],
sortable: false,
2012-04-04 07:06:55 +00:00
sums: [],
2011-05-16 08:24:46 +00:00
type: 'text',
unique: ''
2011-04-22 22:03:10 +00:00
})
.options(options || {})
2012-05-28 19:35:41 +00:00
.update({
draggable: updateDraggable,
2012-05-28 19:35:41 +00:00
items: function() {
if (!self.isAsync) {
updateItems();
} else {
if (Ox.isArray(self.options.items)) {
// FIXME: retarded workaround
2013-12-31 07:58:36 +00:00
self.items = self.options.items;
self.options.items = self.itemsAPI.update(self.items);
/*
self.listLength = self.options.items.length;
updateSelected();
updateSort();
*/
}
updateQuery();
2012-05-28 19:35:41 +00:00
}
},
query: function() {
that.reloadList();
},
2012-05-28 19:35:41 +00:00
selected: function() {
2012-06-30 19:59:20 +00:00
setSelected(self.options.selected);
2012-05-28 19:35:41 +00:00
},
sort: updateSort,
sortable: updateSortable
2012-05-28 19:35:41 +00:00
})
.on({
scroll: scroll
});
2011-04-22 22:03:10 +00:00
2012-04-04 07:06:55 +00:00
self.options.sort = self.options.sort.map(function(sort) {
return Ox.isString(sort) ? {
key: sort.replace(/^[\+\-]/, ''),
operator: sort[0] == '-' ? '-' : '+'
} : sort;
});
if (Ox.isArray(self.options.items) && !self.options._tree) {
2013-12-31 07:58:36 +00:00
self.items = self.options.items;
self.options.items = Ox.api(self.items, {
2012-04-04 07:06:55 +00:00
cache: true,
2012-09-03 20:27:09 +00:00
map: self.options.map,
2012-04-04 07:06:55 +00:00
sort: self.options.sort,
sums: self.options.sums,
unique: self.options.unique
});
// FIXME: this is retarded, only needed because setting items later will
// overwrite the items API.
self.itemsAPI = self.options.items;
2012-04-04 07:06:55 +00:00
}
2011-11-03 15:42:41 +00:00
that.$content.bindEvent({
2011-04-22 22:03:10 +00:00
mousedown: mousedown,
singleclick: singleclick,
2011-09-03 23:04:18 +00:00
doubleclick: doubleclick
}).on({
touchend: function(e) {
2013-05-15 07:30:31 +00:00
if (
self.touchSelection.length
&& self.options.selected[0] == self.touchSelection[0]
) {
doubleclick(e);
} else {
2013-05-15 07:30:31 +00:00
self.touchSelection = Ox.clone(self.options.selected);
}
}
2011-04-22 22:03:10 +00:00
});
self.options.draggable && updateDraggable();
self.options.sortable && updateSortable();
2011-04-22 22:03:10 +00:00
// fixme: without this, horizontal lists don't get their full width
self.options.orientation == 'horizontal' && that.$content.css({height: '1px'});
Ox.extend(self, {
2011-04-22 22:03:10 +00:00
$items: [],
$pages: [],
format: {},
//isAsync: true,
isAsync: !self.options._tree,
2011-04-22 22:03:10 +00:00
itemMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px margin ... fixme: the 2x should be computed later
keyboardEvents: {
2013-07-14 17:45:45 +00:00
key_control_c: function() {
copyItems();
},
key_control_shift_c: function() {
copyItems(true);
},
key_control_e: editItems,
key_control_n: function() {
addItem('');
},
key_alt_control_n: function() {
addItem('alt');
},
2013-07-14 17:45:45 +00:00
key_alt_control_shift_n: function() {
addItem('alt_shift');
},
2013-07-14 17:45:45 +00:00
key_control_shift_n: function() {
addItem('shift');
},
2011-04-22 22:03:10 +00:00
key_control_v: pasteItems,
2013-08-03 14:47:25 +00:00
key_control_x: function() {
cutItems();
},
2013-07-14 17:45:45 +00:00
key_control_shift_x: function() {
cutItems(true);
},
2011-04-22 22:03:10 +00:00
key_delete: deleteItems,
2011-09-29 17:25:50 +00:00
key_end: scrollToLast,
2011-04-22 22:03:10 +00:00
key_enter: open,
2011-09-29 17:25:50 +00:00
key_home: scrollToFirst,
2011-04-22 22:03:10 +00:00
key_pagedown: scrollPageDown,
key_pageup: scrollPageUp,
key_section: preview, // fixme: firefox gets keyCode 0 when pressing space
key_space: preview
},
listMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px padding
page: 0,
preview: false,
requests: [],
scrollTimeout: 0,
2013-05-15 07:30:31 +00:00
selected: [],
touchSelection: []
2011-04-22 22:03:10 +00:00
});
if (!self.isAsync) {
self.selected = self.options.items.map(function(item, i) {
return Ox.extend(item, {_index: i})
}).filter(function(item) {
2014-09-26 08:59:06 +00:00
return Ox.contains(self.options.selected, item[self.options.unique]);
}).map(function(item) {
return item['_index'];
});
}
self.options.max == -1 && Ox.extend(self.keyboardEvents, {
key_alt_control_a: function() {
that.invertSelection();
},
key_control_a: function() {
that.selectAll();
}
2011-04-22 22:03:10 +00:00
});
self.options.min == 0 && Ox.extend(self.keyboardEvents, {
2011-04-22 22:03:10 +00:00
key_control_shift_a: selectNone
});
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'up' : 'left')
] = selectPrevious;
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'down' : 'right')
] = selectNext;
if (self.options.max == -1) {
self.keyboardEvents[
'key_shift_' + (self.options.orientation == 'vertical' ? 'up' : 'left')
2011-04-22 22:03:10 +00:00
] = addPreviousToSelection;
self.keyboardEvents[
'key_shift_' + (self.options.orientation == 'vertical' ? 'down' : 'right'
2012-06-29 12:19:34 +00:00
)
2011-04-22 22:03:10 +00:00
] = addNextToSelection;
}
if (self.options.orientation == 'vertical') {
Ox.extend(self.keyboardEvents, {
2011-04-22 22:03:10 +00:00
key_left: function() {
triggerToggleEvent(false);
},
key_right: function() {
triggerToggleEvent(true);
}
});
} else if (self.options.orientation == 'both') {
Ox.extend(self.keyboardEvents, {
2011-04-22 22:03:10 +00:00
key_down: selectBelow,
key_up: selectAbove
});
if (self.options.max == -1) {
Ox.extend(self.keyboardEvents, {
2011-04-22 22:03:10 +00:00
key_shift_down: addBelowToSelection,
key_shift_up: addAboveToSelection
});
}
self.pageLengthByRowLength = [
2014-01-18 02:23:45 +00:00
0, 60, 60, 60, 60, 60, 60, 63, 64, 72,
80, 88, 96, 104, 112, 120, 128, 136, 144, 152,
160, 168, 176, 184, 192
2011-04-22 22:03:10 +00:00
];
}
2012-06-29 12:19:34 +00:00
if (self.options.selectAsYouType) {
Ox.extend(self.keyboardEvents, {keys: selectAsYouType});
}
that.bindEvent(self.keyboardEvents);
2011-04-22 22:03:10 +00:00
!self.isAsync ? updateItems() : updateQuery();
2011-04-22 22:03:10 +00:00
function addAboveToSelection() {
var pos = getAbove();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addAllToSelection(pos) {
2011-12-19 17:19:54 +00:00
var arr, i, len = self.$items.length;
2011-04-22 22:03:10 +00:00
if (!isSelected(pos)) {
if (self.selected.length == 0) {
addToSelection(pos);
} else {
2011-12-19 17:19:54 +00:00
arr = [pos];
2011-04-22 22:03:10 +00:00
if (Ox.min(self.selected) < pos) {
2012-05-30 17:22:24 +00:00
for (i = pos - 1; i >= 0; i--) {
if (isSelected(i)) {
break;
}
arr.push(i);
}
2011-04-22 22:03:10 +00:00
}
if (Ox.max(self.selected) > pos) {
2011-12-19 17:19:54 +00:00
for (i = pos + 1; i < len; i++) {
2011-04-22 22:03:10 +00:00
if (isSelected(i)) {
break;
}
arr.push(i);
}
}
2011-12-19 17:19:54 +00:00
addToSelection(arr);
2011-04-22 22:03:10 +00:00
}
}
}
function addBelowToSelection() {
var pos = getBelow();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addItem(keys) {
2012-06-29 12:19:34 +00:00
that.triggerEvent('add', {keys: keys});
2011-04-22 22:03:10 +00:00
}
function addNextToSelection() {
var pos = getNext();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addPreviousToSelection() {
var pos = getPrevious();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addToSelection(pos) {
Ox.makeArray(pos).reverse().forEach(function(pos) {
2011-12-19 17:19:54 +00:00
if (!isSelected(pos)) {
self.selected.unshift(pos);
2012-06-29 12:19:34 +00:00
if (!Ox.isUndefined(self.$items[pos])) {
self.$items[pos].addClass('OxSelected');
}
2011-12-19 17:19:54 +00:00
} else {
2014-02-02 13:18:09 +00:00
// allow for 'cursor navigation' within selection
2011-12-19 17:19:54 +00:00
self.selected.splice(self.selected.indexOf(pos), 1);
self.selected.unshift(pos);
2011-12-19 17:19:54 +00:00
}
});
triggerSelectEvent();
2011-04-22 22:03:10 +00:00
}
function clear() {
2011-11-02 10:23:15 +00:00
self.requests.forEach(function(request) {
Ox.Request.cancel(request);
2011-04-22 22:03:10 +00:00
});
Ox.extend(self, {
2011-09-28 17:31:35 +00:00
$items: [],
2011-04-22 22:03:10 +00:00
$pages: [],
page: 0,
requests: []
});
}
function constructEmptyPage(page) {
var i, $page = Ox.Element().addClass('OxPage').css(getPageCSS(page));
Ox.loop(getPageLength(page), function() {
Ox.ListItem({
2011-04-22 22:03:10 +00:00
construct: self.options.construct
2011-05-16 08:24:46 +00:00
}).appendTo($page);
});
2011-04-22 22:03:10 +00:00
return $page;
}
function copyItems(add) {
2013-07-14 17:45:45 +00:00
self.options.selected.length && that.triggerEvent('copy' + (add ? 'add' : ''), {
ids: self.options.selected
2011-04-22 22:03:10 +00:00
});
}
function cutItems(add) {
self.options.selected.length && that.triggerEvent('cut' + (add ? 'add' : ''), {
ids: self.options.selected
});
2011-04-22 22:03:10 +00:00
}
function deleteItems() {
self.options.selected.length && that.triggerEvent('delete', {
ids: self.options.selected
2011-04-22 22:03:10 +00:00
});
}
function deselect(pos) {
2011-12-19 17:19:54 +00:00
var triggerEvent = false;
Ox.makeArray(pos).forEach(function(pos) {
2011-12-19 17:19:54 +00:00
if (isSelected(pos)) {
self.selected.splice(self.selected.indexOf(pos), 1);
!Ox.isUndefined(self.$items[pos])
&& self.$items[pos].removeClass('OxSelected');
triggerEvent = true;
}
});
triggerEvent && triggerSelectEvent();
2011-04-22 22:03:10 +00:00
}
2012-06-29 12:19:34 +00:00
function doubleclick(data) {
open(isSpecialTarget(data));
}
2011-09-17 11:49:29 +00:00
function dragstart(data) {
var $target = $(data.target),
2011-09-04 12:19:36 +00:00
$parent = $target.parent();
2011-10-17 11:26:04 +00:00
if ((
$target.is('.OxTarget') // icon lists
2012-06-27 07:43:28 +00:00
|| $parent.is('.OxTarget') // table lists
|| $parent.parent().is('.OxTarget') // table lists with div inside cell
2011-10-17 11:26:04 +00:00
) && !$target.is('.OxSpecialTarget')) {
Ox.$body.addClass('OxDragging');
2011-09-03 23:04:18 +00:00
self.drag = {
ids: self.options.selected
2011-09-04 22:06:40 +00:00
};
2011-09-03 23:04:18 +00:00
// fixme: shouldn't the target have been
// automatically passed already, somewhere?
that.triggerEvent('draganddropstart', {
event: data,
ids: self.drag.ids
2011-04-22 22:03:10 +00:00
});
2011-09-03 23:04:18 +00:00
}
}
2011-11-03 15:42:41 +00:00
2011-09-17 11:49:29 +00:00
function drag(data) {
2011-09-04 22:06:40 +00:00
self.drag && that.triggerEvent('draganddrop', {
event: data,
ids: self.drag.ids
2011-09-04 22:06:40 +00:00
});
2011-09-03 23:04:18 +00:00
}
2011-09-17 11:49:29 +00:00
function dragpause(data) {
2011-09-04 22:06:40 +00:00
self.drag && that.triggerEvent('draganddroppause', {
event: data,
ids: self.drag.ids
2011-09-04 22:06:40 +00:00
});
2011-04-22 22:03:10 +00:00
}
2011-09-17 11:49:29 +00:00
function dragenter(data) {
2011-09-04 22:06:40 +00:00
self.drag && that.triggerEvent('draganddropenter', {
event: data,
ids: self.drag.ids
2011-09-04 22:06:40 +00:00
});
2011-04-22 22:03:10 +00:00
}
2011-09-17 11:49:29 +00:00
function dragleave(data) {
2011-09-04 22:06:40 +00:00
self.drag && that.triggerEvent('draganddropleave', {
event: data,
ids: self.drag.ids
2011-09-04 22:06:40 +00:00
});
2011-04-22 22:03:10 +00:00
}
2011-09-17 11:49:29 +00:00
function dragend(data) {
2011-09-03 23:04:18 +00:00
if (self.drag) {
Ox.$body.removeClass('OxDragging');
2011-09-03 23:04:18 +00:00
that.triggerEvent('draganddropend', {
event: data,
ids: self.drag.ids
2011-04-22 22:03:10 +00:00
});
2011-09-03 23:04:18 +00:00
delete self.drag;
}
2011-04-22 22:03:10 +00:00
}
function editItems() {
/*
self.options.selected.length && that.triggerEvent('edit', {
ids: self.options.selected
});
*/
}
2011-04-22 22:03:10 +00:00
function emptyFirstPage() {
if (self.$pages[0]) {
if (self.options.type == 'text') {
self.$pages[0].find('.OxEmpty').each(function() {
2014-09-26 12:18:11 +00:00
Ox.UI.getElement($(this)).remove();
});
} else if (self.options.orientation == 'both') {
that.$content.css({height: getListSize() + 'px'});
}
}
2011-04-22 22:03:10 +00:00
}
function fillFirstPage() {
if (self.$pages[0]) {
if (self.options.type == 'text') {
var height = getHeight(),
lastItemHeight = height % self.options.itemHeight || self.options.itemHeight,
visibleItems = Math.ceil(height / self.options.itemHeight);
if (self.listLength < visibleItems) {
Ox.range(self.listLength, visibleItems).forEach(function(i) {
var $item = Ox.ListItem({
2012-05-26 15:48:19 +00:00
construct: self.options.construct
});
$item.addClass('OxEmpty').removeClass('OxTarget');
if (i == visibleItems - 1) {
$item.$element.css({
height: lastItemHeight + 'px',
overflowY: 'hidden'
});
}
$item.appendTo(self.$pages[0]);
});
}
} else if (self.options.orientation == 'both') {
var height = getHeight(),
listSize = getListSize();
if (listSize < height) {
that.$content.css({height: height + 'px'});
}
2011-04-22 22:03:10 +00:00
}
}
}
function findCell(e) {
var $element = $(e.target);
while (
!$element.is('.OxCell')
&& !$element.is('.OxPage')
&& !$element.is('body')
) {
2011-04-22 22:03:10 +00:00
$element = $element.parent();
}
return $element.is('.OxCell') ? $element : null;
2011-04-22 22:03:10 +00:00
}
function findItemPosition(e) {
var $element = $(e.target),
$parent,
2011-04-22 22:03:10 +00:00
position = -1;
while (
!$element.is('.OxTarget') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length
) {
$element = $parent;
2011-04-22 22:03:10 +00:00
}
if ($element.is('.OxTarget')) {
while (
!$element.is('.OxItem') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length
) {
$element = $parent;
2011-04-22 22:03:10 +00:00
}
if ($element.is('.OxItem')) {
2011-04-22 22:03:10 +00:00
position = $element.data('position');
}
}
return position;
}
function getAbove() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[0] - self.rowLength;
2011-04-22 22:03:10 +00:00
if (pos < 0) {
pos = -1;
}
}
return pos;
}
function getBelow() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[0] + self.rowLength;
2011-04-22 22:03:10 +00:00
if (pos >= self.$items.length) {
pos = -1;
}
}
return pos;
}
function getHeight() {
return that.height() - (
!self.options.disableHorizontalScrolling
&& that.$content.width() > that.width()
2014-09-26 12:18:11 +00:00
? Ox.UI.SCROLLBAR_SIZE : 0
);
2011-04-22 22:03:10 +00:00
}
function getListSize() {
return Math.ceil(self.listLength / self.rowLength) * (self.options[
self.options.orientation == 'horizontal' ? 'itemWidth' : 'itemHeight'
] + self.itemMargin);
2011-04-22 22:03:10 +00:00
}
function getNext() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[0] + 1;
2011-04-22 22:03:10 +00:00
if (pos == self.$items.length) {
pos = -1;
}
}
return pos;
}
function getPage() {
return Math.max(
Math.floor(self.options.orientation == 'horizontal' ?
(that.scrollLeft() - self.listMargin / 2) / self.pageWidth :
(that.scrollTop() - self.listMargin / 2) / self.pageHeight
), 0);
}
function getPageByPosition(pos) {
return Math.floor(pos / self.pageLength);
2011-04-22 22:03:10 +00:00
}
function getPageByScrollPosition(pos) {
return getPageByPosition(pos / (
self.options.orientation == 'vertical'
? self.options.itemHeight
: self.options.itemWidth
));
}
2011-04-22 22:03:10 +00:00
function getPageCSS(page) {
return self.options.orientation == 'horizontal' ? {
left: (page * self.pageWidth + self.listMargin / 2) + 'px',
top: (self.listMargin / 2) + 'px',
width: (page < self.pages - 1 ? self.pageWidth :
getPageLength(page) * (self.options.itemWidth + self.itemMargin)) + 'px'
} : {
top: (page * self.pageHeight + self.listMargin / 2) + 'px',
width: self.pageWidth + 'px'
};
2011-04-22 22:03:10 +00:00
}
function getPageHeight() {
return Math.ceil(self.pageLength * (self.options.itemHeight + self.itemMargin) / self.rowLength);
}
2011-09-01 04:46:14 +00:00
function getPageLength(page) {
var mod = self.listLength % self.pageLength;
return page < self.pages - 1 || (self.listLength && mod == 0) ? self.pageLength : mod;
2011-09-01 04:46:14 +00:00
}
2011-04-22 22:03:10 +00:00
function getPositionById(id) {
var pos = -1;
Ox.forEach(self.$items, function($item, i) {
if ($item.options('data')[self.options.unique] == id) {
pos = i;
2012-07-05 08:58:08 +00:00
return false; // break
2011-04-22 22:03:10 +00:00
}
});
return pos;
}
function getPositions(callback) {
2011-04-22 22:03:10 +00:00
// fixme: optimize: send non-selected ids if more than half of the items are selected
if (self.options.selected.length/* && ids.length < self.listLength*/) {
2011-04-22 22:03:10 +00:00
self.requests.push(self.options.items({
2011-06-01 10:59:12 +00:00
positions: self.options.selected,
query: self.options.query,
2011-04-22 22:03:10 +00:00
sort: self.options.sort
}, function(result) {
getPositionsCallback(result, callback);
}));
2011-04-22 22:03:10 +00:00
} else {
getPositionsCallback(null, callback);
2011-04-22 22:03:10 +00:00
}
}
function getPositionsCallback(result, callback) {
2011-11-04 15:54:28 +00:00
//Ox.Log('List', 'getPositionsCallback', result);
var pos = 0, previousSelected = self.options.selected;
2011-04-22 22:03:10 +00:00
if (result) {
self.options.selected = [];
self.positions = {};
self.selected = [];
2011-04-22 22:03:10 +00:00
Ox.forEach(result.data.positions, function(pos, id) {
// fixme: in case the order of self.options.selected
// is important - it may get lost here
if (pos > -1) {
self.options.selected.push(id);
self.selected.push(pos);
}
2011-04-22 22:03:10 +00:00
});
if (self.selected.length) {
pos = Ox.min(self.selected);
self.page = getPageByPosition(pos);
}
if (!Ox.isEqual(self.options.selected, previousSelected)) {
that.triggerEvent('select', {ids: self.options.selected});
}
} else if (self.stayAtPosition) {
self.page = getPageByScrollPosition(self.stayAtPosition);
2011-04-22 22:03:10 +00:00
}
that.$content.empty();
loadPages(self.page, function() {
scrollToPosition(pos, true);
callback && callback();
2011-04-22 22:03:10 +00:00
});
}
function getPrevious() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[0] - 1;
2011-04-22 22:03:10 +00:00
}
return pos;
}
function getRow(pos) {
return Math.floor(pos / self.rowLength);
}
function getRowLength() {
return self.options.orientation == 'both' ? Math.floor(
(getWidth() - self.listMargin)
/ (self.options.itemWidth + self.itemMargin)
) : 1;
2011-04-22 22:03:10 +00:00
}
function getScrollPosition() {
// if orientation is both, this returns the
// element position at the current scroll position
return Math.floor(
2011-04-22 22:03:10 +00:00
that.scrollTop() / (self.options.itemHeight + self.itemMargin)
) * self.rowLength;
}
2011-12-19 17:19:54 +00:00
function getSelectedIds(callback) {
var ids = [], notFound = false;
if (self.$items.length == 0) {
2011-12-19 17:19:54 +00:00
callback(self.options.selected);
} else {
2011-12-19 17:19:54 +00:00
Ox.forEach(self.selected, function(pos) {
if (self.$items[pos]) {
ids.push(self.$items[pos].options('data')[self.options.unique]);
} else {
notFound = true;
2012-07-05 08:58:08 +00:00
return false; // break
2011-12-19 17:19:54 +00:00
}
});
2011-12-19 17:19:54 +00:00
if (notFound) {
// selection across items that are not in the DOM
self.options.items({
keys: [self.options.unique],
query: self.options.query,
2011-12-19 17:19:54 +00:00
range: [0, self.listLength],
sort: self.options.sort
}, function(result) {
var ids = [], rest = [],
useRest = self.selected.length > self.listLength / 2;
result.data.items.forEach(function(item, i) {
2014-09-26 08:59:06 +00:00
if (isSelected(i)) {
2011-12-19 17:19:54 +00:00
ids.push(item[self.options.unique]);
} else if (useRest) {
rest.push(item[self.options.unique]);
}
});
useRest ? callback(ids, rest) : callback(ids);
});
} else {
callback(ids);
}
}
2011-04-22 22:03:10 +00:00
}
function getWidth() {
2011-11-04 15:54:28 +00:00
//Ox.Log('List', 'LIST THAT.WIDTH()', that.width())
return that.width() - (
2014-09-26 12:18:11 +00:00
that.$content.height() > that.height() ? Ox.UI.SCROLLBAR_SIZE : 0
);
2011-04-22 22:03:10 +00:00
}
function isSelected(pos) {
2014-09-26 08:59:06 +00:00
return Ox.contains(self.selected, pos);
2011-04-22 22:03:10 +00:00
}
2011-10-17 10:23:16 +00:00
function isSpecialTarget(e) {
var $element = $(e.target),
$parent;
while (
!$element.is('.OxSpecialTarget') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length
) {
$element = $parent;
}
return $element.is('.OxSpecialTarget');
}
2011-04-22 22:03:10 +00:00
function loadItems() {
self.$pages[0].empty();
2011-09-28 17:31:35 +00:00
self.$items = [];
2014-02-07 11:07:53 +00:00
////var timeC = 0, timeA = 0;
2011-04-22 22:03:10 +00:00
self.options.items.forEach(function(item, pos) {
// fixme: duplicated
var time0 = +new Date();
self.$items[pos] = Ox.ListItem({
2011-04-22 22:03:10 +00:00
construct: self.options.construct,
data: item,
position: pos,
unique: self.options.unique
});
2014-02-07 11:07:53 +00:00
////timeC += +new Date() - time0;
2011-04-22 22:03:10 +00:00
isSelected(pos) && self.$items[pos].addClass('OxSelected');
2014-02-07 11:07:53 +00:00
////var time0 = +new Date();
self.$items[pos].appendTo(self.$pages[0]);
2014-02-07 11:07:53 +00:00
////timeA += +new Date() - time0;
2011-04-22 22:03:10 +00:00
});
// timeout needed so that height is present
setTimeout(fillFirstPage, 0);
self.selected.length && scrollToPosition(self.selected[0]);
2014-02-07 11:07:53 +00:00
////Ox.Log('List', 'CONSTRUCT:', timeC, 'APPEND:', timeA);
2012-01-30 23:27:27 +00:00
that.triggerEvent('init', {items: self.options.items.length});
// fixme: do sync lists need to trigger init?
2011-04-22 22:03:10 +00:00
}
function loadPage(page, callback) {
if (page < 0 || page >= self.pages) {
!Ox.isUndefined(callback) && callback();
return;
}
2012-05-22 14:08:09 +00:00
Ox.Log('List', that.oxid, 'loadPage', page);
2012-06-27 18:29:39 +00:00
var keys = Ox.unique(self.options.keys.concat(self.options.unique)),
2011-04-22 22:03:10 +00:00
offset = page * self.pageLength,
range = [offset, offset + getPageLength(page)];
if (!Ox.isUndefined(self.$pages[page])) {
Ox.Log('List', 'fixme: unload should have made this undefined already');
}
self.$pages[page] = constructEmptyPage(page);
page == 0 && fillFirstPage();
self.$pages[page].appendTo(that.$content);
self.requests.push(self.options.items({
keys: keys,
query: self.options.query,
range: range,
sort: self.options.sort
}, function(result) {
var $emptyPage = self.$pages[page];
self.$pages[page] = Ox.Element().addClass('OxPage').css(getPageCSS(page));
result.data.items.forEach(function(v, i) {
var pos = offset + i;
self.$items[pos] = Ox.ListItem({
construct: self.options.construct,
data: v,
//format: self.options.format,
position: pos,
unique: self.options.unique
});
isSelected(pos) && self.$items[pos].addClass('OxSelected');
self.$items[pos].appendTo(self.$pages[page]);
});
page == 0 && fillFirstPage();
// FIXME: why does emptyPage sometimes have no methods?
//Ox.Log('List', 'emptyPage', $emptyPage)
$emptyPage && $emptyPage.remove && $emptyPage.remove();
2011-04-22 22:03:10 +00:00
self.$pages[page].appendTo(that.$content);
!Ox.isUndefined(callback) && callback(); // fixme: callback necessary? why not bind to event?
}));
2011-04-22 22:03:10 +00:00
}
function loadPages(page, callback) {
Ox.Log('List', 'loadPages', page);
2011-04-22 22:03:10 +00:00
var counter = 0,
fn = function() {
if (++counter == 3) {
!Ox.isUndefined(callback) && callback();
that.triggerEvent('load');
}
};
// fixme: find out which option is better
/*
loadPage(page, function() {
loadPage(page - 1, fn);
loadPage(page + 1, fn);
});
*/
loadPage(page, fn);
loadPage(page - 1, fn);
loadPage(page + 1, fn);
}
2011-09-17 11:49:29 +00:00
function mousedown(data) {
var pos = findItemPosition(data);
2011-04-22 22:03:10 +00:00
that.gainFocus();
self.mousedownOnSelectedCell = false;
2011-04-22 22:03:10 +00:00
if (pos > -1) {
2011-09-17 11:49:29 +00:00
if (data.metaKey) {
2011-04-22 22:03:10 +00:00
if (!isSelected(pos) && (self.options.max == -1 || self.options.max > self.selected.length)) {
// meta-click on unselected item
addToSelection(pos);
} else if (isSelected(pos) && self.options.min < self.selected.length) {
// meta-click on selected item
deselect(pos);
}
2011-09-17 11:49:29 +00:00
} else if (data.shiftKey) {
if (!isSelected(pos) && self.options.max == -1) {
// shift-click on unselected item
2011-04-22 22:03:10 +00:00
addAllToSelection(pos);
} else if (isSelected(pos)) {
// shift-click on selected item
addToSelection(pos);
2011-04-22 22:03:10 +00:00
}
} else if (!isSelected(pos) && self.options.max != 0) {
2011-04-22 22:03:10 +00:00
// click on unselected item
select(pos);
} else if (self.options.type == 'text') {
2012-06-27 07:43:28 +00:00
// click on a selected table list cell
self.mousedownOnSelectedCell = true;
2011-04-22 22:03:10 +00:00
}
2011-09-17 11:49:29 +00:00
} else if (!$(data.target).is('.OxToggle') && self.options.min == 0) {
2011-04-22 22:03:10 +00:00
// click on empty area
selectNone();
}
// note: we have to save if the mousedown was on a selected cell
// since otherwise, mousedown would select a previously unselected item,
// and the subsequent singleclick might trigger an unwanted edit event.
2011-04-22 22:03:10 +00:00
}
2011-09-17 11:49:29 +00:00
function movestart(data) {
Ox.$body.addClass('OxDragging');
var pos = findItemPosition(data),
$items = self.$items.filter(function($item, i) {
if ($item.is('.OxSelected')) {
$item.addClass('OxDrag');
return true;
}
return false;
});
2011-09-03 23:04:18 +00:00
self.drag = {
$items: $items,
index: Ox.indexOf($items, function($item) {
return $item.options('position') == pos;
}),
length: $items.length,
startPos: pos,
2011-09-17 11:49:29 +00:00
startY: data.clientY,
stopPos: pos
};
2011-09-03 23:04:18 +00:00
}
2011-09-17 11:49:29 +00:00
function move(data) {
var clientY = data.clientY - that.offset().top,
2011-09-03 23:04:18 +00:00
offset = clientY % 16,
position = Ox.limit(
Math.floor(clientY / 16),
0, self.$items.length - 1
);
if (position < self.drag.startPos) {
2011-09-03 23:04:18 +00:00
self.drag.stopPos = position + (offset > 8 ? 1 : 0);
} else if (position > self.drag.startPos) {
2011-09-03 23:04:18 +00:00
self.drag.stopPos = position - (offset <= 8 ? 1 : 0);
}
if (self.drag.stopPos != self.drag.startPos) {
moveItems(self.drag.startPos, self.drag.stopPos);
self.drag.startPos = self.drag.stopPos;
2011-09-03 23:04:18 +00:00
}
}
2011-09-17 11:49:29 +00:00
function moveend(data) {
var ids = [];
Ox.$body.removeClass('OxDragging');
self.$items.forEach(function($item) {
$item.removeClass('OxDrag');
ids.push($item.options('data')[self.options.unique]);
2011-09-03 23:04:18 +00:00
});
that.triggerEvent('move', {ids: ids});
2011-09-03 23:04:18 +00:00
delete self.drag;
}
function moveItems(startPos, stopPos) {
var pos = stopPos;
while (self.$items[pos].is('.OxSelected')) {
pos = pos + (pos < startPos ? -1 : 1);
if (pos < 0 || pos > self.$items.length - 1) {
// handle item can still be moved, but group cannot
return;
}
}
self.drag.$items.forEach(function($item) {
$item.detach();
});
self.drag.$items.forEach(function($item, i) {
if (i == 0) {
$item[
pos < startPos ? 'insertBefore' : 'insertAfter'
](self.$items[pos].$element); // fixme: shouldn't require $element
} else {
$item.insertAfter(self.drag.$items[i - 1]);
}
});
self.drag.$items.forEach(function($item, i) {
self.$items.splice($item.options('position') - i, 1);
});
self.$items.splice.apply(
self.$items,
[stopPos - self.drag.index, 0].concat(self.drag.$items)
);
2011-04-22 22:03:10 +00:00
self.$items.forEach(function($item, pos) {
$item.options({position: pos});
2011-04-22 22:03:10 +00:00
});
self.selected = [];
self.drag.$items.forEach(function($item) {
self.selected.push($item.options('position'));
});
2011-04-22 22:03:10 +00:00
}
2011-10-17 10:23:16 +00:00
function open(isSpecialTarget) {
self.options.selected.length && that.triggerEvent('open', {
2011-10-17 10:23:16 +00:00
ids: self.options.selected,
isSpecialTarget: isSpecialTarget == true
2011-04-22 22:03:10 +00:00
});
}
function pasteItems() {
that.triggerEvent('paste');
2011-04-22 22:03:10 +00:00
}
function preview() {
if (self.options.selected.length) {
2011-04-22 22:03:10 +00:00
self.preview = !self.preview;
if (self.preview) {
that.triggerEvent('openpreview', {
ids: self.options.selected
2011-04-22 22:03:10 +00:00
});
} else {
that.triggerEvent('closepreview');
}
}
}
function scroll() {
if (self.isAsync) {
2011-04-22 22:03:10 +00:00
var page = self.page;
self.scrollTimeout && clearTimeout(self.scrollTimeout);
self.scrollTimeout = setTimeout(function() {
self.scrollTimeout = 0;
self.page = getPage();
if (self.page == page - 1) {
unloadPage(self.page + 2);
loadPage(self.page - 1);
} else if (self.page == page + 1) {
unloadPage(self.page - 2);
loadPage(self.page + 1);
} else if (self.page == page - 2) {
unloadPage(self.page + 3);
unloadPage(self.page + 2);
loadPage(self.page);
loadPage(self.page - 1);
} else if (self.page == page + 2) {
unloadPage(self.page - 3);
unloadPage(self.page - 2);
loadPage(self.page);
loadPage(self.page + 1);
} else if (self.page != page) {
unloadPages(page);
loadPages(self.page);
}
}, 250);
}
//that.gainFocus();
}
function scrollPageDown() {
2011-11-03 16:05:24 +00:00
that.scrollTop(that.scrollTop() + getHeight());
2011-04-22 22:03:10 +00:00
}
function scrollPageUp() {
2011-11-03 16:05:24 +00:00
that.scrollTop(that.scrollTop() - getHeight());
2011-04-22 22:03:10 +00:00
}
function scrollTo(value) {
that.animate(self.options.orientation == 'horizontal' ? {
scrollLeft: (self.listSize * value) + 'px'
} : {
scrollTop: (self.listSize * value) + 'px'
}, 0);
}
function scrollToFirst() {
that[self.options.orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop'](0);
}
function scrollToLast() {
that[self.options.orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop'](self.listSize);
}
function scrollToPosition(pos, leftOrTopAlign) {
2011-11-04 15:54:28 +00:00
//Ox.Log('List', 'scrollToPosition', pos)
2011-04-22 22:03:10 +00:00
var itemHeight = self.options.itemHeight + self.itemMargin,
itemWidth = self.options.itemWidth + self.itemMargin,
positions = [],
scroll,
size;
if (self.options.orientation == 'horizontal') {
if (self.options.centered) {
that.stop().animate({
2012-06-30 09:18:55 +00:00
scrollLeft: self.listMargin / 2 + (pos + 0.5) * itemWidth
- that.width() / 2 + 'px'
2011-08-17 11:39:33 +00:00
}, 250);
2011-04-22 22:03:10 +00:00
} else {
positions[0] = pos * itemWidth + self.listMargin / 2;
positions[1] = positions[0] + itemWidth + self.itemMargin / 2;
scroll = that.scrollLeft();
size = getWidth();
2012-06-30 09:18:55 +00:00
// fixme: css instead of animate(0)?
2011-04-22 22:03:10 +00:00
if (positions[0] < scroll || leftOrTopAlign) {
2012-06-30 09:18:55 +00:00
that.animate({scrollLeft: positions[0] + 'px'}, 0);
2011-04-22 22:03:10 +00:00
} else if (positions[1] > scroll + size) {
2012-06-30 09:18:55 +00:00
that.animate({scrollLeft: (positions[1] - size) + 'px'}, 0);
2011-04-22 22:03:10 +00:00
}
}
} else {
2012-06-30 09:18:55 +00:00
positions[0] = (
self.options.orientation == 'vertical' ? pos : getRow(pos)
) * itemHeight;
positions[1] = positions[0] + itemHeight + (
self.options.orientation == 'vertical' ? 0 : self.itemMargin
);
2011-04-22 22:03:10 +00:00
scroll = that.scrollTop();
size = getHeight();
2012-06-30 09:18:55 +00:00
// fixme: css instead of animate(0)?
2011-04-22 22:03:10 +00:00
if (positions[0] < scroll || leftOrTopAlign) {
2012-06-30 09:18:55 +00:00
that.animate({scrollTop: positions[0] + 'px'}, 0);
2011-04-22 22:03:10 +00:00
} else if (positions[1] > scroll + size) {
2012-06-30 09:18:55 +00:00
that.animate({scrollTop: (positions[1] - size) + 'px'}, 0);
2011-04-22 22:03:10 +00:00
}
}
}
function select(pos) {
if (!isSelected(pos) || self.selected.length > 1) {
setSelected([pos]);
triggerSelectEvent();
2011-04-22 22:03:10 +00:00
self.options.centered && scrollToPosition(pos);
}
}
function selectAbove() {
var pos = getAbove();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
2012-06-29 12:19:34 +00:00
function selectAsYouType(data) {
2012-06-29 12:19:34 +00:00
self.options.items({
keys: [self.options.unique],
query: {
conditions: [
self.options.query,
{
key: self.options.selectAsYouType,
operator: '^',
value: data.keys
},
],
operator: '&'
},
range: [0, 1],
sort: [{key: self.options.selectAsYouType, operator: '+'}]
}, function(result) {
result.data.items.length && that.options({
selected: [result.data.items[0][self.options.unique]]
});
});
}
2011-04-22 22:03:10 +00:00
function selectBelow() {
var pos = getBelow();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
function selectNext() {
var pos = getNext();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
} else if (self.selected.length) {
that.triggerEvent('selectnext');
2011-04-22 22:03:10 +00:00
}
}
function selectNone() {
deselect(Ox.clone(self.selected));
2011-04-22 22:03:10 +00:00
}
function selectPrevious() {
var pos = getPrevious();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
} else if (self.selected.length) {
that.triggerEvent('selectprevious');
2011-04-22 22:03:10 +00:00
}
}
function selectQuery(str) {
Ox.forEach(self.$items, function(v, i) {
if (Ox.toLatin(v.title).toUpperCase().indexOf(str) == 0) {
select(i);
scrollToPosition(i);
2012-07-05 08:58:08 +00:00
return false; // break
2011-04-22 22:03:10 +00:00
}
});
}
function setSelected(ids, callback) {
// ids can be positions
2012-06-30 19:59:20 +00:00
// fixme: callback is never used
2011-09-29 17:25:50 +00:00
// note: can't use selectNone here,
2011-04-22 22:03:10 +00:00
// since it'd trigger a select event
var counter = 0;
2011-04-22 22:03:10 +00:00
self.$items.forEach(function($item, pos) {
if (isSelected(pos)) {
2012-06-29 12:19:34 +00:00
if (!Ox.isUndefined(self.$items[pos])) {
2011-04-22 22:03:10 +00:00
self.$items[pos].removeClass('OxSelected');
2012-06-29 12:19:34 +00:00
}
2011-05-16 08:24:46 +00:00
}
2011-04-22 22:03:10 +00:00
});
self.selected = [];
2011-04-22 22:03:10 +00:00
ids.forEach(function(id, i) {
var pos = Ox.isString(id) ? getPositionById(id) : id;
if (pos > -1) {
select(pos, i);
} else if (self.isAsync) {
// async and id not in current view
self.options.items({
2011-06-01 10:59:12 +00:00
positions: [id],
query: self.options.query,
sort: self.options.sort
2011-06-01 13:51:35 +00:00
}, function(result) {
pos = result.data.positions[id];
select(pos, i);
});
}
});
function select(pos, i) {
2011-04-22 22:03:10 +00:00
self.selected.push(pos);
2012-06-29 12:19:34 +00:00
if (!Ox.isUndefined(self.$items[pos])) {
2011-04-22 22:03:10 +00:00
self.$items[pos].addClass('OxSelected');
2012-06-29 12:19:34 +00:00
}
2011-05-24 06:15:44 +00:00
i == 0 && scrollToPosition(pos);
++counter == ids.length && callback && callback();
}
2011-04-22 22:03:10 +00:00
}
2012-06-29 12:19:34 +00:00
function singleclick(data) {
// these can't trigger on mousedown, since the mousedown
// could still be the start of a doubleclick or drag
var pos = findItemPosition(data),
$cell, clickable, editable;
if (pos > -1) {
if (
!data.metaKey && !data.shiftKey
&& isSelected(pos) && self.selected.length > 1
) {
// click on one of multiple selected items
select(pos);
} else if (self.mousedownOnSelectedCell) {
$cell = findCell(data);
if ($cell) {
clickable = $cell.is('.OxClickable');
editable = $cell.is('.OxEditable') && !$cell.is('.OxEdit');
if (clickable || editable) {
// click on a clickable or editable cell
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
}
}
}
}
}
2011-04-22 22:03:10 +00:00
function toggleSelection(pos) {
2011-12-19 17:19:54 +00:00
// FIXME: unused
2011-04-22 22:03:10 +00:00
if (!isSelected(pos)) {
addToSelection(pos);
} else {
deselect(pos);
}
}
function triggerClickEvent(event, $item, $cell) {
// event can be 'click' or 'edit'
var key;
if ($cell) {
key = $cell.attr('class').split('OxColumn')[1].split(' ')[0];
key = key[0].toLowerCase() + key.slice(1);
}
that.triggerEvent(event, Ox.extend({
2011-04-22 22:03:10 +00:00
id: $item.data('id')
}, key ? {key: key} : {}));
2011-04-22 22:03:10 +00:00
}
var triggerSelectEvent = Ox.debounce(function() {
// throttle in case shift+arrow is pressed
getSelectedIds(function(ids, rest) {
2011-12-19 17:19:54 +00:00
self.options.selected = ids;
that.triggerEvent('select', Ox.extend({
ids: ids
}, rest ? {
rest: rest
} : {}));
if (self.preview) {
if (ids.length) {
that.triggerEvent('openpreview', {
ids: ids
});
} else {
that.triggerEvent('closepreview');
}
}
});
}, true);
2011-04-22 22:03:10 +00:00
function triggerToggleEvent(expanded) {
that.triggerEvent('toggle', {
expanded: expanded,
ids: self.options.selected
2011-04-22 22:03:10 +00:00
});
}
function unloadPage(page) {
if (page < 0 || page >= self.pages) {
return;
}
2011-11-04 15:54:28 +00:00
//Ox.Log('List', 'unloadPage', page)
//Ox.Log('List', 'self.$pages', self.$pages)
//Ox.Log('List', 'page not undefined', !Ox.isUndefined(self.$pages[page]))
2011-04-22 22:03:10 +00:00
if (!Ox.isUndefined(self.$pages[page])) {
self.$pages[page].remove();
2011-04-22 22:03:10 +00:00
delete self.$pages[page];
}
}
function unloadPages(page) {
unloadPage(page);
unloadPage(page - 1);
unloadPage(page + 1)
}
function updateDraggable() {
that.$content[self.options.draggable ? 'bindEvent' : 'unbindEvent']({
dragstart: dragstart,
drag: drag,
dragpause: dragpause,
dragenter: dragenter,
dragleave: dragleave,
dragend: dragend
});
}
function updateItems() {
clear();
that.$content.empty();
self.$pages = [];
self.$pages[0] = Ox.Element()
.addClass('OxPage')
.css({
left: self.listMargin / 2 + 'px',
top: self.listMargin / 2 + 'px'
})
.appendTo(that.$content);
self.listLength = self.options.items.length;
loadItems();
}
2011-04-22 22:03:10 +00:00
function updatePages(pos, scroll) {
// only used if orientation is both
clear();
self.pageLength = self.pageLengthByRowLength[self.rowLength]
Ox.extend(self, {
2011-04-22 22:03:10 +00:00
listSize: getListSize(),
pages: Math.ceil(self.listLength / self.pageLength),
pageWidth: (self.options.itemWidth + self.itemMargin) * self.rowLength,
pageHeight: getPageHeight()
});
that.$content.css({
height: self.listSize + 'px'
});
self.page = getPageByPosition(pos);
//that.scrollTop(0);
that.$content.empty();
loadPages(self.page, function() {
scrollTo(scroll);
});
}
function updatePositions() {
self.$items.forEach(function($item, pos) {
$item.options({position: pos});
2011-04-22 22:03:10 +00:00
});
}
function updateQuery(callback) { // fixme: shouldn't this be setQuery?
2013-06-04 12:27:09 +00:00
var data;
self.requests.push(data = self.options.items({
query: self.options.query
}, function(result) {
2011-04-22 22:03:10 +00:00
var keys = {};
// timeout needed since a synchronous items function
// will reach here before one can bind to the init event,
// and before any sizes can be determined via the DOM
setTimeout(function() {
clear(); // fixme: bad function name ... clear what?
2013-02-12 01:31:45 +00:00
that.triggerEvent(
'init',
2013-06-04 12:27:09 +00:00
Ox.extend(
result.data,
data && data.query ? {query: data.query} : {}
)
2013-02-12 01:31:45 +00:00
);
self.rowLength = getRowLength();
self.pageLength = self.options.orientation == 'both'
? self.pageLengthByRowLength[self.rowLength]
: self.options.pageLength;
Ox.extend(self, {
listLength: result.data.items,
pages: Math.max(Math.ceil(result.data.items / self.pageLength), 1),
pageWidth: self.options.orientation == 'vertical'
? 0 : (self.options.itemWidth + self.itemMargin) * (
self.options.orientation == 'horizontal'
? self.pageLength : self.rowLength
),
pageHeight: self.options.orientation == 'horizontal'
? 0 : Math.ceil(self.pageLength * (
self.options.itemHeight + self.itemMargin
) / self.rowLength)
});
self.listSize = getListSize();
that.$content.css(
self.options.orientation == 'horizontal' ? 'width' : 'height',
self.listSize + 'px'
);
getPositions(callback);
});
2011-04-22 22:03:10 +00:00
}));
}
2011-05-24 11:56:53 +00:00
function updateSelected() {
2011-11-04 15:54:28 +00:00
//Ox.Log('List', 'updateSelected')
2011-12-19 17:19:54 +00:00
getSelectedIds(function(oldIds) {
var newIds = [];
Ox.forEach(self.options.items, function(item) {
2014-09-26 08:59:06 +00:00
if (Ox.contains(oldIds, item.id)) {
2011-12-19 17:19:54 +00:00
newIds.push(item.id);
}
2012-07-05 08:58:08 +00:00
if (newIds.length == oldIds.length) {
return false; // break
}
2011-12-19 17:19:54 +00:00
});
setSelected(newIds);
2011-05-24 11:56:53 +00:00
});
}
2011-05-24 12:50:16 +00:00
function updateSort() {
var length = self.options.sort.length,
operator = [],
sort = [];
//if (self.listLength > 1) {
if (!self.isAsync) {
2011-12-19 17:19:54 +00:00
getSelectedIds(function(selectedIds) {
self.options.sort.forEach(function(v, i) {
operator.push(v.operator);
sort.push({});
self.options.items.forEach(function(item) {
sort[i][item.id] = v.map
? v.map(item[v.key], item)
: item[v.key]
});
});
2011-12-19 17:19:54 +00:00
self.options.items.sort(function(a, b) {
var aValue, bValue, index = 0, ret = 0;
while (ret == 0 && index < length) {
aValue = sort[index][a.id];
bValue = sort[index][b.id];
if (aValue < bValue) {
ret = operator[index] == '+' ? -1 : 1;
} else if (aValue > bValue) {
ret = operator[index] == '+' ? 1 : -1;
} else {
index++;
}
}
2011-12-19 17:19:54 +00:00
return ret;
});
2011-12-19 17:19:54 +00:00
if (selectedIds.length) {
self.selected = [];
self.options.items.forEach(function(item, i) {
2014-09-26 08:59:06 +00:00
if (Ox.contains(selectedIds, item.id)) {
2011-12-19 17:19:54 +00:00
self.selected.push(i);
}
});
}
loadItems();
});
2011-04-22 22:03:10 +00:00
} else {
clear(); // fixme: bad function name
getPositions();
}
//}
2011-04-22 22:03:10 +00:00
}
function updateSortable() {
that.$content[self.options.sortable ? 'bindEvent' : 'unbindEvent']({
dragstart: movestart,
drag: move,
dragend: moveend
});
}
2011-05-16 08:24:46 +00:00
/*@
addItems <f> add item to list
(items) -> <u> add items at the end of the list
(pos, items) -> <u> add items at position
2011-05-16 08:24:46 +00:00
pos <n> position to add items
items <a> array of items to add
2011-05-16 08:24:46 +00:00
@*/
2011-04-22 22:03:10 +00:00
that.addItems = function(pos, items) {
if (arguments.length == 1) {
items = pos;
pos = self.listLength;
}
2011-04-22 22:03:10 +00:00
var $items = [],
length = items.length;
2011-04-22 22:03:10 +00:00
self.selected.forEach(function(v, i) {
if (v >= pos) {
self.selected[i] += length;
}
});
items.forEach(function(item, i) {
var $item;
$items.push($item = Ox.ListItem({
2011-04-22 22:03:10 +00:00
construct: self.options.construct,
data: item,
position: pos + i,
unique: self.options.unique
}));
if (i == 0) {
if (pos == 0) {
$item.insertBefore(self.$items[0]);
} else {
$item.insertAfter(self.$items[pos - 1]);
}
} else {
$item.insertAfter($items[i - 1]);
}
});
2012-05-24 07:45:33 +00:00
self.options.items.splice.apply(self.options.items, [pos, 0].concat(items));
self.$items.splice.apply(self.$items, [pos, 0].concat($items));
self.listLength = self.options.items.length;
//loadItems();
2011-04-22 22:03:10 +00:00
updatePositions();
2012-04-08 12:38:15 +00:00
emptyFirstPage();
fillFirstPage();
2012-05-30 17:22:24 +00:00
};
2011-04-22 22:03:10 +00:00
2011-08-15 14:11:13 +00:00
/*@
closePreview <f> to be called when preview is closed externally
() -> <o> the list
2011-08-15 14:11:13 +00:00
@*/
that.closePreview = function() {
self.preview = false;
that.triggerEvent('closepreview');
2011-08-15 14:11:13 +00:00
return that;
};
/*@
clearCache <f> empty list cache
() -> <o> the list
@*/
2012-06-27 07:41:10 +00:00
that.clearCache = function() { // fixme: was used by TableList resizeColumn, now probably no longer necessary
2011-08-15 14:11:13 +00:00
self.$pages = [];
return that;
};
2014-02-12 13:56:54 +00:00
that.getPasteIndex = function() {
return self.selected.length
? Ox.max(self.selected) + 1
: self.items.length;
};
/*@
invertSelection <f> Invert selection
() -> <o> The list
@*/
that.invertSelection = function() {
var arr = Ox.range(self.listLength).filter(function(pos) {
return !isSelected(pos);
});
setSelected(arr);
triggerSelectEvent();
return that;
};
/*@
openPreview <f> to be called when preview is opened externally
() -> <o> the list
@*/
that.openPreview = function() {
self.preview = true;
that.triggerEvent('openpreview', {ids: self.options.selected});
return that;
};
2011-05-16 08:24:46 +00:00
/*@
reloadList <f> reload list contents
2011-08-07 22:16:06 +00:00
() -> <o> the list
2011-05-16 08:24:46 +00:00
@*/
that.reloadList = function(stayAtPosition) {
2011-08-07 22:16:06 +00:00
var scrollTop = that.scrollTop();
if (!self.isAsync) {
loadItems();
scrollList();
} else {
2011-08-07 22:16:06 +00:00
updateQuery(scrollList);
}
function scrollList() {
stayAtPosition && that.scrollTop(scrollTop);
}
2011-04-22 22:03:10 +00:00
return that;
};
2011-05-16 08:24:46 +00:00
/*@
reloadPages <f> reload list pages
2011-08-07 22:16:06 +00:00
() -> <o> the list
2011-05-16 08:24:46 +00:00
@*/
2011-04-22 22:03:10 +00:00
that.reloadPages = function() {
2012-06-27 07:41:10 +00:00
// this is called by TableList when the column layout changes
var page, scrollLeft, scrollTop;
if (!self.isAsync) {
scrollLeft = that.scrollLeft();
scrollTop = that.scrollTop();
loadItems();
that.scrollLeft(scrollLeft).scrollTop(scrollTop);
} else {
page = self.page;
clear();
self.page = page
that.$content.empty();
loadPages(self.page);
}
2011-04-22 22:03:10 +00:00
return that;
};
2011-05-16 08:24:46 +00:00
/*@
removeItems <f> remove items from list
(ids) -> <u> remove items
(pos, length) -> <u> remove items
ids <a> array of item ids
pos <n> remove items starting at this position
2011-05-16 08:24:46 +00:00
length <n> number of items to remove
@*/
2011-04-22 22:03:10 +00:00
that.removeItems = function(pos, length) {
2011-11-04 15:54:28 +00:00
Ox.Log('List', 'removeItems', pos, length)
if (Ox.isUndefined(length)) { // pos is list of ids
2011-04-22 22:03:10 +00:00
pos.forEach(function(id) {
var p = getPositionById(id);
that.removeItems(p, 1);
});
} else { // remove items from pos to pos+length
2011-04-22 22:03:10 +00:00
Ox.range(pos, pos + length).forEach(function(i) {
2014-09-26 08:59:06 +00:00
isSelected(i) && deselect(i);
self.$items[i].remove();
2011-04-22 22:03:10 +00:00
});
self.options.items.splice(pos, length);
2011-04-22 22:03:10 +00:00
self.$items.splice(pos, length);
self.selected.forEach(function(v, i) {
if (v >= pos + length) {
self.selected[i] -= length;
}
});
self.listLength = self.options.items.length;
2011-04-22 22:03:10 +00:00
updatePositions();
}
2012-04-08 12:38:15 +00:00
emptyFirstPage();
fillFirstPage();
2012-05-30 17:22:24 +00:00
};
2011-05-16 08:24:46 +00:00
/*@
scrollToSelection <f> scroll list to current selection
() -> <o> List Object
2011-05-16 08:24:46 +00:00
@*/
2011-04-22 22:03:10 +00:00
that.scrollToSelection = function() {
self.selected.length && scrollToPosition(self.selected[0]);
return that;
};
/*@
selectAll <f> Select all
() -> <o> The list
@*/
that.selectAll = function() {
addToSelection(Ox.range(self.listLength));
return that;
};
/*@
selectPosition <f> select position
(pos) -> <o> List Object
@*/
that.selectPosition = function(pos) {
select(pos);
return that;
};
/*@
selectSelected <f> Change the first selected item within the selection
(offset) -> <o> List Object
offset <n> Offset (`-1` for previous, `1` for next)
@*/
that.selectSelected = function(offset) {
var pos, positions;
if (self.selected.length > 1) {
positions = Ox.sort(Ox.clone(self.selected));
pos = positions[
Ox.mod(positions.indexOf(self.selected[0]) + offset, positions.length)
];
addToSelection(pos);
scrollToPosition(pos);
}
return that;
};
2011-05-16 08:24:46 +00:00
/*@
2014-09-26 08:59:06 +00:00
size <f> Update list size
() -> <o> List Object
2011-05-16 08:24:46 +00:00
@*/
2011-04-22 22:03:10 +00:00
that.size = function() { // fixme: not a good function name
if (self.options.orientation == 'both') {
var rowLength = getRowLength(),
pageLength = self.pageLengthByRowLength[rowLength],
pos = getScrollPosition(),
scroll = that.scrollTop() / self.listSize;
if (pageLength != self.pageLength) {
self.pageLength = pageLength;
self.rowLength = rowLength;
updatePages(pos, scroll);
} else if (rowLength != self.rowLength) {
self.rowLength = rowLength;
self.pageWidth = (self.options.itemWidth + self.itemMargin) * self.rowLength; // fixme: make function
self.listSize = getListSize();
self.pageHeight = getPageHeight();
self.$pages.forEach(function($page, i) {
!Ox.isUndefined($page) && $page.css({
width: self.pageWidth + 'px',
top: (i * self.pageHeight + self.listMargin / 2) + 'px'
});
});
that.$content.css({
height: self.listSize + 'px'
});
scrollTo(scroll);
}
emptyFirstPage();
fillFirstPage();
2011-04-22 22:03:10 +00:00
} else if (self.options.type == 'text') {
emptyFirstPage();
fillFirstPage();
}
return that;
2012-05-30 17:22:24 +00:00
};
2012-05-21 10:38:18 +00:00
/*@
sort <f> sort
@*/
2011-05-24 14:51:40 +00:00
// needed when a value has changed
// but, fixme: better function name
that.sort = function() {
updateSort();
2012-05-30 17:22:24 +00:00
};
2011-05-24 14:51:40 +00:00
2011-05-16 08:24:46 +00:00
/*@
sortList <f> sort list
(key, operator) -> <f> returns List Element
key <s> key to sort list by
operator <s> +/- sort ascending or descending
map <f> function that maps values to sort values
2011-05-16 08:24:46 +00:00
@*/
2011-05-24 12:50:16 +00:00
// fixme: this (and others) should be deprecated,
// one should set options instead
that.sortList = function(key, operator, map) {
2011-11-04 15:54:28 +00:00
// Ox.Log('List', 'sortList', key, operator, map)
2011-04-22 22:03:10 +00:00
if (key != self.options.sort[0].key || operator != self.options.sort[0].operator) {
2011-05-24 12:50:16 +00:00
self.options.sort[0] = {key: key, operator: operator, map: map};
updateSort();
2011-04-22 22:03:10 +00:00
that.triggerEvent('sort', self.options.sort[0]);
}
return that;
2012-05-30 17:22:24 +00:00
};
2011-04-22 22:03:10 +00:00
2011-05-16 08:24:46 +00:00
/*@
value <f> get/set list value
(id, {key: value, ...}) -> <f> sets value, returns List Element
2011-05-16 08:24:46 +00:00
(id, key, value) -> <f> sets value, returns List Element
(id, key) -> <a> returns value
(id) -> <o> returns all values of id
id <s|n> id of item (or a number, will be interpreted as position)
2011-05-16 08:24:46 +00:00
key <s> key if item property
value <*> value, can be whatever that property is
2011-05-16 08:24:46 +00:00
@*/
that.value = function() {
var args = Ox.slice(arguments),
id = args.shift(),
pos = Ox.isNumber(id) ? id : getPositionById(id),
2011-04-22 22:03:10 +00:00
$item = self.$items[pos],
data = $item ? $item.options('data') : {},
updateItems = false;
2011-04-22 22:03:10 +00:00
if (arguments.length == 1) {
return data;
} else if (arguments.length == 2 && Ox.isString(arguments[1])) {
return data[arguments[1]];
} else if ($item) {
Ox.forEach(Ox.makeObject(args), function(value, key) {
if (key == self.options.unique) {
// unique id has changed
self.options.selected = self.options.selected.map(function(id_) {
return id_ == data[key] ? value : id_
});
}
if (!self.isAsync) {
self.options.items[pos][key] = value;
} else if (self.items) {
// items array was passed to initialize the list
self.items[
Ox.getIndex(self.items, self.options.unique, id)
][key] = value;
updateItems = true;
}
data[key] = value;
});
2011-04-22 22:03:10 +00:00
$item.options({data: data});
if (updateItems) {
self.options.items.update(self.items);
}
2011-04-22 22:03:10 +00:00
return that;
}
};
return that;
};