From d7badfe326d4f911a713b6e155f1618f4cdf5749 Mon Sep 17 00:00:00 2001 From: rlx <0x0073@0x2620.org> Date: Mon, 7 Feb 2011 18:57:05 +0000 Subject: [PATCH] add Ox.TreeList, fix Ox.List bugs --- build/css/ox.ui.css | 24 +-- build/css/ox.ui.modern.css | 6 +- build/js/ox.ui.js | 311 +++++++++++++++++++++++++++++++------ 3 files changed, 281 insertions(+), 60 deletions(-) diff --git a/build/css/ox.ui.css b/build/css/ox.ui.css index 7ffce970..b3c4c5b9 100644 --- a/build/css/ox.ui.css +++ b/build/css/ox.ui.css @@ -894,47 +894,48 @@ Lists right: 0; bottom: 0; } -.OxTextList .OxBody .OxContent { +.OxTextList .OxContent { //width: 100%; } -.OxTextList .OxBody .OxItem { +.OxTextList .OxItem { height: 16px; cursor: default; } -.OxTextList .OxBody .OxItem .OxCell { +.OxTextList .OxItem .OxCell { float: left; height: 14px; padding: 1px 4px 1px 4px; + border-right: 1px solid; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } -.OxTextList .OxBody .OxItem .OxCell.OxEdit { +.OxTextList .OxItem .OxCell.OxEdit { height: 16px; padding: 0; } -.OxTextList .OxBody .OxItem .OxCell > img { +.OxTextList .OxItem .OxCell > img { width: 16px; height: 16px; margin: -1px 0 0 -4px; } -.OxTextList .OxBody .OxItem .OxSpace { +.OxTextList .OxItem .OxSpace { float: left; width: 4px; height: 16px; } -.OxTextList .OxBody .OxItem .OxLine { +.OxTextList .OxItem .OxLine { float: left; width: 1px; height: 16px; } -.OxTextList .OxBody .OxItem.OxSelected .OxCell.OxClickable { +.OxTextList .OxItem.OxSelected .OxCell.OxClickable { cursor: pointer; } -.OxTextList .OxBody .OxItem.OxSelected .OxCell.OxEditable { +.OxTextList .OxItem.OxSelected .OxCell.OxEditable { cursor: text; } -.OxTextList .OxBody .OxItem.OxSelected.OxDrag .OxCell { +.OxTextList .OxItem.OxSelected.OxDrag .OxCell { cursor: ns-resize; } .OxTextList .OxPage { @@ -943,6 +944,9 @@ Lists .OxTextList.OxDrop .OxItem .OxCell { color: green; } +.OxTreeList .OxItem .OxCell { + border-right-width: 0 +} /* ================================================================================ diff --git a/build/css/ox.ui.modern.css b/build/css/ox.ui.modern.css index 338ead22..84aeb8b2 100644 --- a/build/css/ox.ui.modern.css +++ b/build/css/ox.ui.modern.css @@ -238,13 +238,13 @@ Lists border-color: rgb(24, 24, 24); } .OxThemeModern .OxTextList .OxBody .OxItem .OxCell { - border-right: 1px solid rgb(24, 24, 24); + border-right-color: rgb(24, 24, 24); } .OxThemeModern .OxTextList .OxItem.OxSelected .OxCell { - border-right: 1px solid rgb(40, 40, 40); + border-right-color: rgb(40, 40, 40); } .OxThemeModern .OxTextList .OxFocus .OxItem.OxSelected .OxCell { - border-right: 1px solid rgb(72, 72, 72); + border-right-color: rgb(72, 72, 72); color: rgb(255, 255, 255); } .OxThemeModern .OxTextList .OxBody .OxItem .OxLine { diff --git a/build/js/ox.ui.js b/build/js/ox.ui.js index 36be7ac9..0773eeb6 100644 --- a/build/js/ox.ui.js +++ b/build/js/ox.ui.js @@ -957,6 +957,10 @@ requires // (to be implemented by widget) }; + that._leakSelf = function() { // fixme: remove + return self; + } + that.bindEvent = function() { /*** binds a function to an event triggered by this object @@ -6512,7 +6516,6 @@ requires clickTimeout: 0, dragTimeout: 0, format: {}, - ids: [], itemMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px margin ... fixme: the 2x should be computed later keyboardEvents: { key_control_c: copyItems, @@ -6556,7 +6559,16 @@ requires 'key_' + (self.options.orientation == 'vertical' ? 'shift_down' : 'shift_right') ] = addNextToSelection; } - if (self.options.orientation == 'both') { + if (self.options.orientation == 'vertical') { + $.extend(self.keyboardEvents, { + key_left: function() { + triggerToggleEvent(false); + }, + key_right: function() { + triggerToggleEvent(true); + } + }); + } else if (self.options.orientation == 'both') { $.extend(self.keyboardEvents, { key_down: selectBelow, key_up: selectAbove @@ -6745,7 +6757,7 @@ requires pos: findItemPosition(e) }; $.extend(self.drag, { - id: self.ids[self.drag.pos], + id: self.$items[self.drag.pos].options('data')[self.options.unique], startPos: self.drag.pos, startY: e.clientY, stopPos: self.drag.pos @@ -6779,14 +6791,16 @@ requires }); that.triggerEvent('move', { //id: id, - ids: self.ids + ids: $.map(self.$items, function($item) { + return $item.options('data')[self.options.unique]; + }) //position: pos }); } function dragItem(pos, e) { var $item = self.$items[pos], - id = self.ids[pos], + id = self.$items[pos].options('data')[self.options.unique], startPos = pos, startY = e.clientY, stopPos = startPos, @@ -6824,7 +6838,9 @@ requires }); that.triggerEvent('move', { //id: id, - ids: self.ids + ids: $.map(self.$items, function($item) { + return $item.options('data')[self.options.unique]; + }) //position: pos }); } @@ -6960,8 +6976,8 @@ requires function getPositionById(id) { // fixme: is this really needed? var pos = -1; - $.each(self.ids, function(i, v) { - if (v == id) { + $.each(self.$items, function(i, $item) { + if ($item.options('data')[self.options.unique] == id) { pos = i; return false; } @@ -7038,9 +7054,9 @@ requires } function getSelectedIds() { - // fixme: is carring self.ids around the best way? + Ox.print('gSI', self.selected) return $.map(self.selected, function(pos) { - return self.ids[pos]; + return self.$items[pos].options('data')[self.options.unique]; }); } @@ -7097,7 +7113,6 @@ requires self.$pages[page] = new Ox.ListPage().css(getPageCSS(page)); $.each(result.data.items, function(i, v) { var pos = offset + i; - self.ids[pos] = v[self.options.unique]; // fixme: why not use self.$items[pos].options('id')? self.$items[pos] = new Ox.ListItem({ construct: self.options.construct, data: v, @@ -7226,6 +7241,7 @@ requires addAllToSelection(pos); } } else if (!isSelected(pos)) { + Ox.print('select', pos) select(pos); } else if (self.selected.length > 1) { // this could be the first click @@ -7274,7 +7290,7 @@ requires self.clickTimeout = 0; open(); } - } else if (self.options.min == 0) { + } else if (!$(e.target).hasClass('OxToggle') && self.options.min == 0) { selectNone(); } } @@ -7285,9 +7301,7 @@ requires $item.detach()[insert](self.$items[stopPos].$element); // fixme: why do we need .$element here? //Ox.print('moveItem', startPos, stopPos, insert, self.ids); var $item = self.$items.splice(startPos, 1)[0]; - id = self.ids.splice(startPos, 1)[0]; self.$items.splice(stopPos, 0, $item); - self.ids.splice(stopPos, 0, id); self.$items.forEach(function($item, pos) { $item.data({position: pos}); }); @@ -7534,6 +7548,13 @@ requires }, 100); } + function triggerToggleEvent(expanded) { + that.triggerEvent('toggle', { + expanded: expanded, + ids: getSelectedIds() + }); + } + function unloadPage(page) { if (page < 0 || page >= self.pages) { return; @@ -7553,6 +7574,33 @@ requires unloadPage(page + 1) } + function updatePages(pos, scroll) { + // only used if orientation is both + clear(); + self.pageLength = self.pageLengthByRowLength[self.rowLength] + $.extend(self, { + 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.data('position', pos); + }); + } + function updateQuery(ids) { // fixme: shouldn't this be setQuery? // ids are the selcected ids // (in case list is loaded with selection) @@ -7585,27 +7633,6 @@ requires })); } - function updatePages(pos, scroll) { - // only used if orientation is both - clear(); - self.pageLength = self.pageLengthByRowLength[self.rowLength] - $.extend(self, { - 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 updateSort() { if (self.listLength > 1) { clear(); @@ -7622,6 +7649,39 @@ requires } }; + that.addItems = function(pos, items) { + var $items = [], + length = items.length; + self.selected.forEach(function(v, i) { + if (v >= pos) { + self.selected[i] += length; + } + }); + items.forEach(function(item, i) { + var $item; + $items.push($item = new Ox.ListItem({ + construct: self.options.construct, + data: item, + draggable: self.options.draggable, + 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]); + } + }); + self.options.items.splice.apply(self.options.items, $.merge([pos, 0], items)); + self.$items.splice.apply(self.$items, $.merge([pos, 0], $items)); + updatePositions(); + Ox.print('s.o.i', self.options.items, 's.$i', $.map(self.$items, function(v) {return v.data()})) + } + that.clearCache = function() { // fixme: was used by TextList resizeColumn, now probably no longer necessary self.$pages = []; return that; @@ -7652,19 +7712,26 @@ requires return that; }; + that.removeItems = function(pos, length) { + Ox.range(pos, pos + length).forEach(function(i) { + self.selected.indexOf(i) > -1 && deselect(i); + self.$items[i].remove(); + }); + self.options.items.splice(pos, length); + self.$items.splice(pos, length); + self.selected.forEach(function(v, i) { + if (v >= pos + length) { + self.selected[i] -= length; + } + }); + updatePositions(); + } + that.scrollToSelection = function() { self.selected.length && scrollToPosition(self.selected[0]); return that; }; - that.setId = function(oldId, newId) { // fixme: not used - var index = self.ids.indexOf(oldId); - if (index > -1) { - self.ids[index] = newId; - } - return that; - }; - that.size = function() { // fixme: not a good function name if (self.options.orientation == 'both') { var rowLength = getRowLength(), @@ -7725,9 +7792,6 @@ requires oldValue = data[key]; data[key] = value; $item.options({data: data}); - if (key == self.options.unique) { - self.ids[self.ids.indexOf(oldValue)] = value; - } return that; } }; @@ -7753,7 +7817,7 @@ requires function constructItem(update) { var $element = self.options.construct(self.options.data) - .addClass('OxItem') + .addClass('OxItem OxDeleteme' + self.options.data[self.options.unique]) .attr({ draggable: self.options.draggable }) @@ -8506,6 +8570,159 @@ requires }; + Ox.TreeList = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + items: [], + max: -1, + min: 0, + selected: [], + width: 256 + }) + .options(options || {}); + + that.$element = new Ox.List({ + construct: constructItem, + itemHeight: 16, + items: parseItems(), + itemWidth: self.options.width, + max: self.options.max, + min: self.options.min, + unique: 'id', + }, $.extend({}, self)) + .addClass('OxTextList OxTreeList') + .css({ + width: self.options.width + 'px' + }) + .click(clickItem) + .bindEvent({ + toggle: toggleItems + }); + + function clickItem(e) { + var $target = $(e.target), + $item, id, item; + if ($target.hasClass('OxToggle')) { + $item = $target.parent().parent(); + id = $item.data('id'); + item = getItemById(id); + toggleItem(item, !item.expanded) + } + } + + function constructItem(data) { + var $item = $('
'), + padding = (data.level + !data.items) * 16 - 8; + if (data.level || !data.items) { + $('
') + .addClass('OxCell OxTarget') + .css({ + width: padding + 'px', + }) + .appendTo($item); + } + if (data.items) { + $('
') + .addClass('OxCell') + .css({ + width: '8px', + }) + .append( + // fixme: need Ox.Icon() + $('') + .addClass('OxToggle') + .attr({ + src: oxui.path + '/png/ox.ui.' + Ox.theme() + '/symbol' + + (data.expanded ? 'Collapse' : 'Expand') + '.png' + }) + ) + .appendTo($item); + } + $('
') + .addClass('OxCell OxTarget') + .css({ + width: (self.options.width - padding - 32) + 'px' + }) + .html(data.title) + .appendTo($item); + return $item; + } + + function getItemById(id, items, level) { + var items = items || self.options.items, + level = level || 0, + ret = null; + $.each(items, function(i, item) { + if (item.id == id) { + ret = $.extend(item, { + level: level + }); + return false; + } + if (item.items) { + ret = getItemById(id, item.items, level + 1); + if (ret) { + return false; + } + } + }); + return ret; + } + + function parseItems(items, level) { + var items = items || self.options.items, + level = level || 0, + ret = []; + items.forEach(function(item, i) { + var item_ = $.extend({ + level: level + }, item, item.items ? { + items: !!item.expanded ? + parseItems(item.items, level + 1) : [] + } : {}); + ret.push(item_); + item.items && $.merge(ret, item_.items); + }); + return ret; + } + + function toggleItem(item, expanded) { + var $img, $item, pos; + item.expanded = expanded; + $.each(that.$element.find('.OxItem'), function(i, v) { + var $item = $(v); + if ($item.data('id') == item.id) { + $img = $item.find('img'); + pos = $item.data('position'); + return false; + } + }) + $img.attr({ + src: oxui.path + '/png/ox.ui.' + Ox.theme() + '/symbol' + + (item.expanded ? 'Collapse' : 'Expand') + '.png' + }); + item.expanded ? + that.$element.addItems(pos + 1, parseItems(item.items, item.level + 1)) : + that.$element.removeItems(pos + 1, parseItems(item.items, item.level + 1).length); + } + + function toggleItems(event, data) { + data.ids.forEach(function(id, i) { + var item = getItemById(id); + Ox.print('item', item, !!item.items, data.expanded != !!item.expanded) + if (item.items && data.expanded != !!item.expanded) { + Ox.print('ITEM', item) + toggleItem(item, data.expanded); + } + }); + } + + return that; + + }; + /* ============================================================================ Maps