// vim: et:ts=4:sw=4:sts=4:ft=js

/*@
Ox.TextList <f:Ox.Element> TextList Object
    () ->              <f> TextList Object
    (options) ->       <f> TextList Object
    (options, self) -> <f> TextList Object
    options <o> Options object
        columns <[o]|[]>
            Fixme: There's probably more...
            addable <b>
            editable <b>
            format <f>
            id <s>
            removable <b>
            map <f> function that maps values to sort values
            operator <s> default sort operator
            title <s>
            unique <b> If true, this column acts as unique id
            visible <b>
            width <n>
        columnsMovable <b|false>
        columnsRemovable <b|false>
        columnsResizable <b|false>
        columnsVisible <b|false>
        columnWidth <a|[40, 800]>
        id <s|''>
        items <f|null> function() {} {sort, range, keys, callback} or array
        max <n|-1>
        min <n|0>
        pageLength <n|100>
        scrollbarVisible <b|false>
        selected <a|[]>
        sort <a|[]>
    self    <o> shared private variable
@*/

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')
            .bindEvent({
                key_left: function() {
                    var $element = that.$body.$element,
                        scrollLeft = $element[0].scrollLeft - $element.width();
                    $element.animate({scrollLeft: scrollLeft}, 250);
                },
                key_right: function() {
                    var $element = that.$body.$element,
                        scrollLeft = $element[0].scrollLeft + $element.width();
                    $element.animate({scrollLeft: scrollLeft}, 250);
                },
                keys: find
            });

    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(data) {
                that.editCell(data.id, data.key);
            },
            init: function(data) {
                // fixme: why does this never reach?
                //Ox.print('INIT????')
                //that.triggerEvent('init', data);
            },
            select: function(data) {
                Ox.print('SELECT????')
                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;
        self.options.sort = [{
            key: self.options.columns[i].id,
            operator: isSelected ?
                (self.options.sort[0].operator == '+' ? '-' : '+') :
                self.options.columns[i].operator,
            map: self.options.columns[i].map
        }]
        updateColumn();
        // fixme: strangely, sorting the list blocks updating the column,
        // so we use a timeout for now
        setTimeout(function() {
            that.$body.options({sort: self.options.sort});
        }, 10)
    }

    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)
                .appendTo(that.$head.$content.$element);
            // if sort operator is set, bind click event
            if (v.operator) {
                that.$titles[i].bindEvent({
                    anyclick: function(event, e) {
                        clickColumn(v.id);
                    }
                });
            }
            // if columns are movable, bind drag events
            if (self.options.columnsMovable) {
                that.$titles[i].bindEvent({
                    dragstart: function(event, e) {
                        dragstartColumn(v.id, e);
                    },
                    drag: function(event, e) {
                        dragColumn(v.id, e);
                    },
                    dragend: function(event, e) {
                        dragendColumn(v.id, e);
                    }
                })
            }
            $order = $('<div>')
                .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 columns are resizable, bind click and drag events
            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 = $('<div>').addClass('OxLeft').appendTo($resize.$element);
            $center = $('<div>').addClass('OxCenter').appendTo($resize.$element);
            $right = $('<div>').addClass('OxRight').appendTo($resize.$element);
        });
        that.$head.$content.css({
            width: (Ox.sum(self.columnWidths) + 2) + 'px'
        });
        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 = $('<div>')
                .addClass('OxTarget')
                .css({
                    width: getItemWidth(true) + 'px'
                });
        self.visibleColumns.forEach(function(v, i) {
            //Ox.print(data[v.id], '(--value--)')
            var clickable = Ox.isBoolean(v.clickable) ? v.clickable : v.clickable(data),
                editable = Ox.isBoolean(v.editable) ? v.editable : v.editable(data),
                $cell = $('<div>')
                    .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 find(data) {
        // fixme: works only if items are an array
        var query = data.keys,
            sort = self.options.sort[0];
        Ox.print('QUERY', query)
        Ox.forEach(self.options.items, function(item, i) {
            var value = (
                sort.map ? sort.map(item[sort.key]) : item[sort.key]
            ).toString().toLowerCase();
            if (Ox.startsWith(value, query)) {
                that.$body.options({selected: [item[self.unique]]});
                Ox.print('QUERY', query, 'VALUE', value)
                return false;
            }
        });
    }

    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(cached) {
        // fixme: this gets called for every constructItem and is slooow
        // the proper way to fix this would be to find out how and when
        // that.$element.width() might change... which would probably
        // mean binding to every SplitPanel and window resize...
        // for now, use a cached value
        if (!cached) {
            self.cachedWidth = that.$element.width();
        } else if (!self.cachedWidth || self.cachedWidthTime < +new Date() - 5000) {
            self.cachedWidth = that.$element.width();
            self.cachedWidthTime = +new Date();
        }
        return Math.max(
            Ox.sum(self.columnWidths),
            self.cachedWidth -
            (self.options.scrollbarVisible ? Ox.UI.SCROLLBAR_SIZE : 0)
        );
    }

    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 updateColumn() {
        var columnId = self.options.columns[self.selectedColumn].id
            isSelected = columnId == self.options.sort[0].key;
        if (self.options.columnsVisible) {
            if (isSelected) {
                updateOrder(columnId);
            } else {
                toggleSelected(columnId);
                self.selectedColumn = getColumnIndexById(self.options.sort[0].key);
                toggleSelected(self.options.columns[self.selectedColumn].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.setOption = function(key, value) {
        //Ox.print('---------------------------- TextList setOption', key, value)
        if (key == 'items') {
            that.$body.options(key, value);
        } else if (key == 'paste') {
            that.$body.options(key, value);
        } else if (key == 'selected') {
            that.$body.options(key, value);
        } else if (key == 'sort') {
            updateColumn();
            that.$body.options(key, value);
        }
    };

    that.closePreview = function() {
        that.$body.closePreview();
        return that;
    };

    that.addItem = function(item) {
        /*
        self.options.items.push(item);
        that.$body.options({items: self.options.items});
        //that.$body.options({selected: [item.id]});
        */
    }

    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(stayAtPosition) {
        that.$body.reloadList(stayAtPosition);
        return that;
    };

    that.resizeColumn = function(id, width) {
        resizeColumn(id, width);
        return that;
    }

    that.size = function() {
        Ox.print('SIZE FUNCTION CALLED')
        setWidth();
        that.$body.size();
    }

    // fixme: deprecated
    that.sortList = function(key, operator) {
        Ox.print('$$$$ DEPRECATED $$$$')
        var isSelected = key == self.options.sort[0].key;
        self.options.sort = [{
            key: key,
            operator: operator,
            map: self.options.columns[self.selectedColumn].sort
        }];
        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);
            }
        }
        // fixme: strangely, sorting the list blocks toggling the selection,
        // so we use a timeout for now
        setTimeout(function() {
            that.$body.options({sort: self.options.sort});
            /*
            that.$body.sortList(
                self.options.sort[0].key,
                self.options.sort[0].operator,
                self.options.sort[0].map
            );
            */
        }, 10);
        return that;
    };

    that.value = function(id, key, value) {
        // fixme: make this accept id, {k: v, ...}
        Ox.print('value', id, key, value)
        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 (key == self.options.sort[0].key) {
                that.$body.sort();
            }
            /* fixme: something like this is needed:
            if (column.unique) {
                that.$body.setId($item.data('id'), value);
                $item.data({id: value});
            }
            */
            return that;
        }
    }

    return that;

};
