'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.UI.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.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 = $('<img>').attr({src: self.options.icon}); } } else { $icon = $('<img>').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 }); 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; };