// vim: et:ts=4:sw=4:sts=4:ft=js Ox.TextList = function(options, self) { // fixme: rename to TableList var self = self || {}, that = new Ox.Element({}, self) .defaults({ columns: [], columnsMovable: false, columnsRemovable: false, columnsResizable: false, columnsVisible: false, columnWidth: [40, 800], id: '', items: null, // function() {} {sort, range, keys, callback} or array max: -1, min: 0, pageLength: 100, scrollbarVisible: false, selected: [], sort: [] }) .options(options || {}) .addClass('OxTextList'); Ox.print('Ox.TextList self.options', self.options) self.options.columns.forEach(function(v) { // fixme: can this go into a generic ox.js function? // fixme: and can't these just remain undefined? if (Ox.isUndefined(v.align)) { v.align = 'left'; } if (Ox.isUndefined(v.clickable)) { v.clickable = false; } if (Ox.isUndefined(v.editable)) { v.editable = false; } if (Ox.isUndefined(v.unique)) { v.unique = false; } if (Ox.isUndefined(v.visible)) { v.visible = false; } if (v.unique) { self.unique = v.id; } }); $.extend(self, { columnPositions: [], defaultColumnWidths: $.map(self.options.columns, function(v) { return v.defaultWidth || v.width; }), itemHeight: 16, page: 0, pageLength: 100, scrollLeft: 0, selectedColumn: getColumnIndexById(self.options.sort[0].key), visibleColumns: $.map(self.options.columns, function(v) { return v.visible ? v : null; }) }); // fixme: there might be a better way than passing both visible and position self.options.columns.forEach(function(v) { if (!Ox.isUndefined(v.position)) { self.visibleColumns[v.position] = v; } }) $.extend(self, { columnWidths: $.map(self.visibleColumns, function(v, i) { return v.width; }), pageHeight: self.options.pageLength * self.itemHeight }); self.format = {}; self.options.columns.forEach(function(v, i) { if (v.format) { self.format[v.id] = v.format; } }); // Head if (self.options.columnsVisible) { that.$bar = new Ox.Bar({ orientation: 'horizontal', size: 16 }).appendTo(that); that.$head = new Ox.Container() .addClass('OxHead') .css({ right: self.options.scrollbarVisible ? Ox.UI.SCROLLBAR_SIZE + 'px' : 0 }) .appendTo(that.$bar); that.$head.$content.addClass('OxTitles'); constructHead(); if (self.options.columnsRemovable) { that.$select = new Ox.Select({ id: self.options.id + 'SelectColumns', items: $.map(self.options.columns, function(v, i) { return { checked: v.visible, disabled: v.removable === false, id: v.id, title: v.title } }), max: -1, min: 1, type: 'image' }) .bindEvent('change', changeColumns) .appendTo(that.$bar.$element); } } // Body that.$body = new Ox.List({ construct: constructItem, id: self.options.id, items: self.options.items, itemHeight: 16, items: self.options.items, itemWidth: getItemWidth(), format: self.format, // fixme: not needed, happens in TextList keys: $.map(self.visibleColumns, function(v) { return v.id; }), max: self.options.max, min: self.options.min, pageLength: self.options.pageLength, paste: self.options.paste, orientation: 'vertical', selected: self.options.selected, sort: self.options.sort, sortable: self.options.sortable, type: 'text', unique: self.unique }, $.extend({}, self)) // pass event handler .addClass('OxBody') .css({ top: (self.options.columnsVisible ? 16 : 0) + 'px', overflowY: (self.options.scrollbarVisible ? 'scroll' : 'hidden') }) .scroll(function() { var scrollLeft = $(this).scrollLeft(); if (scrollLeft != self.scrollLeft) { self.scrollLeft = scrollLeft; that.$head && that.$head.scrollLeft(scrollLeft); } }) .bindEvent({ edit: function(event, data) { that.editCell(data.id, data.key); }, select: function(event, data) { self.options.selected = data.ids; } }) .appendTo(that); that.$body.$content.css({ width: getItemWidth() + 'px' }); //Ox.print('s.vC', self.visibleColumns) function addColumn(id) { //Ox.print('addColumn', id); var column, ids, index = 0; Ox.forEach(self.options.columns, function(v) { if (v.visible) { index++; } else if (v.id == id) { column = v; return false; } }); column.visible = true; self.visibleColumns.splice(index, 0, column); self.columnWidths.splice(index, 0, column.width); that.$head.$content.empty(); constructHead(); that.$body.options({ keys: $.map(self.visibleColumns, function(v, i) { return v.id; }) }); that.$body.reloadPages(); } function changeColumns(event, data) { var add, ids = []; Ox.forEach(data.selected, function(column) { var index = getColumnIndexById(column.id); if (!self.options.columns[index].visible) { addColumn(column.id); add = true; return false; } ids.push(column.id); }); if (!add) { Ox.forEach(self.visibleColumns, function(column) { if (ids.indexOf(column.id) == -1) { removeColumn(column.id); return false; } }); } triggerColumnChangeEvent(); } function clickColumn(id) { Ox.print('clickColumn', id); var i = getColumnIndexById(id), isSelected = self.options.sort[0].key == self.options.columns[i].id; that.sortList( self.options.columns[i].id, isSelected ? (self.options.sort[0].operator == '+' ? '-' : '+') : self.options.columns[i].operator ); } function constructHead() { var offset = 0; that.$titles = []; self.columnOffsets = []; self.visibleColumns.forEach(function(v, i) { var $order, $resize, $left, $center, $right; offset += self.columnWidths[i]; self.columnOffsets[i] = offset - self.columnWidths[i] / 2; that.$titles[i] = new Ox.Element() .addClass('OxTitle OxColumn' + Ox.toTitleCase(v.id)) .css({ width: (self.columnWidths[i] - 9) + 'px', textAlign: v.align }) .html(v.title) .bindEvent({ anyclick: function(event, e) { clickColumn(v.id); }, dragstart: function(event, e) { dragstartColumn(v.id, e); }, drag: function(event, e) { dragColumn(v.id, e); }, dragend: function(event, e) { dragendColumn(v.id, e); } }) .appendTo(that.$head.$content.$element); $order = $('
') .addClass('OxOrder') .html(Ox.UI.symbols['triangle_' + ( v.operator == '+' ? 'up' : 'down' )]) .click(function() { $(this).prev().trigger('click') }) .appendTo(that.$head.$content.$element); $resize = new Ox.Element() .addClass('OxResize') .appendTo(that.$head.$content.$element); if (self.options.columnsResizable) { $resize.addClass('OxResizable') .bindEvent({ doubleclick: function(event, e) { resetColumn(v.id, e); }, dragstart: function(event, e) { dragstartResize(v.id, e); }, drag: function(event, e) { dragResize(v.id, e); }, dragend: function(event, e) { dragendResize(v.id, e); } }); } $left = $('
').addClass('OxLeft').appendTo($resize.$element); $center = $('
').addClass('OxCenter').appendTo($resize.$element); $right = $('
').addClass('OxRight').appendTo($resize.$element); }); that.$head.$content.css({ width: (Ox.sum(self.columnWidths) + 2) + 'px' }); //Ox.print('s.sC', self.selectedColumn) //Ox.print('s.cO', self.columnOffsets) if (getColumnPositionById(self.options.columns[self.selectedColumn].id) > -1) { // fixme: save in var toggleSelected(self.options.columns[self.selectedColumn].id); that.$titles[getColumnPositionById(self.options.columns[self.selectedColumn].id)].css({ width: (self.options.columns[self.selectedColumn].width - 25) + 'px' }); } } function constructItem(data) { var $item = $('
') .addClass('OxTarget') .css({ width: getItemWidth() + 'px' }); self.visibleColumns.forEach(function(v, i) { var clickable = Ox.isBoolean(v.clickable) ? v.clickable : v.clickable(data), editable = Ox.isBoolean(v.editable) ? v.editable : v.editable(data), $cell = $('
') .addClass( 'OxCell OxColumn' + Ox.toTitleCase(v.id) + (clickable ? ' OxClickable' : '') + (editable ? ' OxEditable' : '') ) .css({ width: (self.columnWidths[i] - (self.options.columnsVisible ? 9 : 8)) + 'px', borderRightWidth: (self.options.columnsVisible ? 1 : 0) + 'px', textAlign: v.align }) .html(v.id in data ? formatValue(data[v.id], v.format) : '') .appendTo($item); }); function formatValue(value, format) { if (value === null) { value = ''; } else if (format) { value = Ox.isObject(format) ? Ox['format' + Ox.toTitleCase(format.type)] .apply(this, $.merge([value], format.args)) : format(value); } else if (Ox.isArray(value)) { value = value.join(', '); } return value; } //Math.random() < 0.01 && Ox.print('item', data, $item); return $item; } function dragstartColumn(id, e) { self.drag = { startX: e.clientX, startPos: getColumnPositionById(id) } $.extend(self.drag, { stopPos: self.drag.startPos, offsets: $.map(self.visibleColumns, function(v, i) { return self.columnOffsets[i] - self.columnOffsets[self.drag.startPos] }) }); $('.OxColumn' + Ox.toTitleCase(id)).css({ opacity: 0.25 }); that.$titles[self.drag.startPos].addClass('OxDrag').css({ // fixme: why does the class not work? cursor: 'move' }); } function dragColumn(id, e) { var d = e.clientX - self.drag.startX, pos = self.drag.stopPos; Ox.forEach(self.drag.offsets, function(v, i) { if (d < 0 && d < v) { self.drag.stopPos = i; return false; } else if (d > 0 && d > v) { self.drag.stopPos = i; } }); if (self.drag.stopPos != pos) { moveColumn(id, self.drag.stopPos); } } function dragendColumn(id, e) { var column = self.visibleColumns.splice(self.drag.stopPos, 1)[0], width = self.columnWidths.splice(self.drag.stopPos, 1)[0]; self.visibleColumns.splice(self.drag.stopPos, 0, column); self.columnWidths.splice(self.drag.stopPos, 0, width); that.$head.$content.empty(); constructHead(); $('.OxColumn' + Ox.toTitleCase(id)).css({ opacity: 1 }); that.$titles[self.drag.stopPos].removeClass('OxDrag').css({ cursor: 'pointer' }); that.$body.clearCache(); triggerColumnChangeEvent(); } function dragstartResize(id, e) { var pos = getColumnPositionById(id); self.drag = { startX: e.clientX, startWidth: self.columnWidths[pos] }; } function dragResize(id, e) { var width = Ox.limit( self.drag.startWidth - self.drag.startX + e.clientX, self.options.columnWidth[0], self.options.columnWidth[1] ); resizeColumn(id, width); } function dragendResize(id, e) { var pos = getColumnPositionById(id); that.triggerEvent('columnresize', { id: id, width: self.columnWidths[pos] }); } function getCell(id, key) { Ox.print('getCell', id, key) var $item = getItem(id); return $($item.find('.OxCell.OxColumn' + Ox.toTitleCase(key))[0]); } function getColumnIndexById(id) { return Ox.getPositionById(self.options.columns, id); } function getColumnPositionById(id) { return Ox.getPositionById(self.visibleColumns, id); } function getItem(id) { //Ox.print('getItem', id) var $item = null; $.each(that.find('.OxItem'), function(i, v) { $v = $(v); if ($v.data('id') == id) { $item = $v; return false; } }); return $item; } function getItemWidth() { return Math.max( Ox.sum(self.columnWidths), that.$element.width() - (self.options.scrollbarVisible ? Ox.UI.SCROLLBAR_SIZE : 0) ); //return Ox.sum(self.columnWidths) } function moveColumn(id, pos) { // fixme: column head should be one element, not three //Ox.print('moveColumn', id, pos) var startPos = getColumnPositionById(id), stopPos = pos, startClassName = '.OxColumn' + Ox.toTitleCase(id), stopClassName = '.OxColumn' + Ox.toTitleCase(self.visibleColumns[stopPos].id), insert = startPos < stopPos ? 'insertAfter' : 'insertBefore' $column = $('.OxTitle' + startClassName), $order = $column.next(), $resize = $order.next(); //Ox.print(startClassName, insert, stopClassName) $column.detach()[insert](insert == 'insertAfter' ? $('.OxTitle' + stopClassName).next().next() : $('.OxTitle' + stopClassName)); $order.detach().insertAfter($column); $resize.detach().insertAfter($order); $.each(that.$body.find('.OxItem'), function(i, v) { var $v = $(v); $v.children(startClassName).detach()[insert]($v.children(stopClassName)); }); var column = self.visibleColumns.splice(startPos, 1)[0], width = self.columnWidths.splice(startPos, 1)[0]; self.visibleColumns.splice(stopPos, 0, column); self.columnWidths.splice(stopPos, 0, width); } function removeColumn(id) { //Ox.print('removeColumn', id); var className = '.OxColumn' + Ox.toTitleCase(id), index = getColumnIndexById(id), itemWidth, position = getColumnPositionById(id), $column = $('.OxTitle' + className), $order = $column.next(), $resize = $order.next(); self.options.columns[index].visible = false; self.visibleColumns.splice(position, 1); self.columnWidths.splice(position, 1); that.$head.$content.empty(); constructHead(); itemWidth = getItemWidth(); $.each(that.$body.find('.OxItem'), function(i, v) { var $v = $(v); $v.children(className).remove(); $v.css({ width: itemWidth + 'px' }); }); that.$body.$content.css({ width: itemWidth + 'px' }); that.$body.options({ keys: $.map(self.visibleColumns, function(v, i) { return v.id; }) }); //that.$body.clearCache(); } function resetColumn(id) { var width = self.defaultColumnWidths[getColumnIndexById(id)]; resizeColumn(id, width); that.triggerEvent('columnresize', { id: id, width: width }); } function resizeColumn(id, width) { var i = getColumnIndexById(id), pos = getColumnPositionById(id); self.options.columns[i].width = width; self.columnWidths[pos] = width; if (self.options.columnsVisible) { that.$head.$content.css({ width: (Ox.sum(self.columnWidths) + 2) + 'px' }); that.$titles[pos].css({ width: (width - 9 - (i == self.selectedColumn ? 16 : 0)) + 'px' }); } that.find('.OxCell.OxColumn' + Ox.toTitleCase(self.options.columns[i].id)).css({ width: (width - (self.options.columnsVisible ? 9 : 8)) + 'px' }); setWidth(); } function setWidth() { var width = getItemWidth(); that.$body.$content.find('.OxItem').css({ // fixme: can we avoid this lookup? width: width + 'px' }); that.$body.$content.css({ width: width + 'px' // fixme: check if scrollbar visible, and listen to resize/toggle event }); } function toggleSelected(id) { var pos = getColumnPositionById(id); if (pos > -1) { updateOrder(id); 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() + (that.$titles[pos].hasClass('OxSelected') ? -16 : 16) ) + 'px' }); } } function triggerColumnChangeEvent() { that.triggerEvent('columnchange', { ids: $.map(self.visibleColumns, function(v, i) { return v.id; }) }); } function updateOrder(id) { var pos = getColumnPositionById(id); //Ox.print(id, pos) that.$titles[pos].next().html(Ox.UI.symbols[ 'triangle_' + (self.options.sort[0].operator == '+' ? 'up' : 'down') ]); } self.onChange = function(key, value) { if (key == 'items') { //alert('request set!!') that.$body.options(key, value); } else if (key == 'paste') { that.$body.options(key, value); } else if (key == 'selected') { that.$body.options(key, value); } }; // fixme: doesn't work, doesn't return that that.closePreview = that.$body.closePreview; that.editCell = function(id, key) { Ox.print('editCell', id, key) var $item = getItem(id), $cell = getCell(id, key), $input, html = $cell.html(), index = getColumnIndexById(key), column = self.options.columns[index], width = column.width - self.options.columnsVisible; $cell.empty() .addClass('OxEdit') .css({ width: width + 'px' }); $input = new Ox.Input({ autovalidate: column.input ? column.input.autovalidate : null, style: 'square', value: html, width: width }) .bind({ mousedown: function(e) { // keep mousedown from reaching list e.stopPropagation(); } }) .bindEvent({ blur: submit, }) .appendTo($cell); //.focusInput(); setTimeout($input.focusInput, 0); // fixme: strange function submit() { var value = $input.value(); //$input.loseFocus().remove(); // fixme: leaky, inputs remain in focus stack $cell.removeClass('OxEdit') .css({ width: (width - 8) + 'px' }) .html(value) that.triggerEvent('submit', { id: id, key: key, value: value }); } } that.gainFocus = function() { that.$body.gainFocus(); return that; }; that.loseFocus = function() { that.$body.loseFocus(); return that; } that.paste = function(data) { that.$body.paste(); return that; }; that.reloadList = function() { that.$body.reloadList(); return that; }; that.resizeColumn = function(id, width) { resizeColumn(id, width); return that; } that.size = function() { setWidth(); that.$body.size(); } that.sortList = function(key, operator) { var isSelected = key == self.options.sort[0].key; self.options.sort = [{key: key, operator: operator}]; if (self.options.columnsVisible) { if (isSelected) { updateOrder(self.options.columns[self.selectedColumn].id); } else { toggleSelected(self.options.columns[self.selectedColumn].id); self.selectedColumn = getColumnIndexById(key); toggleSelected(self.options.columns[self.selectedColumn].id); } } that.$body.sortList(self.options.sort[0].key, self.options.sort[0].operator); return that; }; that.value = function(id, key, value) { // fixme: make this accept id, {k: v, ...} var $item = getItem(id), //$cell = getCell(id, key), column = self.options.columns[getColumnIndexById(key)]; if (arguments.length == 1) { return that.$body.value(id); } else if (arguments.length == 2) { return that.$body.value(id, key); } else { that.$body.value(id, key, value); /* $cell && $cell.html(column.format ? column.format(value) : value); if (column.unique) { that.$body.setId($item.data('id'), value); $item.data({id: value}); } */ return that; } } return that; };