diff --git a/build/css/ox.ui.css b/build/css/ox.ui.css index 8e2d342f..3c27d536 100644 --- a/build/css/ox.ui.css +++ b/build/css/ox.ui.css @@ -424,6 +424,7 @@ OxSelect Layers ================================================================================ */ + .OxLayer { position: absolute; width: 100%; @@ -444,6 +445,138 @@ Layers overflow: hidden; z-index: 10; } + +/* +================================================================================ +Lists +================================================================================ +*/ + +.OxListPage { + position: absolute; +} + +.OxTextList .OxCell { + float: left; + height: 12px; + padding: 2px 4px 2px 4px; +} +.OxTextList .OxBar { + z-index: 10; + -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.75); + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.75); +} +.OxTextList .OxBar .OxHead { + position: absolute; + left: 0; + right: 12px; + height: 16px; + overflow: hidden; + white-space: nowrap; +} +.OxTextList .OxBar .OxCell { + float: left; + padding: 2px 4px 2px 4px; + height: 12px; +} +.OxTextList .OxBar .OxCell:nthChild(1) { + margin-left: 0; +} +.OxTextList .OxBar .OxTitle { + float: left; + height: 15px; + padding: 1px 2px 0 2px; + font-weight: bold; + font-size: 10px; + text-overflow: ellipsis; + cursor: pointer; + overflow: hidden; + white-space: nowrap; +} +.OxTextList .OxBar .OxTitle:first-child { + padding-left: 4px; +} +.OxTextList .OxBar .OxOrder { + float: left; + width: 10px; + height: 13px; + padding: 3px 0 0 6px; + font-size: 7px; + display: none; +} +.OxTextList .OxBar .OxOrder.OxSelected { + cursor: pointer; + display: block; +} +.OxTextList .OxBar .OxResize { + float: left; + width: 5px; + height: 16px; +} +.OxTextList .OxBar .OxResize .OxLeft, +.OxTextList .OxBar .OxResize .OxCenter, +.OxTextList .OxBar .OxResize .OxRight { + float: left; + height: 16px; + cursor: ew-resize; +} +.OxTextList .OxBar .OxResize .OxLeft, +.OxTextList .OxBar .OxResize .OxRight { + width: 2px; +} +.OxTextList .OxBar .OxResize .OxCenter { + width: 1px; +} +.OxTextList .OxBar .OxResize .OxCenter { + float: left; + width: 1px; + height: 16px; + background: rgb(24, 24, 24); + cursor: ew-resize; +} +.OxTextList .OxBar .OxSelect { + position: absolute; + right: 0px; + width: 11px; + height: 16px; + font-size: 11px; + text-align: center; + cursor: pointer; +} + +.OxTextList .OxBody { + float: left; + position: absolute; + left: 0; + top: 16px; + right: 0; + bottom: 0; +} +.OxTextList .OxBody .OxContent { + width: 100%; +} +.OxTextList .OxBody .OxItem { + height: 16px; +} +.OxTextList .OxBody .OxItem .OxCell { + float: left; + height: 14px; + padding: 2px 4px 0 4px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +.OxTextList .OxBody .OxItem .OxSpace { + float: left; + width: 4px; + height: 16px; +} +.OxTextList .OxBody .OxItem .OxLine { + float: left; + width: 1px; + height: 16px; +} + /* ================================================================================ Menus diff --git a/build/css/ox.ui.modern.css b/build/css/ox.ui.modern.css index 33c8f246..b0eece15 100644 --- a/build/css/ox.ui.modern.css +++ b/build/css/ox.ui.modern.css @@ -98,6 +98,58 @@ Forms color: rgb(96, 96, 96) } +/* +================================================================================ +Lists +================================================================================ +*/ + +.OxThemeModern .OxTextList .OxItem .OxCell { + border-right: 1px solid rgb(32, 32, 32); +} +.OxThemeModern .OxTextList .OxItem:nth-child(odd) { + background: rgb(14, 14, 14); +} +.OxThemeModern .OxTextList .OxItem:nth-child(even) { + background: rgb(18, 18, 18); +} +.OxThemeModern .OxTextList .OxItem.OxSelected:nth-child(odd) { + background: rgb(30, 30, 30); +} +.OxThemeModern .OxTextList .OxItem.OxSelected:nth-child(even) { + background: rgb(34, 34, 34); +} +.OxThemeModern .OxTextList.OxFocus .OxItem.OxSelected:nth-child(odd) { + background: rgb(62, 62, 62); +} +.OxThemeModern .OxTextList.OxFocus .OxItem.OxSelected:nth-child(even) { + background: rgb(66, 66, 66); +} +.OxThemeModern .OxTextList .OxBar .OxSelected { + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgb(80, 80, 80)), color-stop(1, rgb(48, 48, 48))); + color: rgb(255, 255, 255); +} +.OxThemeModern .OxTextList .OxBar .OxOrder { + color: rgb(255, 255, 255); +} +.OxThemeModern .OxTextList .OxBar .OxResize .OxCenter { + background: rgb(24, 24, 24); +} +.OxThemeModern .OxTextList .OxBody .OxItem .OxCell { + border-right: 1px solid rgb(24, 24, 24); +} +.OxThemeModern .OxTextList .OxItem.OxSelected .OxCell { + border-right: 1px solid rgb(40, 40, 40); +} +.OxThemeModern .OxTextList.OxFocus .OxItem.OxSelected .OxCell { + border-right: 1px solid rgb(72, 72, 72); + color: rgb(255, 255, 255); +} +.OxThemeModern .OxTextList .OxBody .OxItem .OxLine { + background: rgb(24, 24, 24); +} + + /* ================================================================================ Menus diff --git a/build/js/ox.ui.js b/build/js/ox.ui.js index 85eb160b..8f77feaa 100644 --- a/build/js/ox.ui.js +++ b/build/js/ox.ui.js @@ -686,6 +686,7 @@ requires data: data, time: Ox.getTime() }; + //Ox.print("callback", callback, "options.callback", options.callback, "data", data) callback(data); } @@ -2382,7 +2383,243 @@ requires Ox.List = function(options, self) { var self = self || {}, - that = new Ox.Container({}, self); + that = new Ox.Container({}, self) + .defaults({ + itemHeight: 16, + itemWidth: 16, + orientation: "vertical", + request: function() {}, // {sort:, range:, callback:}, without parameter returns {items, size etc.} + rowLength: 1, + sort: [], + type: "text" + }) + .options(options || {}); + + Ox.print("self1", self) + $.extend(self, { + $items: [], + $pages: [], + page: 0, + pageLength: 100, + pages: 0, + selected: [] + }); + Ox.print("self2", self) + $.extend(self, { + pageWidth: self.options.orientation == "horizontal" ? + self.pageLength * self.options.itemWidth : 0, + pageHeight: self.options.orientation == "horizontal" ? 0 : + self.pageLength * self.options.itemHeight / self.options.rowLength + }); + + function addAllToSelection(pos) { + var arr, + len = self.$items.length; + if (!isSelected(pos)) { + if (selected.length == 0) { + addToSelection(pos); + } else { + if (Ox.min(selected) < pos) { + var arr = [pos]; + for (var i = pos - 1; i >= 0; i--) { + if (isSelected(i)) { + $.each(arr, function(i, v) { + addToSelection(v); + }); + break; + } + arr.push(i); + } + } + if (Ox.max(selected) > pos) { + var arr = [pos]; + for (var i = pos + 1; i < len; i++) { + if (isSelected(i)) { + $.each(arr, function(i, v) { + addToSelection(v); + }); + break; + } + arr.push(i); + } + } + } + } + } + + function addToSelection(pos) { + if (!isSelected(pos)) { + selected.push(pos); + if (!Ox.isUndefined(self.$items[pos])) { + self.$items[pos].addClass("OxSelected"); + } + Ox.Event.trigger("select_" + self.options.id, { + items: selected.length + }); + } + } + + function deselect(pos) { + if (isSelected(pos)) { + selected.splice(selected.indexOf(pos), 1); + if (!Ox.isUndefined(self.$items[pos])) { + self.$items[pos].removeClass("OxSelected"); + } + Ox.Event.trigger("select_" + self.options.id, { + items: selected.length + }); + } + } + + function getNext() { + var pos = -1; + if (selected.length) { + var pos = Ox.max(selected) + 1; + if (pos == self.$items.length) { + pos = -1; + } + } + return pos; + } + + function getPage() { + return self.options.orientation == "vertical" + ? Math.floor(that.scrollTop() / pageHeight) + : Math.floor(that.scrollLeft() / pageWidth); + } + + function getPrevious() { + var pos = -1; + if (selected.length) { + var pos = Ox.min(selected) - 1; + } + return pos; + } + + function invertSelection() { + $.each(self.options.items, function(i, v) { + toggleSelection(i); + }); + } + + function isSelected(pos) { + return selected.indexOf(pos) > -1; + } + + function loadPage(page, callback) { + if (page < 0 || page >= pages) { + return; + } + var offset = page * self.pageLength, + range = [offset, offset + (page < self.pages - 1 ? + self.pageLength : self.listLength % self.pageLength)]; + if (Ox.isUndefined(self.$pages[page])) { + Ox.Request.send({ + callback: function(data) { + self.$pages[page] = new Ox.ListPage(); + if (self.options.type == "text") { + self.$pages[page].css({ + top: (page * self.pageHeight) + "px" + }); + } else { + self.$pages[page].css({ + + }); + } + $.each(data, function(i, v) { + var pos = offset + i; + self.$items[pos] = new Ox.ListItem(v); + if (isSelected(pos)) { + self.$items[pos].addClass("OxSelected"); + } + self.$items[pos].appendTo(self.$pages[page]) + }); + }, + range: range, + sort: self.options.sort + }); + } + self.$pages[page].appendTo(that.$content); + } + + function loadPages(page, callback) { + var counter = 0, + fn = function() { + counter++; + counter == 2 && !Ox.isUndefined(callback) && callback(); + }; + loadPage(page, function() { + loadPage(page - 1, fn); + loadPage(page + 1, fn); + }); + } + + function select(pos) { + if (!isSelected(pos) || selected.length > 1) { + selectNone(); + addToSelection(pos); + } + } + + function selectAll() { + $.each(self.$items, function(i, v) { + addToSelection(i); + }); + } + + function selectNext() { + var pos = getNext(); + if (pos > -1) { + select(pos); + scrollTo(pos); + } + } + + function selectNone() { + $.each(self.$items, function(i, v) { + deselect(i); + }); + } + + function selectPrevious() { + var pos = getPrevious(); + if (pos > -1) { + select(pos); + scrollTo(pos); + } + } + + function selectQuery(str) { + $.each(self.$items, function(i, v) { + if (Ox.toLatin(v.title).toUpperCase().indexOf(str) == 0) { + select(i); + scrollTo(i); + return false; + } + }); + } + + function scrollTo(pos) { + + } + + function toggleSelection(pos) { + if (!isSelected(pos)) { + addToSelection(pos); + } else { + deselect(pos); + } + } + + function unloadPage(page) { + !Ox.isUndefined(self.$pages[page]) && self.$pages[page].remove(); + } + + function unloadPages(page) { + unloadPage(page); + unloadPage(page - 1); + unloadPage(page + 1) + } return that; @@ -2392,6 +2629,250 @@ requires }; + Ox.ListPage = function(options, self) { + var self = self || {}, + that = new Ox.Element({}, self) + .addClass("OxListPage"); + return that; + }; + + Ox.TextList = function(options, self) { + + var self = self || {}, + that = new Ox.Element({}, self) + .defaults({ + columns: [], + request: function() {}, // {sort, range, keys, callback} + sort: {} + }) + .options(options || {}) + .addClass("OxTextList"); + + $.extend(self, { + columnWidths: [], + itemHeight: 16, + page: 0, + pageLength: 100, + scrollLeft: 0, + selectedColumn: getColumnById(self.options.sort.key) + }); + $.extend(self, { + pageHeight: self.pageLength * self.itemHeight + }); + Ox.print("self", self); + + // Head + + that.$bar = new Ox.Bar({ + orientation: "horizontal", + size: 16 + }).appendTo(that); + that.$head = new Ox.Container() + .addClass("OxHead") + .appendTo(that.$bar); + that.$head.$content.addClass("OxTitles"); + that.$titles = []; + $.each(self.options.columns, function(i, v) { + var $order, $resize, $left, $center, $right; + self.columnWidths[i] = v.width; + that.$titles[i] = $("
") + .addClass("OxTitle") + .css({ + width: (v.width - 9) + "px", + textAlign: v.align + }) + .html(v.title) + .click(function() { + if (self.options.sort.key != v.id) { + that.sort(v.id, v.operator); + } else { + that.order(self.options.operator == "+" ? "-" : "+"); + } + }) + .appendTo(that.$head.$content.$element); + $order = $("
") + .addClass("OxOrder") + .html(oxui.symbols["triangle_" + ( + self.options.sort.operator == "+" ? "up" : "down" + )]) + .click(function() { + $(this).prev().trigger("click") + }) + .appendTo(that.$head.$content.$element); + $resize = $("
") + .addClass("OxResize") + .mousedown(function(e) { + var initialWidth = self.columnWidths[i], + initialX = e.clientX; + $window.mousemove(function(e) { + var x = e.clientX, + width = Ox.limit(initialWidth - initialX + x, 40, 512); + resizeColumn(i, width); + }); + $window.mouseup(function() { + $window.unbind("mousemove"); + $window.unbind("mouseup"); + }); + }) + .dblclick(function() { + resizeColumn(i, v.width); + }) + .appendTo(that.$head.$content.$element); + $left = $("
").addClass("OxLeft").appendTo($resize); + $center = $("
").addClass("OxCenter").appendTo($resize); + $right = $("
").addClass("OxRight").appendTo($resize); + }); + that.$head.$content.css({ + width: Ox.sum(self.columnWidths) + "px" + }); + toggleSelected(self.selectedColumn); + that.$titles[self.selectedColumn].css({ + width: (self.options.columns[self.selectedColumn].width - 25) + "px" + }); + that.$select = $("
") + .addClass("OxSelect") + .html(oxui.symbols.select) + .appendTo(that.$bar.$element); + + // Body + + self.options.request({ + callback: function(result) { + Ox.print("result", result); + $.extend(self, { + listHeight: result.data.items * self.itemHeight, + listLength: result.data.items, + pages: Math.ceil(result.data.items / self.pageLength) + }); + Ox.print("self", self); + that.$body = new Ox.List({ + itemHeight: 16, + itemWidth: Ox.sum(self.columnWidths), + orientation: "vertical", + request: self.options.request, + sort: self.options.sort, + type: "text" + }, self) + .addClass("OxBody") + .scroll(function() { + var scrollLeft = $(this).scrollLeft(); + if (scrollLeft != self.scrollLeft) { + self.scrollLeft = scrollLeft; + that.$head.scrollLeft(scrollLeft); + } + }) + .appendTo(that); + Ox.print("that.$body", that.$body) + that.$body.$content.css({ + width: Math.max(Ox.sum(self.columnWidths), that.$element.width() - 12) + "px", + height: self.listHeight + "px" + }); + } + }); + + function constructItem(item, pos) { + var item = $("
") + .addClass("OxListItem") + .css({ + width: Ox.sum(columnWidths) + "px" + }) + .data("pos", pos) + .click(function() {}); + $.each(self.options.columns, function(i, v) { + var $cell = $("
") + .addClass("OxCell OxColumn" + Ox.toTitleCase(v.id)) + .css({ + width: (columnWidths[i] - 9) + "px", + textAlign: v.align + }) + .html(item[v.id]) + .appendTo($item) + }); + return $item; + } + + function getColumnById(id) { + var pos = -1; + $.each(self.options.columns, function(i, v) { + if (v.id == id) { + pos = i; + return false; + } + }); + return pos; + } + + function resizeColumn(pos, width) { + self.columnWidths[pos] = width; + that.$head.$content.css({ + width: (Ox.sum(self.columnWidths) + 2) + "px" + }); + that.$titles[pos].css({ + width: (width - 9 - (pos == self.selectedColumn ? 16 : 0)) + "px" + }); + that.$body.$content.find(".OxItem").css({ // fixme: can we avoid this lookup? + width: Ox.sum(self.columnWidths) + "px" + }); + that.$body.$content.css({ + width: Math.max(Ox.sum(self.columnWidths), that.$element.width() - 12) + "px" // fixme: check if scrollbar visible, and listen to resize/toggle event + }); + $(".OxColumn" + Ox.toTitleCase(args.columns[pos].id)).css({ + width: (width - 9) + "px" + }); + that.$body.clearCache(); + } + + function selectColumn(pos) { + that.sort(args.columns[pos].id, args.columns[pos].operator); + } + + function toggleOrder(pos) { + self.options.columns[pos].operator = self.options.columns[pos].operator == "+" ? "-" : "+"; + that.$titles[pos].next().html(oxui.symbols[ + "triangle" + self.options.columns[pos].operator == "+" ? "up" : "down" + ]); + } + + function toggleSelected(pos) { + if (pos > 0) { + that.$titles[pos].prev().children().eq(2).toggleClass("OxSelected"); + } + that.$titles[pos].toggleClass("OxSelected"); + that.$titles[pos].next().toggleClass("OxSelected"); + that.$titles[pos].next().next().children().eq(0).toggleClass("OxSelected"); + that.$titles[pos].css({ + width: ( + that.$titles[pos].width() + pos == self.selectedColumn ? -16 : 16 + ) + "px" + }); + + } + + that.order = function(operator) { + if (operator != self.options.sort.operator) { + self.options.sort.operator = operator; + toggleOrder(self.selectedColumn); + that.$body.order(operator); + } + }; + + that.sort = function(key, operator) { + if (key != self.options.sort.key || operator != self.options.sort.operator) { + self.options.sort = { + key: key, + operator: operator + }; + toggleSelected(self.selectedColumn); + selectedColumn = getColumnById(key); + toggleSelected(self.selectedColumn); + that.$body.sort(self.options.sort.key, self.options.sort.operator); + } + }; + + return that; + + }; + /* ============================================================================ Menus diff --git a/demos/test/list.html b/demos/test/list.html new file mode 100644 index 00000000..68a64869 --- /dev/null +++ b/demos/test/list.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/test/list.js b/demos/test/list.js new file mode 100644 index 00000000..5d271f6f --- /dev/null +++ b/demos/test/list.js @@ -0,0 +1,50 @@ +$(function() { + Ox.theme("modern"); + var $body = $("body"), + app = new Ox.App({ + requestURL: "http://blackbook.local:8000/api/" + }), + $list = new Ox.TextList({ + columns: [ + { + align: "left", + id: "title", + operator: "+", + title: "Title", + width: 160 + }, + { + align: "left", + id: "director", + operator: "+", + title: "Director", + width: 160 + }, + { + align: "right", + id: "year", + operator: "-", + title: "Year", + width: 80 + } + ], + request: function(options) { + app.request("find", $.extend(options, { + query: { + conditions: [ + { + key: "country", + value: "france", + operator: "" + } + ], + operator: "" + } + }), options.callback); + }, + sort: { + key: "year", + operator: "-" + } + }).appendTo($body); +});