Viewing File: /usr/local/cpanel/base/cjt/datatable.js

/*
    # base/cjt/datatable.js                           Copyright(c) 2020 cPanel, L.L.C.
    #                                                           All rights reserved.
    # copyright@cpanel.net                                         http://cpanel.net
    # This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

// Extensions to YUI DataTable
// Requires DataSource and Paginator

(function() {

    var DT = YAHOO.widget.DataTable;

    // This fixes issue #2529475, which wasn't addressed in YUI 2.9.0 and
    // thus will remain broken in YUI 2.
    if (!YAHOO.widget.Paginator._fixed_2529475) {
        var Dom = DOM;
        var Paginator = YAHOO.widget.Paginator;

        YAHOO.widget.Paginator._fixed_2529475 = true;

        YAHOO.widget.Paginator.prototype.updateVisibility = function(e) {
            var alwaysVisible = this.get("alwaysVisible"),
                totalRecords, visible, rpp, rppOptions, i, len, opt;

            if (!e || e.type === "alwaysVisibleChange" || !alwaysVisible) {
                totalRecords = this.get("totalRecords");
                visible = true;
                rpp = this.get("rowsPerPage");
                rppOptions = this.get("rowsPerPageOptions");

                if (isArray(rppOptions)) {
                    for (i = 0, len = rppOptions.length; i < len; ++i) {
                        opt = rppOptions[i];

                        // account for value 'all'
                        if (lang.isNumber(opt.value || opt)) { // THIS IS THE FIX.
                            rpp = Math.min(rpp, (opt.value || opt));
                        }
                    }
                }

                if (totalRecords !== Paginator.VALUE_UNLIMITED &&
                    totalRecords <= rpp) {
                    visible = false;
                }

                visible = visible || alwaysVisible;

                for (i = 0, len = this._containers.length; i < len; ++i) {
                    Dom.setStyle(this._containers[i], "display",
                        visible ? "" : "none");
                }
            }
        };
    }


    if (!DT._has_message_last_row) {
        DT._has_message_last_row = true;

        var _show = DT.prototype.showTableMessage;
        DT.prototype.showTableMessage = function() {
            if (this.getTbodyEl().rows.length === 0) {
                DOM.addClass(this.getMsgTdEl().parentNode, "cjt_last_row");
            }
            return _show.apply(this, arguments);
        };
        var _hide = DT.prototype.hideTableMessage;
        DT.prototype.hideTableMessage = function() {
            DOM.removeClass(this.getMsgTdEl().parentNode, "cjt_last_row");
            return _hide.apply(this, arguments);
        };
    }

    // Remove hidden table message text so copy/paste doesn't show it.
    // YUI 2.9.0 did not fix this; it likely will remain broken in YUI 2.
    if (!DT._fixed_2529233_message_text) {
        DT._fixed_2529233_message_text = true;

        var _hideTableMessage = DT.prototype.hideTableMessage;
        DT.prototype.hideTableMessage = function() {
            var ret = _hideTableMessage.apply(this, arguments);
            this.getMsgTdEl().firstChild.innerHTML = "";
            return ret;
        };
    }

    if (!DT.fixed_reorderColumn_class_bug_2529435) {
        DT.fixed_reorderColumn_class_bug_2529435 = true;
        var _reorder = DT.prototype.reorderColumn;
        DT.prototype.reorderColumn = function(col, idx) {
            var old_idx = col.getIndex();
            var cols_last_i = this.getColumnSet().keys.length - 1;

            var ret = _reorder.apply(this, arguments);

            if (ret) {
                var changed_first = (idx === 0) || (old_idx === 0);
                var changed_last = (idx === cols_last_i) || (old_idx === cols_last_i);

                var rows, rows_length, cur_row, row_cells, old_cell, cur_cell;
                var class_first = DT.CLASS_FIRST,
                    class_last = DT.CLASS_LAST;
                if (changed_first || changed_last) {
                    rows = this.getTbodyEl().rows;
                    rows_length = rows.length;
                    for (var r = 0; cur_row = rows[r]; r++) {
                        row_cells = cur_row.cells;
                        cur_cell = row_cells[idx];

                        // dragged a column to be first
                        if (idx === 0) {
                            old_cell = row_cells[1];

                            // dragged column was last
                            if (old_idx === cols_last_i) {
                                cur_cell.className = cur_cell.className.replace(class_last, class_first);
                            } else {
                                cur_cell.className += " " + class_first;
                            }

                            if (cols_last_i === 1) {
                                old_cell.className = old_cell.className.replace(class_first, class_last);
                            } else {
                                DOM.removeClass(old_cell, class_first);
                            }
                        } else if (old_idx === 0) { // dragged a column that *was* first
                            old_cell = row_cells[0];

                            // dragged column is now last
                            if (idx === cols_last_i) {
                                cur_cell.className = cur_cell.className.replace(class_first, class_last);
                            } else {
                                DOM.removeClass(cur_cell, class_first);
                            }

                            if (cols_last_i === 1) {
                                old_cell.className = old_cell.className.replace(class_last, class_first);
                            } else {
                                old_cell.className += " " + class_first;
                            }
                        } else if (idx === cols_last_i) { // dragged a column to be last
                            cur_cell.className += " " + class_last;
                            DOM.removeClass(row_cells[cols_last_i], class_last);
                        } else if (old_idx === cols_last_i) { // dragged a column that *was* last
                            row_cells[cols_last_i].className += " " + class_last;
                            DOM.removeClass(cur_cell, class_last);
                        }
                    }
                }
            }

            return ret;
        };
    }

    // add max_width and fixed_width DataTable properties,
    // and make column resizing not affect the table width
    // (except for the last column, of course)
    if (!DT._has_max_width_and_fixed_width) {
        DT._has_max_width_and_fixed_width = true;

        var _initAttributes = DT.prototype.initAttributes;
        DT.prototype.initAttributes = function() {
            _initAttributes.apply(this, arguments);

            var that = this;

            // the last column cannot have an active resize handle
            var _fixed_width_reorder_listener = function(oArgs) {
                var columns = that.getColumnSet().flat;
                for (var c = 0; c < columns.length; c++) {
                    var cur_resizer_el = columns[c].getResizerEl();
                    if (cur_resizer_el) {
                        var th_cursor = DOM.getStyle(columns[c].getThEl(), "cursor");
                        cur_resizer_el.style.cursor = (c === columns.length - 1) ? th_cursor : "";
                    }
                }
            };

            this.setAttributeConfig("max_width", {
                value: null,
                validator: YAHOO.lang.isNumber
            });
            this.setAttributeConfig("fixed_width", {
                value: null,
                method: function(new_fixed_width) {
                    if (new_fixed_width) {
                        this.subscribe("initEvent", _fixed_width_reorder_listener);
                        this.subscribe("columnReorderEvent", _fixed_width_reorder_listener);
                    } else {
                        this.unsubscribe("columnReorderEvent", _fixed_width_reorder_listener);
                    }
                }
            });
        };

        // We need a way of identifying in CSS which drag handles are going to be
        // inactive when a table has fixed_width.
        //
        // The _initResizeableColumns method is private...
        // but it should be stable for YUI 2 (which should have no more releases).
        var _irc = DT.prototype._initResizeableColumns;
        DT.prototype._initResizeableColumns = function() {
            _irc.apply(this, arguments);

            var cols = this.getColumnSet().keys;
            var cur_col, next_col;
            for (var c = 0;
                (cur_col = cols[c]) && (next_col = cols[c + 1]); c++) {
                if (cur_col.resizeable) {
                    if (next_col.resizeable) {
                        DOM.removeClass(cur_col._elResizer, "inactive");
                    } else {
                        DOM.addClass(cur_col._elResizer, "inactive");
                    }
                }
            }
        };

        // we want the datatable not to expand unless the last column is expanded
        var _onMouseDown = YAHOO.util.ColumnResizer.prototype.onMouseDown;
        YAHOO.util.ColumnResizer.prototype.onMouseDown = function() {
            _onMouseDown.apply(this, arguments);

            // for all columns besides the last one,
            // adjust the next column equally to the first
            var this_col = this.column;
            var datatable = this.datatable;
            var columns = datatable.getColumnSet().flat;
            var col_index = this_col.getKeyIndex();

            var this_col_is_rightmost = (columns.length === col_index + 1);

            var custom_setColumnWidth;

            var _setColumnWidth = datatable.setColumnWidth;

            if (!this_col_is_rightmost) {
                var other_column = columns[col_index + 1];
                while (other_column && (other_column.hidden || !other_column.resizeable)) {
                    col_index++;
                    other_column = columns[col_index + 1];
                }

                if (other_column) {
                    var other_column_head_cell_liner = other_column.getThLinerEl();
                    var other_column_n_liner_padding =
                        (parseInt(DOM.getStyle(other_column_head_cell_liner, "paddingLeft")) || 0) + (parseInt(DOM.getStyle(other_column_head_cell_liner, "paddingRight")) || 0);
                    var other_column_start_width = other_column_head_cell_liner.offsetWidth - other_column_n_liner_padding;

                    var col_max_width = other_column_start_width + this.startWidth - this.nLinerPadding - 1;
                    custom_setColumnWidth = function(col, width) {
                        if (width <= col_max_width) {
                            _setColumnWidth.call(this, col, width);
                            _setColumnWidth.call(this, other_column, col_max_width - width + 1);
                        }
                    };
                } else {

                    // abort drag if this is not the rightmost column,
                    // fixed_width is enabled,
                    // and there are no other columns to adjust
                    return this.datatable.get("fixed_width") ? false : true;
                }
            } else if (!this.datatable.get("fixed_width")) {
                var table_max_width = this.datatable.get("max_width");
                if (table_max_width) {
                    var max_col_width = this.startWidth + table_max_width - (parseInt(DOM.getStyle(this.datatable.getTableEl(), "width")) || 0) - this.nLinerPadding;

                    custom_setColumnWidth = function(col, width) {
                        if (width > max_col_width) {
                            width = max_col_width;
                        }
                        _setColumnWidth.call(this, col, width);
                    };

                }
            } else {
                return false;
            }

            if (custom_setColumnWidth) {
                var has_own = datatable.hasOwnProperty("setColumnWidth");
                datatable.setColumnWidth = custom_setColumnWidth;
                datatable.subscribe("columnResizeEvent", function() {
                    this.unsubscribe("columnResizeEvent", arguments.callee);
                    if (has_own) {
                        datatable.setColumnWidth = _setColumnWidth;
                    } else {
                        delete datatable.setColumnWidth;
                    }
                });
            }
        };
    }

    // relative_widths is a hash of relative content widths that will be scaled
    // such that the table's offsetWidth matches the desired_width.
    // Within relative_widths an item can be either a number primitive or
    // { value: number, absolute: boolean }. Actual Number objects would be
    // well-suited for attaching the "absolute" property", but these were decided
    // against because they are not widely used and present behavioral oddities,
    // e.g. (new Number(0)) === true
    // This will also check for a fixed_width property on a Column and, if there is
    // no relative_width for that column, will leave that column the
    // width that it currently is.
    DT.prototype.set_width = function(desired_width, relative_widths) {
        if (!desired_width) {
            desired_width = this.getTableEl().offsetWidth;
        }
        var cols = this.getColumnSet().flat.filter(function(c) {
            return !c.hidden;
        });
        var cols_length = cols.length;

        // these are only used if !relative_widths
        var relative_content_widths = {};
        var total_relative_content_width = 0;
        var total_fixed_content_width = 0;

        var total_padding = 0;
        var total_border_widths = 0;
        var fixed_column_widths = {};
        var cur_col;
        for (var c = 0; cur_col = cols[c]; c++) {
            var liner_el = cur_col.getThLinerEl();
            var resize_liner_el = liner_el.parentNode;

            var liner_padding_width = (parseFloat(DOM.getStyle(liner_el, "padding-left")) || 0) + (parseFloat(DOM.getStyle(liner_el, "padding-right")) || 0);

            total_padding += liner_padding_width;

            // Firefox/Gecko likes to report fractional values
            // for computed CSS "width", so we compute content width here
            // rather than reading computed style.
            // Also, this really should use clientWidth, but older IE versions
            // sometimes errantly return 0 for clientWidth.
            // We can safely assume, though, that liner elements have no border.
            if (!relative_widths) {
                var liner_content_width = liner_el.offsetWidth - liner_padding_width;

                // see above about IE and clientWidth
                var th_content_width = resize_liner_el.offsetWidth; -(parseFloat(DOM.getStyle(resize_liner_el, "padding-left")) || 0) - (parseFloat(DOM.getStyle(resize_liner_el, "padding-right")) || 0);

                // extra width = cell content outside the liner
                var extra_width = th_content_width - liner_el.offsetWidth;

                if (!cur_col.fixed_width) {
                    relative_content_widths[cur_col.key] = liner_content_width + extra_width;
                    total_relative_content_width += liner_content_width + extra_width;
                }
            }

            var th_el = cur_col.getThEl();

            // see above about IE and clientWidth
            var clientWidth = th_el.clientWidth;
            if (clientWidth) {
                total_border_widths += th_el.offsetWidth - th_el.clientWidth;
            } else {
                total_border_widths +=
                    (parseFloat(DOM.getStyle(th_el, "border-left-width")) || 0) + (parseFloat(DOM.getStyle(th_el, "border-right-width")) || 0);
            }

            if (cur_col.fixed_width && ("width" in cur_col)) {
                total_fixed_content_width += fixed_column_widths[c] = parseFloat(cur_col.width);
                if (relative_widths && !relative_widths[cur_col.key]) {
                    relative_widths[cur_col.key] = {
                        absolute: true,
                        value: fixed_column_widths[c]
                    };
                }
            }
        }

        // this is how much the content widths need to take up in total
        // so that the table takes up the desired width
        var total_available_content_width = desired_width - total_border_widths - total_padding;

        // precompute the widths so that we can compensate for rounding problems,
        // e.g. rounded widths not all adding up as they should
        var new_widths = [];
        var total_new_widths = 0;
        var cur_fixed;
        if (relative_widths) {
            var total_relative_width = 0;
            var total_available_relative_width = total_available_content_width;
            var cur_key;
            for (var w = 0; cur_col = cols[w]; w++) {
                cur_key = cur_col.key;
                if (!relative_widths[cur_key]) {
                    relative_widths[cur_key] = cols[w].width || parseFloat(DOM.getStyle(cur_col.getThLinerEl(), "width"));
                }

                if (relative_widths[cur_key].absolute) {
                    fixed_column_widths[w] = cur_fixed = relative_widths[cur_key].value || relative_widths[cur_key];
                    total_available_relative_width -= cur_fixed;
                } else {
                    total_relative_width += relative_widths[cur_key].value || relative_widths[cur_key];
                }
            }

            for (var c = 0; cur_col = cols[c]; c++) {
                cur_key = cur_col.key;
                var cur_width = relative_widths[cur_key].value || relative_widths[cur_key];
                if (!relative_widths[cur_key].absolute) {
                    cur_width = parseInt(.5 + total_available_relative_width * cur_width / total_relative_width);
                }
                new_widths.push(cur_width);
                total_new_widths += cur_width;
            }
        } else {
            var total_available_relative_width = total_available_content_width - total_fixed_content_width;

            var cur_width;
            for (var c = 0; cur_col = cols[c]; c++) {
                if (cur_col.fixed_width) {
                    cur_width = fixed_column_widths[c];
                } else {
                    cur_width = parseInt(.5 + total_available_relative_width * relative_content_widths[cur_col.key] / total_relative_content_width);
                }
                new_widths.push(cur_width);
                total_new_widths += cur_width;
            }
        }

        if (total_new_widths !== total_available_content_width) {

            // Sort the column indexes by their new_width value,
            // then add/subtract 1 to/from each new_width until the deficit is 0.
            // If the table needs to be bigger, then add to the smallest rows;
            // otherwise, subtract from the largest.
            // We do this by controlling the sort direction of the indexes array.
            var sort_xformer = function(i) {
                return new_widths[i];
            };

            var sorted_indexes = [];
            for (var i = 0; i < cols_length; i++) {
                sorted_indexes.push(i);
            }

            var i = 0;

            var deficit = total_available_content_width - total_new_widths;

            if (deficit > 0) {
                sorted_indexes.sort_by(sort_xformer);
                while (deficit > 0) {
                    if (!(sorted_indexes[i] in fixed_column_widths)) {
                        new_widths[sorted_indexes[i]]++;
                        deficit--;
                    }
                    i++;
                    if (i === cols_length) {
                        i = 0;
                    }
                }
            } else {
                sort_xformer.reverse = true;
                sorted_indexes.sort_by(sort_xformer);
                while (deficit < 0) {
                    if (!(sorted_indexes[i] in fixed_column_widths)) {
                        new_widths[sorted_indexes[i]]--;
                        deficit++;
                    }
                    i++;
                    if (i === cols_length) {
                        i = 0;
                    }
                }
            }
        }

        var table_style = this.getTableEl().style;
        table_style.visibility = "hidden";
        table_style.width = "auto";
        for (var c = 0; c < cols_length; c++) {
            this.setColumnWidth(this.getColumn(cols[c].key), new_widths[c]);
        }
        table_style.visibility = "";
    };

    // Determine default widths based on the pixel width
    // of its header's longest string (in chars) -- or,
    // if a column has "size_to_data", the pixel width of the longest string
    // in the column (in chars).
    DT.prototype.size_columns = function() {

        // When NVData does not provide usable column widths,
        // give each column a default width based on the pixel width
        // of its header's longest string.
        relative_widths = {};
        var column_count = this.getColumnSet().flat.length;
        var test_span = document.createElement("span");
        test_span.style.position = "absolute";
        test_span.style.visibility = "hidden";

        for (var c = 0; c < column_count; c++) {
            var col = this.getColumn(c);
            var cur_liner_el = col.getThLinerEl();
            var longest_word = CPANEL.util.get_text_content(cur_liner_el).split(/\s+/).sort_by("!length")[0];
            CPANEL.util.set_text_content(test_span, longest_word);
            cur_liner_el.appendChild(test_span);
            var min_header_width = test_span.offsetWidth;
            cur_liner_el.removeChild(test_span);

            if (col.default_width_is_absolute) {
                var str;
                var length_cache = {};
                var longest_cell = this.getDataSource().liveData
                    .map(function(r) {
                        str = String(r[col.key]);
                        if (str in length_cache) {
                            return length_cache[str];
                        }
                        return length_cache[str] = str ? str.split(/\s+/).sort_by("!length")[0] : 0;
                    })
                    .sort_by("!length")[0];
                var cell_liner = this.getTdLinerEl({
                    record: this.getRecord(0),
                    column: col
                });
                CPANEL.util.set_text_content(test_span, longest_cell);
                cell_liner.appendChild(test_span);
                var max_data_width = test_span.offsetWidth;
                cell_liner.removeChild(test_span);

                var absolute_width = {
                    value: 1 + Math.max(min_header_width, max_data_width),
                    absolute: true
                };
                relative_widths[col.key] = absolute_width;
            } else {
                relative_widths[col.key] = min_header_width;
            }
        }

        return relative_widths;
    };


    // Allow a certain number of columns on the left or right to be
    // non-draggable.
    //
    // This necessitates messing with private stuff...but YUI 2.9.0 should be
    // pretty fixed (as of May 2011); the team has stated publicly that they
    // intend no further releases of YUI 2.
    // Anyhow, after DataTable creates all of the drag stuff for each column,
    // we now go in and undo that work for the appropriate columns.
    // It's less than ideal; hopefully YUI 3 will include this ability built-in.
    var _ia = DT.prototype.initAttributes;
    DT.prototype.initAttributes = function(oConfigs) {
        _ia.apply(this, arguments);
        this.setAttributeConfig("left_nondraggable_columns", {
            value: 0,
            validator: YAHOO.lang.isNumber
        });
        this.setAttributeConfig("right_nondraggable_columns", {
            value: 0,
            validator: YAHOO.lang.isNumber
        });
    };
    var _idc = DT.prototype._initDraggableColumns;
    DT.prototype._initDraggableColumns = function() {
        _idc.apply(this, arguments);

        var tree = this._oColumnSet.tree[0],
            len = tree.length;
        var left = this.get("left_nondraggable_columns");
        var right = this.get("right_nondraggable_columns");
        for (var i = 0; i < len; i++) {
            if ((i < left) || (i > len - right - 1)) {
                DOM.removeClass(tree[i].getThEl(), DT.CLASS_DRAGGABLE);
                tree[i]._dd.unreg();
                delete tree[i]._dd;
            }
        }
    };

    // So that set_width will still see fixed_width in a Column
    // after a column reorder.
    var _get_def = YAHOO.widget.Column.prototype.getDefinition;
    YAHOO.widget.Column.prototype.getDefinition = function() {
        var def = _get_def.apply(this, arguments);
        def.fixed_width = this.fixed_width;
        return def;
    };


    // config parameters:
    //  search_box        - REQUIRED HTML element or ID
    //  searchable_fields - array
    //  default_sortedBy  - see sortedBy config parameter; use this if the data
    //                      from the DataSource is always sorted a certain way

    var IGNORE_KEY_CODES = {
        9: true, // tab
        16: true, // shift
        17: true, // ctrl
        18: true // alt
    };

    var _SearchableDataTable = function() {
        _SearchableDataTable.superclass.constructor.apply(this, arguments);
        var dt = this;
        var ds = this.getDataSource();

        this._search_box = YAHOO.util.Dom.get(this.get("search_box"));

        var search_timeout = null;
        EVENT.on(this._search_box, "keyup", function(e) {
            clearTimeout(search_timeout);
            if (!(e.keyCode in IGNORE_KEY_CODES)) {
                search_timeout = setTimeout(function() {
                    dt.do_text_search();
                }, 500);
            }
        });

        if (this.get("dynamicData")) {
            this._set_generateRequest();
        } else {

            // for filtering
            var _concatenated_values = [];
            var searchable_fields = (this.get("searchable_fields") || this.getColumnSet().keys.map(function(c) {
                return c.key;
            }));
            var searchable_fields_length = searchable_fields.length;
            var liveData = ds.liveData;
            var liveData_length = liveData.length;
            for (var ld = 0; ld < liveData_length; ld++) {
                var search_values = [];
                var row = liveData[ld];
                for (var v = 0; v < searchable_fields_length; v++) {
                    search_values.push(row[searchable_fields[v]]);
                }
                _concatenated_values.push(search_values.join("\n").toLowerCase());
            }

            ds.subscribe("responseParseEvent", function(oArgs) {
                var req = oArgs.request,
                    resp = oArgs.response;

                // filter
                if (req) {
                    var matches = [];
                    req = req.toLowerCase();

                    var all_results = resp.results;
                    var resp_length = all_results.length;
                    var __concatenated_values = _concatenated_values;
                    for (var r = 0; r < resp_length; r++) {
                        if (__concatenated_values[r].indexOf(req) !== -1) {
                            matches.push(all_results[r]);
                        }
                    }

                    resp.results = matches;
                }
            });

            this.subscribe("dataReturnEvent", function(oArgs) {
                var req = oArgs.request,
                    resp = oArgs.response,
                    pay = oArgs.payload;

                // restore sort
                var need_to_sort = resp.results.length > 1;

                var cur_sort, sort_column;
                if (need_to_sort) {
                    cur_sort = this.getState().sortedBy;
                    sort_column = cur_sort && (cur_sort.column || this.getColumn(cur_sort.key));

                    need_to_sort = sort_column;
                }

                // forego sorting if it would duplicate the DataSource sort
                if (need_to_sort) {
                    var ds_sort = this.get("default_sortedBy");

                    need_to_sort = !ds_sort || cur_sort.key !== ds_sort.key || cur_sort.dir !== ds_sort.dir;
                }

                if (need_to_sort) {
                    if (resp.results === liveData) {
                        resp.results = liveData.slice(0); // so sorts won't affect liveData
                    }

                    var sort_func = sort_column.sortOptions && sort_column.sortOptions.sortFunction;

                    // Get the field to sort
                    var sField = (sort_column.sortOptions && sort_column.sortOptions.field) || sort_column.field;
                    var desc = ((cur_sort.dir === DT.CLASS_DESC) ? true : false);

                    if (sort_func) {
                        resp.results.sort(function(a, b) {
                            return sort_func(a, b, desc, sField);
                        });
                    } else {
                        resp.results.sort_by(desc ? "!" + sField : sField);
                    }
                }

                // reset pagination to first page
                if (pay) {
                    if (pay.pagination) {
                        pay.pagination.recordOffset = 0;
                    } else {
                        pay.pagination = {
                            recordOffset: 0
                        };
                    }
                } else {
                    oArgs.payload = {
                        pagination: {
                            recordOffset: 0
                        }
                    };
                }
            });
        }

        this.subscribe("dynamicDataChange", this._set_generateRequest);
    };

    EVENT.throwErrors = true;
    YAHOO.extend(_SearchableDataTable, DT, {
        initAttributes: function(confs) {
            confs = confs || {};
            _SearchableDataTable.superclass.initAttributes.call(this, confs);

            this.setAttributeConfig("search_box", {
                value: ""
            });
            this.setAttributeConfig("searchable_fields", {
                value: null
            });
            this.setAttributeConfig("default_sortedBy", {
                value: ""
            });
            this.setAttributeConfig("api_request_prototype", {
                value: {}
            });
        },

        _set_generateRequest: function() {
            if (this.get("dynamicData")) {
                var func = _make_generateRequest_func(this.get("api_request_prototype"));
                this.set("generateRequest", func);
            } else {
                this.resetValue("generateRequest");
            }
        },

        _last_query: null,
        _search_box: null,
        _last_local_search: null, // only used if !dynamicData
        _search_box: null,

        getState: function() {
            var state = DT.prototype.getState.apply(this, arguments);
            state.search = this._search_box && this._search_box.value.trim();
            return state;
        },

        do_text_search: function(query, keep_pagination) {
            var state = this.getState();
            var callback = {
                success: this.onDataReturnReplaceRows,
                scope: this,
                argument: state
            };

            if (this.get("dynamicData")) {
                if (!keep_pagination) {
                    state.pagination.recordOffset = 0;
                }
                var request = this.get("generateRequest")(state, this);
                this.getDataSource().sendRequest(request, callback);
                this.showTableMessage(this.get("MSG_LOADING"));
            } else {
                if (!query) {
                    query = this._search_box && this._search_box.value.trim();
                }
                if (query !== this._last_local_search) {
                    this.getDataSource().sendRequest(query, callback);
                    this._last_local_search = query;
                }
            }
        },

        refresh_page: function() {
            return this.do_text_search(null, true);
        }
    });

    var _make_generateRequest_func = function(request_obj_prototype) {
        return function(dt_state, dt) {
            var request_obj;
            if (request_obj_prototype instanceof Function) {
                request_obj = new request_obj_prototype();
            } else {
                request_obj = {};
                YAHOO.lang.augmentObject(request_obj, request_obj_prototype);
            }

            var api_data = {};
            if (dt_state.sortedBy) {
                var sort = (dt_state.sortedBy.dir === DT.CLASS_DESC) ? "!" : "";
                sort += dt_state.sortedBy.key;
                if (dt.getDataSource().get_field_parser(dt_state.sortedBy.key) === "number") {
                    sort = [sort, "numeric"];
                }
                api_data.sort = [sort];
            }
            if (dt_state.search) {
                api_data.filter = [
                    ["*", "contains", dt_state.search]
                ];
            }
            if (dt_state.pagination) {
                api_data.paginate = {
                    start: dt_state.pagination.recordOffset,
                    size: dt_state.pagination.rowsPerPage
                };
            }

            request_obj.api_data = api_data;

            return CPANEL.api.construct_query(request_obj);
        };
    };

    // nvdata    - An initial value for the NVData object (stored as JSON)
    // default_rowsPerPage

    var _Standard_Table = function(id, cols, ds, opts) {
        var nvdata = opts && opts.nvdata || {};

        if (cols && opts && opts.actions) {
            var actions = opts.actions;
            var a, cur_link, link;
            var link_prototypes = actions.map(function(a) {
                cur_link = document.createElement("a");
                cur_link.className = "cjt_table_action_link";
                cur_link.href = "javascript:void(0)";
                CPANEL.util.set_text_content(cur_link, a.label);
                return cur_link;
            });
            var frag = document.createDocumentFragment();
            var new_link;
            var actions_formatter = function(cell, rec) {
                for (a = 0; cur_link = link_prototypes[a]; a++) {
                    new_link = cur_link.cloneNode(true);
                    EVENT.on(new_link, "click", actions[a].handler, rec);
                    frag.appendChild(new_link);
                }
                cell.appendChild(frag);
            };

            var actions_col = {
                key: "actions",
                label: LEXICON.actions,
                formatter: actions_formatter,
                resizeable: false,
                sortable: false
            };

            if (opts.right_nondraggable_columns) {
                opts.right_nondraggable_columns++;
            } else {
                opts.right_nondraggable_columns = 1;
            }

            cols.push(actions_col);
        }

        // we allow 0 as a shorthand for "all"
        var rpp_opts = opts.rows_per_page_options || [25, 100, 150, 0];

        var rows_per_page = nvdata.rows_per_page;
        if (typeof rows_per_page === "undefined") {
            rows_per_page = rpp_opts[0].value || rpp_opts[0];
        }

        var paginator = new YAHOO.widget.Paginator({
            alwaysVisible: false,
            firstPageLinkLabel: LEXICON.first,
            previousPageLinkLabel: LEXICON.previous,
            nextPageLinkLabel: LEXICON.next,
            lastPageLinkLabel: LEXICON.last,
            rowsPerPage: rows_per_page,

            //        template: YAHOO.widget.Paginator.TEMPLATE_DEFAULT + " {JumpToPageDropdown}",
            pageLinks: 5,
            containers: [id + "_top_paginator", id + "_bottom_paginator"]
        });

        paginator.setAttributeConfig("rowsPerPage", {
            getter: function(attr, val) {
                return Number(val) || this.getTotalRecords();
            }
        });

        // defaults for the StandardTable instance
        if (!opts) {
            opts = {};
        }
        YAHOO.lang.augmentObject(opts, {
            MSG_LOADING: CPANEL.icons.ajax + "&nbsp;" + LEXICON.loading,
            MSG_EMPTY: LEXICON.no_records,
            paginator: paginator,
            search_box: id + "_search_box",
            fixed_width: true,
            initialLoad: false,
            draggableColumns: !!YAHOO.util.DD
        }, true); // for now, enforce these defaults


        // Per request from Design, we are foregoing use of YUI Paginator's
        // page size dropdown selector and using a custom-rolled one; ergo
        // rows_per_page_options instead of YUI's rowsPerPageOptions.
        var page_size_changer = DOM.get(id + "_page_size_changer");
        if (page_size_changer) {

            var len = rpp_opts.length;
            var page_size_links = [];
            var clicked_page_size_link;
            for (var c = 0; c < len; c++) {
                var cur_size = rpp_opts[c];
                var label, value;
                if (typeof cur_size === "object") {
                    value = parseInt(cur_size.value);
                    label = cur_size.text || value || LEXICON.all;
                } else {
                    label = cur_size || LEXICON.all;
                    value = parseInt(cur_size);
                }

                var new_link = document.createElement("a");
                CPANEL.util.set_text_content(new_link, label);
                var classes = [];
                if (c === 0) {
                    classes.push("first");
                } else if (len - c === 1) {
                    classes.push("last");
                }
                if (value === rows_per_page) {
                    classes.push("current");
                    clicked_page_size_link = new_link;
                }
                new_link.className = classes.join(" ");

                (function() {
                    var this_value = value;
                    EVENT.on(new_link, "click", function(e) {
                        if (clicked_page_size_link !== this) {
                            DOM.removeClass(clicked_page_size_link, "current");
                            DOM.addClass(this, "current");
                            clicked_page_size_link = this;
                            paginator.all_records_shown = !this_value;
                            paginator.setRowsPerPage(this_value || paginator.getTotalRecords());
                        }
                    });
                })();

                page_size_changer.appendChild(new_link);
                page_size_links.push(new_link);
            }

            paginator.subscribe("render", function() {
                page_size_changer.style.display = "";
            });

            paginator.subscribe("rowsPerPageChange", paginator.updateVisibility, paginator, true);

            paginator.subscribe("totalRecordsChange", function(e) {
                var total = this.getTotalRecords();

                CPANEL.util.set_text_content(id + "_matches_count", LOCALE.maketext("[quant,_1,record,records] match.", total));

                var rpp = this.get("rowsPerPage");

                for (i = 0, len = rpp_opts.length; i < len; ++i) {
                    opt = rpp_opts[i];
                    opt = opt.value || opt;

                    // account for values 0 and 'all'
                    if (opt && YAHOO.lang.isNumber(opt)) {
                        rpp = Math.min(rpp, opt);
                    }
                }

                page_size_changer.style.visibility = (total > rpp) ? "" : "hidden";
            });
        }

        if (nvdata.column_sort) {
            sortedBy = nvdata.column_sort;
            sortedBy.dir = DT[(sortedBy.dir === "asc") ? "CLASS_ASC" : "CLASS_DESC"];
            opts.sortedBy = sortedBy;
        }
        if (nvdata.column_order) {
            cols.sort_by(function(c) {
                return nvdata.column_order.indexOf(c.key);
            });
        }
        if (nvdata.hidden_columns) {
            cols.forEach(function(c) {
                c.hidden = !!nvdata.hidden_columns[c.key];
            });
        }

        _Standard_Table.superclass.constructor.call(this, id + "_container", cols, ds, opts);


        this.subscribe("columnReorderEvent", function(column_oldIndex) {
            this.get("nvdata").column_order = this.getColumnSet().keys.map(function(c) {
                return c.key;
            });
            this._save_nvdata();
        });
        this.subscribe("columnResizeEvent", function() {
            this._save_column_widths();
        });
        this.subscribe("columnSortEvent", function(column_dir) {
            dir = (column_dir.dir === DT.CLASS_ASC) ? "asc" : "desc";
            this.get("nvdata").column_sort = {
                key: column_dir.column.key,
                dir: dir
            };
            this._save_nvdata();
        });
        this.subscribe("columnShowEvent", function resizer(oArgs) {
            this.set_width(this._initial_width);
            this._update_nvdata_hidden_columns();
            this._save_column_widths();
        });
        this.subscribe("columnHideEvent", function resizer() {
            this.set_width(this._initial_width);
            this._update_nvdata_hidden_columns();
            this._save_column_widths();
        });

        var initial_width = this.getTableEl().offsetWidth;
        this._initial_width = initial_width;

        this.subscribe("beforeRenderEvent", function hider() {
            this.unsubscribe("beforeRenderEvent", hider);
            DOM.setStyle(id + "_container", "visibility", "hidden");
        });

        this.subscribe("postRenderEvent", function postrender() {
            this.unsubscribe("postRenderEvent", postrender);

            var search_placeholder_overlay = new CPANEL.widgets.Text_Input_Placeholder(
                id + "_search_box",
                LEXICON.search
            );

            this.subscribe("postRenderEvent", function() {
                search_placeholder_overlay.align();
            });
            this.search_placeholder_overlay = search_placeholder_overlay;
        });

        this.subscribe("postRenderEvent", function show_table() {

            // We can't do this until there is at least one record.
            if (!this.getRecord(0)) {
                return;
            }

            this.unsubscribe("postRenderEvent", show_table);

            var relative_widths = {};
            var column_count = cols.length;
            var we_have_useful_nvdata_widths = ("column_widths" in nvdata) && (cols.every(function(cd) {
                return cd.hidden || nvdata.column_widths[cd.key] || nvdata.column_widths[cd.key] === 0;
            }));
            if (we_have_useful_nvdata_widths) {
                for (var c = 0; c < column_count; c++) {
                    relative_widths[cols[c].key] = nvdata.column_widths[cols[c].key];
                }
            } else {
                relative_widths = this.size_columns();
            }

            // This overrides whatever nvdata has for the actions column.
            var actions_col = this.getColumn("actions");
            if (actions_col) {
                var first_actions_liner = this.getTdLinerEl({
                    record: this.getRecord(0),
                    column: this.getColumn("actions")
                });
                if (first_actions_liner) {
                    var first_link = first_actions_liner.children[0];
                    if (first_link) {
                        var last_link = DOM.getLastChild(first_actions_liner);
                        var left = DOM.getX(first_link);
                        var right = DOM.getX(last_link) + last_link.offsetWidth;
                        relative_widths.actions = {
                            value: right - left + 1,
                            absolute: true
                        };
                        this.getColumn("actions").fixed_width = true;
                    }
                }
            }

            this.set_width(this._initial_width, relative_widths);
            if (!we_have_useful_nvdata_widths) {
                this._save_column_widths();
            }

            var table = this;
            paginator.subscribe("rowsPerPageChange", function(args) {
                paginator.updateVisibility();
                table.get("nvdata").rows_per_page = paginator.all_records_shown ? 0 : args.newValue;
                table._save_nvdata();
            });

            DOM.setStyle(id + "_container", "visibility", "");
        });

        // for old IE (7, 6?)
        this.getTableEl().cellSpacing = 0;

        // tooltips
        this.subscribe("cellFormatEvent", function(oArgs) {
            if (!oArgs.el.title && !DOM.hasClass(oArgs.el.parentNode, "yui-dt-col-actions")) {
                oArgs.el.title = CPANEL.util.get_text_content(oArgs.el);
            }
        });


        this._column_select_link = DOM.get(id + "_column_select_link");
        if (this._column_select_link) {
            EVENT.on(this._column_select_link, "click", this.toggle_column_select, this, true);
        }

        var reload_link = DOM.get(id + "_reload_link");
        if (reload_link) {
            this._reload_link = reload_link;
            EVENT.on(reload_link, "click", this.reload, this, true);
        }

        this.subscribe("dataReturnEvent", function(args) {

        });
    };
    _Standard_Table.COLUMN_SELECT_ITEM_TEMPLATE = "<label><input type=\"checkbox\" {checked_html} /> {column_name_html}</label>";
    YAHOO.lang.extend(_Standard_Table, _SearchableDataTable, {

        initAttributes: function() {
            var table = this;

            this.setAttributeConfig("nvdata", {
                value: {},
                validator: YAHOO.lang.isObject
            });

            return _Standard_Table.superclass.initAttributes.apply(this, arguments);
        },

        reload: function() {
            if (this._reload_link) {
                DOM.addClass(this._reload_link, "reloading");
            }
            if (this.get("dynamicData")) {
                this.do_text_search();
                if (this._reload_link) {
                    this.subscribe("postRenderEvent", function stop_reloading() {
                        this.unsubscribe("postRenderEvent", stop_reloading);
                        DOM.removeClass(this._reload_link, "reloading");
                    });
                }
            } else {
                location.reload();
            }
        },

        toggle_column_select: function(e) {
            if (this._column_select_shown) {
                this._column_select_shown.destroy();
                EVENT.removeListener(document.body, "mousedown", this._column_select_shown.body_listener);
                delete this._column_select_shown;
                return;
            }

            var ctrl_el = this._column_select_link || DOM.get(this.getId() + "_column_select_link");
            if (ctrl_el) {
                var column_select = new YAHOO.widget.Overlay(this.getId() + "_column_select", {
                    context: [ctrl_el, "tr", "br"]
                });
                DOM.addClass(column_select.element, "cjt_table_column_select");

                var row_template = _Standard_Table.COLUMN_SELECT_ITEM_TEMPLATE;
                var body_html = "<ul class=\"cjt_table_column_select_list\">" + this.getColumnSet().flat.map(function(c) {
                    return "<li>" + YAHOO.lang.substitute(row_template, {
                        checked_html: (c.hidden ? "" : "checked='checked'"),
                        column_name_html: c.label.html_encode()
                    }) + "</li>";
                }).join("") + "</ul>";

                DOM.addClass(column_select.element, "column_select");

                column_select.showEvent.unsubscribe(column_select.showMacGeckoScrollbars, column_select);

                // blackhole this since there is no way to disable it
                column_select.showMacGeckoScrollbars = function() {};

                column_select.hideMacGeckoScrollbars();

                column_select.setBody(body_html);
                var inputs = DOM.getElementsBy(Boolean, "input", column_select.body);
                var table = this;
                inputs.forEach(function(el, idx) {
                    var key = table.getColumn(idx);
                    EVENT.addListener(el, "click", function(e) {
                        table[el.checked ? "showColumn" : "hideColumn"](key);
                    });
                });

                column_select.render(ctrl_el.parentNode);
                ctrl_el.parentNode.insertBefore(column_select.element, ctrl_el.nextSibling);

                column_select.show();

                column_select.hideEvent.subscribe(function() {

                    // for some reason this is necessary...
                    setTimeout(function() {
                        column_select.destroy();
                    }, 100);
                });

                YAHOO.util.Event.on(column_select.element, "mousedown", function(e) {
                    YAHOO.util.Event.stopPropagation(e);
                });
                var body_listener = function(e) {
                    var target = YAHOO.util.Event.getTarget(e);
                    if ((ctrl_el === target) || DOM.isAncestor(ctrl_el, target)) {
                        return;
                    }
                    EVENT.removeListener(document.body, "mousedown", body_listener);
                    table.toggle_column_select();
                };
                EVENT.on(document.body, "mousedown", body_listener);

                this._column_select_shown = column_select;
                column_select.body_listener = body_listener;
            }
        },


        _update_nvdata_hidden_columns: function() {
            var hidden_columns = {};
            this.getColumnSet().flat.forEach(function(c) {
                hidden_columns[c.key] = c.hidden;
            });
            this.get("nvdata").hidden_columns = hidden_columns;
        },
        _save_hidden_columns: function() {
            this._save_nvdata();
        },
        _save_column_widths: function() {
            var column_widths = {};
            var columns = this.getColumnSet().flat;
            for (var c = 0; c < columns.length; c++) {
                var cur_column = columns[c];
                if (!cur_column.hidden && cur_column.width) {
                    column_widths[cur_column.key] = cur_column.width;
                }
            }
            this.get("nvdata").column_widths = column_widths;
            this._save_nvdata();
        },

        _save_nvdata: function() {
            CPANEL.nvdata.save(this.get("nvdata"));
        }
    });

    if (!CPANEL.datatable) {
        CPANEL.datatable = {};
    }
    YAHOO.lang.augmentObject(CPANEL.datatable, {
        Standard_Table: _Standard_Table,
        SearchableDataTable: _SearchableDataTable,
        make_generateRequest_func: _make_generateRequest_func
    });

})();
Back to Directory File Manager