305 lines
9.3 KiB
JavaScript
305 lines
9.3 KiB
JavaScript
'use strict';
|
|
|
|
/*@
|
|
Ox.TreeList <f> Tree List
|
|
options <o> Options object
|
|
data <f|null> Data to be parsed as items (needs documentation)
|
|
expanded <b|false> If true, and data is not null, all items are expanded
|
|
icon <o|f|null> Image URL, or function that returns an image object
|
|
items <a|[]> Array of items
|
|
max <n|-1> Maximum number of items that can be selected, -1 unlimited
|
|
min <n|0> Minimum number of items that have to be selected
|
|
selected <a|[]> Selected ids
|
|
width <n|256> List width
|
|
self <o> Shared private variable
|
|
([options[, self]]) -> <o:Ox.List> Tree List Object
|
|
@*/
|
|
|
|
Ox.TreeList = function(options, self) {
|
|
|
|
// fixme: expanding the last item should cause some scroll
|
|
|
|
self = self || {};
|
|
var that = Ox.Element({}, self)
|
|
.defaults({
|
|
data: null,
|
|
expanded: false,
|
|
icon: null,
|
|
items: [],
|
|
max: 1,
|
|
min: 0,
|
|
selected: [],
|
|
width: 'auto'
|
|
})
|
|
.options(options || {})
|
|
.update({
|
|
data: function() {
|
|
self.options.items = getItems();
|
|
self.$list.options({items: parseItems()});
|
|
},
|
|
selected: function() {
|
|
//self.$list.options({selected: self.options.selected});
|
|
selectItem({ids: self.options.selected});
|
|
self.$list.scrollToSelection();
|
|
},
|
|
width: function() {
|
|
// ...
|
|
}
|
|
});
|
|
|
|
if (self.options.data) {
|
|
self.options.items = getItems();
|
|
}
|
|
|
|
that.setElement(
|
|
self.$list = Ox.List({
|
|
_tree: true,
|
|
construct: constructItem,
|
|
itemHeight: 16,
|
|
items: parseItems(),
|
|
itemWidth: self.options.width,
|
|
keys: ['expanded', 'id', 'items', 'level', 'title'],
|
|
max: self.options.max,
|
|
min: self.options.min,
|
|
unique: 'id'
|
|
})
|
|
.addClass('OxTableList OxTreeList')
|
|
.css({
|
|
width: self.options.width + 'px',
|
|
overflowY: 'scroll'
|
|
})
|
|
.bindEvent(function(data, event) {
|
|
if (event == 'anyclick') {
|
|
clickItem(data);
|
|
} else if (event == 'toggle') {
|
|
toggleItems(data);
|
|
}
|
|
that.triggerEvent(event, data);
|
|
})
|
|
);
|
|
|
|
self.options.selected.length && selectItem({ids: self.options.selected});
|
|
|
|
function clickItem(e) {
|
|
var $target = $(e.target),
|
|
$item, id, item;
|
|
if ($target.is('.OxToggle')) {
|
|
$item = $target.parent().parent();
|
|
id = $item.data('id');
|
|
item = getItemById(id);
|
|
toggleItem(item, !item.expanded);
|
|
}
|
|
}
|
|
|
|
function constructItem(data) {
|
|
var $item = $('<div>').css({
|
|
width: self.options.width == 'auto' ? '100%'
|
|
: self.options.width - Ox.SCROLLBAR_SIZE + 'px'
|
|
}),
|
|
$cell = $('<div>').addClass('OxCell').css({width: '8px'}),
|
|
$icon = data.id ? getIcon(data.id, data.expanded || (
|
|
data.items ? false : null
|
|
)) : null,
|
|
padding = data.level * 16 - 8;
|
|
if (data.level) {
|
|
$('<div>')
|
|
.addClass('OxCell OxTarget')
|
|
.css({width: padding + 'px'})
|
|
.appendTo($item);
|
|
}
|
|
$cell.appendTo($item);
|
|
$icon && $icon.addClass(data.items ? 'OxToggle' : 'OxTarget').appendTo($cell);
|
|
$('<div>')
|
|
.addClass('OxCell OxTarget')
|
|
.css({
|
|
width: self.options.width - padding - 32 - Ox.SCROLLBAR_SIZE + 'px'
|
|
})
|
|
.html(data.title || '')
|
|
.appendTo($item);
|
|
return $item;
|
|
}
|
|
|
|
function getIcon(id, expanded) {
|
|
var isFunction = Ox.isFunction(self.options.icon),
|
|
$icon = isFunction ? self.options.icon(id, expanded) : null;
|
|
if (!$icon) {
|
|
if (expanded === null) {
|
|
if (!$icon && self.options.icon && !isFunction) {
|
|
$icon = $('<img>').attr({src: self.options.icon});
|
|
}
|
|
} else {
|
|
$icon = $('<img>').attr({src: Ox.getImageURL(
|
|
'symbol' + (expanded ? 'Down' : 'Right')
|
|
)});
|
|
}
|
|
}
|
|
return $icon;
|
|
}
|
|
|
|
function getItemById(id, items, level) {
|
|
var ret = null;
|
|
items = items || self.options.items;
|
|
level = level || 0;
|
|
Ox.forEach(items, function(item) {
|
|
if (item.id == id) {
|
|
ret = Ox.extend(item, {
|
|
level: level
|
|
});
|
|
return false; // break
|
|
}
|
|
if (item.items) {
|
|
ret = getItemById(id, item.items, level + 1);
|
|
if (ret) {
|
|
return false; // break
|
|
}
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
function getItems() {
|
|
var items = [];
|
|
Ox.sort(Object.keys(self.options.data)).forEach(function(key) {
|
|
items.push(parseData(key, self.options.data[key]));
|
|
});
|
|
return items;
|
|
}
|
|
|
|
function getParent(id, items) {
|
|
var ret;
|
|
Ox.forEach(items, function(item) {
|
|
if (item.items) {
|
|
if (Ox.getObjectById(item.items, id)) {
|
|
ret = item.id;
|
|
} else {
|
|
ret = getParent(id, item.items);
|
|
}
|
|
if (ret) {
|
|
return false; // break
|
|
}
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
function parseData(key, value) {
|
|
var ret = {
|
|
id: Ox.uid().toString(),
|
|
title: Ox.encodeHTMLEntities(key.toString()) + ': '
|
|
},
|
|
type = Ox.typeOf(value);
|
|
if (type == 'array' || type == 'object') {
|
|
ret.expanded = self.options.expanded;
|
|
ret.title += Ox.toTitleCase(type)
|
|
+ ' <span class="OxLight">[' + Ox.len(value) + ']</span>';
|
|
ret.items = type == 'array' ? value.map(function(v, i) {
|
|
return parseData(i, v);
|
|
}) : Ox.sort(Object.keys(value)).map(function(k) {
|
|
return parseData(k, value[k]);
|
|
});
|
|
} else {
|
|
ret.title += (
|
|
type == 'function'
|
|
? value.toString().split('{')[0]
|
|
: Ox.encodeHTMLEntities(JSON.stringify(value))
|
|
.replace(/(^"|"$)/g, '<span class="OxLight">"</span>')
|
|
);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function parseItems(items, level) {
|
|
var ret = [];
|
|
items = items || self.options.items;
|
|
level = level || 0;
|
|
items.forEach(function(item, i) {
|
|
if (item.items && self.options.expanded) {
|
|
item.expanded = true;
|
|
}
|
|
var item_ = Ox.extend({level: level}, item, item.items ? {
|
|
items: item.expanded
|
|
? parseItems(item.items, level + 1)
|
|
: []
|
|
} : {});
|
|
ret.push(item_);
|
|
if (item.items) {
|
|
ret = ret.concat(item_.items);
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
function selectItem(data) {
|
|
var id = data.ids[0], parent = id, parents = [];
|
|
while (parent = getParent(parent, self.options.items)) {
|
|
parents.push(parent);
|
|
}
|
|
parents = parents.reverse();
|
|
toggleItems({
|
|
expanded: true,
|
|
ids: parents
|
|
});
|
|
self.$list.options({selected: data.ids})
|
|
}
|
|
|
|
function toggleItem(item, expanded) {
|
|
var $img, pos;
|
|
item.expanded = expanded;
|
|
that.find('.OxItem').each(function() {
|
|
var $item = $(this);
|
|
if ($item.data('id') == item.id) {
|
|
$img = $item.find('.OxToggle');
|
|
pos = $item.data('position');
|
|
return false;
|
|
}
|
|
});
|
|
//self.$list.value(item.id, 'expanded', expanded);
|
|
$img.attr({
|
|
src: getIcon(item.id, expanded).attr('src')
|
|
});
|
|
if (expanded) {
|
|
self.$list.addItems(
|
|
pos + 1, parseItems(item.items, item.level + 1)
|
|
);
|
|
} else {
|
|
self.$list.removeItems(
|
|
pos + 1, parseItems(item.items, item.level + 1).length
|
|
);
|
|
}
|
|
}
|
|
|
|
function toggleItems(data) {
|
|
data.ids.forEach(function(id, i) {
|
|
var item = getItemById(id);
|
|
if (item.items && data.expanded != !!item.expanded) {
|
|
toggleItem(item, data.expanded);
|
|
}
|
|
});
|
|
}
|
|
|
|
/*@
|
|
gainFocus <f> gainFocus
|
|
@*/
|
|
that.gainFocus = function() {
|
|
self.$list.gainFocus();
|
|
return that;
|
|
};
|
|
|
|
/*@
|
|
hasFocus <f> hasFocus
|
|
@*/
|
|
that.hasFocus = function() {
|
|
return self.$list.hasFocus();
|
|
};
|
|
|
|
/*@
|
|
loseFocus <f> loseFocus
|
|
@*/
|
|
that.loseFocus = function() {
|
|
self.$list.loseFocus();
|
|
return that;
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|