'use strict'; /*@ Ox.TreeList Tree List ([options[, self]]) -> Tree List Object options Options object data Data to be parsed as items (needs documentation) expanded If true, and data is not null, all items are expanded icon Image URL, or function that returns an image object items Array of items max Maximum number of items that can be selected, -1 unlimited min Minimum number of items that have to be selected selected Selected ids width List width self Shared private variable @*/ 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' }, Ox.extend(Ox.clone(self), {updateCallbacks: []})) // pass event handler .addClass('OxTableList OxTreeList') .css({ width: self.options.width + 'px', overflowY: 'scroll' }) .bindEvent({ anyclick: clickItem, select: selectItem, toggle: toggleItems }) ); 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 = $('
').css({ width: self.options.width - Ox.UI.SCROLLBAR_SIZE + 'px' }), $cell = $('
').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) { $('
') .addClass('OxCell OxTarget') .css({width: padding + 'px'}) .appendTo($item); } $cell.appendTo($item); $icon && $icon.addClass(data.items ? 'OxToggle' : 'OxTarget').appendTo($cell); $('
') .addClass('OxCell OxTarget' + (!data.items ? ' OxSelectable' : '')) .css({ width: self.options.width - padding - 32 - Ox.UI.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 = $('').attr({src: self.options.icon}); } } else { $icon = $('').attr({src: Ox.UI.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 }); Ox.Break(); } if (item.items) { ret = getItemById(id, item.items, level + 1); if (ret) { Ox.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) { Ox.Break(); } } }); return ret; } function parseData(key, value) { var ret = { id: Ox.uid().toString(), title: key.toString() + ': ' }, type = Ox.typeOf(value); if (type == 'array' || type == 'object') { ret.expanded = self.options.expanded; ret.title += Ox.toTitleCase(type) + ' [' + Ox.len(value) + ']'; 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] : JSON.stringify(value) .replace(/(^"|"$)/g, '"') ); } 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; } }); //that.$element.value(item.id, 'expanded', expanded); $img.attr({ src: getIcon(item.id, expanded).attr('src') }); if (expanded) { that.$element.addItems( pos + 1, parseItems(item.items, item.level + 1) ); } else { that.$element.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 gainFocus @*/ that.gainFocus = function() { self.$list.gainFocus(); return that; }; /*@ hasFocus hasFocus @*/ that.hasFocus = function() { return self.$list.hasFocus(); }; /*@ loseFocus loseFocus @*/ that.loseFocus = function() { self.$list.loseFocus(); return that; }; return that; };