/* Copyright (c) 2007, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.2.2 */ /** * The DataTable widget provides a progressively enhanced DHTML control for * displaying tabular data across A-grade browsers. * * @module datatable * @requires yahoo, dom, event, datasource * @optional dragdrop * @title DataTable Widget * @beta */ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /** * DataTable class for the YUI DataTable widget. * * @class DataTable * @uses YAHOO.util.EventProvider * @constructor * @param elContainer {HTMLElement} Container element for the TABLE. * @param oColumnSet {YAHOO.widget.ColumnSet} ColumnSet instance. * @param oDataSource {YAHOO.util.DataSource} DataSource instance. * @param oConfigs {object} (optional) Object literal of configuration values. */ YAHOO.widget.DataTable = function(elContainer,oColumnSet,oDataSource,oConfigs) { // Internal vars var i, ok; this._nIndex = YAHOO.widget.DataTable._nCount; this._sName = "instance" + this._nIndex; this.id = "yui-dt"+this._nIndex; // Validate configs if(oConfigs && (oConfigs.constructor == Object)) { for(var sConfig in oConfigs) { this[sConfig] = oConfigs[sConfig]; } } // Validate DataSource if(oDataSource) { if(oDataSource instanceof YAHOO.util.DataSource) { this.dataSource = oDataSource; } else { } } // Validate ColumnSet if(oColumnSet && (oColumnSet instanceof YAHOO.widget.ColumnSet)) { this._oColumnSet = oColumnSet; } else { return; } // Create RecordSet this._oRecordSet = new YAHOO.widget.RecordSet(); // Validate HTML Element var elTable = null; elContainer = YAHOO.util.Dom.get(elContainer); if(elContainer && elContainer.tagName && (elContainer.tagName.toLowerCase() == "div")) { this._elContainer = elContainer; // Peek in container child nodes to see if TABLE already exists if(elContainer.hasChildNodes()) { var children = elContainer.childNodes; for(i=0; i" + contentText + ""; //elHeadContent.innerHTML = contentText; } else { elHeadContent.innerHTML = contentText; } }; /** * If pagination is enabled, initializes paginator container elements and sets * internal tracking variables. * * @method _initPaginator * @private */ YAHOO.widget.DataTable.prototype._initPaginator = function() { var i,j; // Set up default values var paginator = { elements:[], // element references pageLinks:0, // show all links dropdownOptions:null, // no dropdown rowsPerPage:500, // 500 rows currentPage:1 // page one }; var elements = paginator.elements; // Deal with unused deprecated values if(this.startRecordIndex != 1) { } if(this.pageLinksStart != 1) { } // Validate deprecated rowsPerPage value if(this.rowsPerPage && YAHOO.util.Lang.isNumber(this.rowsPerPage)) { paginator.rowsPerPage = this.rowsPerPage; } // Validate rowsPerPage value if(this.paginatorOptions && YAHOO.util.Lang.isNumber(this.paginatorOptions.rowsPerPage)) { paginator.rowsPerPage = this.paginatorOptions.rowsPerPage; } // Validate deprecated currentPage value if(this.pageCurrent && YAHOO.util.Lang.isNumber(this.pageCurrent)) { paginator.currentPage = this.pageCurrent; } // Validate currentPage value if(this.paginatorOptions && YAHOO.util.Lang.isNumber(this.paginatorOptions.currentPage)) { paginator.currentPage = this.paginatorOptions.currentPage; } // Validate deprecated pagers values if(this.pagers && YAHOO.util.Lang.isArray(this.pagers)) { var dep_containers = this.pagers; for(i=0; i -1) { for(i=0; i 0) { YAHOO.util.Dom.removeClass(this.getFirstRow(),YAHOO.widget.DataTable.CLASS_FIRST); var elFirstRow = this._elBody.rows[0]; YAHOO.util.Dom.addClass(elFirstRow,YAHOO.widget.DataTable.CLASS_FIRST); this._elFirstRow = elFirstRow; } else { this._elFirstRow = null; } }; /** * Resets last row being tracked by class YAHOO.widget.DataTable.CLASS_LAST. * * @method _resetLastRow * @private */ YAHOO.widget.DataTable.prototype._resetLastRow = function() { if(this._elBody.rows.length > 0) { YAHOO.util.Dom.removeClass(this.getLastRow(),YAHOO.widget.DataTable.CLASS_LAST); var elLastRow = this._elBody.rows[this._elBody.rows.length-1]; YAHOO.util.Dom.addClass(elLastRow,YAHOO.widget.DataTable.CLASS_LAST); this._elLastRow = elLastRow; } else { this._elLastRow = null; } }; /** * Restripes rows by applying class YAHOO.widget.DataTable.CLASS_EVEN or * YAHOO.widget.DataTable.CLASS_ODD. * * @method _restripeRows * @param range {Number} (optional) Range defines a subset of rows to restripe. * @private */ YAHOO.widget.DataTable.prototype._restripeRows = function(range) { if(!range) { var rows = this._elBody.rows; for(var i=0; i 0)) { if(!e.shiftKey || oSelf.rowSingleSelect) { oSelf.unselectAllRows(); } newSelected = oSelf._elBody.rows[oldSelected.sectionRowIndex-1]; oSelf.selectRow(newSelected); } } // cell mode else if(oldSelected.tagName.toLowerCase() == "td") { // We have room to move up if((oldSelected.sectionRowIndex > 0)) { if(!e.shiftKey) { oSelf.unselectAllRows(); } newSelected = oSelf._elBody.rows[oldSelected.sectionRowIndex-1]; oSelf.select(newSelected); } } // Arrows can cause widget to lose focus //oSelf._bFocused = false; //oSelf.focusTable(); } } }; /** * Handles keyup events on the TABLE. Executes deletion * * @method _onKeyup * @param e {HTMLEvent} The key event. * @param oSelf {YAHOO.widget.DataTable} DataTable instance. * @private */ YAHOO.widget.DataTable.prototype._onKeyup = function(e, oSelf) { var key = YAHOO.util.Event.getCharCode(e); // delete if(key == 46) {//TODO: && this.isFocused //TODO: delete row } }; /** * Handles keydown events on the DOCUMENT. Executes interaction with editor. * * @method _onDocumentKeydown * @param e {HTMLEvent} The key event. * @param oSelf {YAHOO.widget.DataTable} DataTable instance. * @private */ YAHOO.widget.DataTable.prototype._onDocumentKeydown = function(e, oSelf) { // esc Clears active editor if((e.keyCode == 27)) { oSelf.cancelEditorData(); } // enter Saves active editor data if(e.keyCode == 13) { YAHOO.util.Event.stopEvent(e); oSelf.saveEditorData(); } }; /** * Handles click events on the DOCUMENT. Hides active editor. * * @method _onDocumentClick * @param e {HTMLEvent} The click event. * @param oSelf {YAHOO.widget.DataTable} DataTable instance. * @private */ YAHOO.widget.DataTable.prototype._onDocumentClick = function(e, oSelf) { oSelf.saveEditorData(); }; /** * Handles click events on paginator links. * * @method _onPagerClick * @param e {HTMLEvent} The click event. * @param oSelf {YAHOO.widget.DataTable} DataTable instance. * @private */ YAHOO.widget.DataTable.prototype._onPagerClick = function(e, oSelf) { oSelf.saveEditorData(); var elTarget = YAHOO.util.Event.getTarget(e); var elTag = elTarget.tagName.toLowerCase(); var knownTag = false; // True if event should stop propagating if (elTag != "table") { while(!knownTag) { switch(elTag) { case "body": knownTag = true; break; case "a": YAHOO.util.Event.stopEvent(e); switch(elTarget.className) { case YAHOO.widget.DataTable.CLASS_PAGELINK: oSelf.showPage(parseInt(elTarget.innerHTML,10)); break; case YAHOO.widget.DataTable.CLASS_FIRSTLINK: oSelf.showPage(1); break; case YAHOO.widget.DataTable.CLASS_LASTLINK: oSelf.showPage(oSelf._paginator.totalPages); break; case YAHOO.widget.DataTable.CLASS_PREVLINK: oSelf.showPage(oSelf._paginator.currentPage-1); break; case YAHOO.widget.DataTable.CLASS_NEXTLINK: oSelf.showPage(oSelf._paginator.currentPage+1); break; } knownTag = true; break; default: break; } elTarget = elTarget.parentNode; if(elTarget) { elTag = elTarget.tagName.toLowerCase(); } else { break; } } } }; /** * Handles change events on paginator SELECT. * * @method _onPagerSelect * @param e {HTMLEvent} The change event. * @param oSelf {YAHOO.widget.DataTable} DataTable instance. * @private */ YAHOO.widget.DataTable.prototype._onPagerSelect = function(e, oSelf) { var elTarget = YAHOO.util.Event.getTarget(e); var value = elTarget[elTarget.selectedIndex].value; // How many rows per page var oldRowsPerPage = oSelf._paginator.rowsPerPage; var rowsPerPage = parseInt(value,10) || null; if(rowsPerPage && (rowsPerPage != oldRowsPerPage)) { if(rowsPerPage > oldRowsPerPage) { oSelf._paginator.currentPage = 1; } oSelf._paginator.rowsPerPage = rowsPerPage; oSelf.populateTable(); } }; ///////////////////////////////////////////////////////////////////////////// // // Private Custom Event Handlers // ///////////////////////////////////////////////////////////////////////////// /** * Handles row delete events. * * @method _onRowDelete * @param oArgs.rowIndexes {Number[]} The indexes of the deleted rows. * @private */ YAHOO.widget.DataTable.prototype._onRowDelete = function(oArgs) { this._restripeRows(); }; /** * Passes along recordSetUpdate Event when recordUpdateEvent is caught from RecordSet. * * @event _onRecordUpdate * @param oArgs.record {YAHOO.widget.Record} The Record instance. * @param oArgs.key {String} The Record key. * @param oArgs.newData {Object} New data. * @param oArgs.oldData {Object} New data. * @private */ YAHOO.widget.DataTable.prototype._onRecordUpdate = function(oArgs) { this.fireEvent("recordSetUpdateEvent",oArgs); }; ///////////////////////////////////////////////////////////////////////////// // // Public member variables // ///////////////////////////////////////////////////////////////////////////// /** * DataSource instance. * * @property dataSource * @type YAHOO.util.DataSource */ YAHOO.widget.DataTable.prototype.dataSource = null; /** * Initial request to send to DataSource. * * @property initialRequest * @type String * @default "" */ YAHOO.widget.DataTable.prototype.initialRequest = ""; /** * Defines value of CAPTION attribute. * * @property caption * @type String */ YAHOO.widget.DataTable.prototype.caption = null; /** * Defines value of SUMMARY attribute. * * @property summary * @type String */ YAHOO.widget.DataTable.prototype.summary = null; /** * True if DataTable's width is a fixed size. * * @property fixedWidth * @type Boolean * @default false */ YAHOO.widget.DataTable.prototype.fixedWidth = false; /** * True if TBODY should scroll while THEAD remains fixed. * * @property scrollable * @type Boolean * @default false */ YAHOO.widget.DataTable.prototype.scrollable = false; /** * True if only one row may be selected at a time. * * @property rowSingleSelect * @type Boolean * @default false */ YAHOO.widget.DataTable.prototype.rowSingleSelect = false; /** * ContextMenu instance. * * @property contextMenu * @type YAHOO.widget.ContextMenu */ YAHOO.widget.DataTable.prototype.contextMenu = null; /** * True if built-in paginator is enabled. * * @property paginator * @type Boolean * @default false */ YAHOO.widget.DataTable.prototype.paginator = false; /** * Object literal of initial paginator key:value properties. * * @property paginatorOptions * @type Object * @default {} */ /** * If built-in paginator is enabled, each page will display up to the given * number of rows per page. A value less than 1 will display all available * rows. * * @property paginatorOptions.rowsPerPage * @type Number * @default 500 */ /** * If built-in paginator is enabled, current page to display. * * @property paginatorOptions.currentPage * @type Number * @default 1 */ /** * Array of container elements to hold paginator UI, if enabled. If null, * 2 containers will be created dynamically, one before and one after the * TABLE element. * * @property paginatorOptions.containers * @type HTMLElement[] * @default null */ /** * Values to show in the SELECT dropdown. Can be an array of numbers to populate * each OPTION's value and text with the same value, or an array of object * literals of syntax {value:myValue, text:myText} will populate OPTION with * corresponding value and text. A null value or empty array prevents the * dropdown from displayed altogether. * * @property paginatorOptions.dropdownOptions * @type Number[] | Object{} */ /** * Maximum number of links to page numbers to show in paginator UI. Any pages * not linked would be available through the next/previous style links. A 0 * value displays all page links. A negative value disables all page links. * * @property paginatorOptions.pageLinks * @type Number * @default 0 */ YAHOO.widget.DataTable.prototype.paginatorOptions = null; /** * Object literal holds sort metadata: * sortedBy.colKey * sortedBy.dir * * * @property sortedBy * @type Object */ YAHOO.widget.DataTable.prototype.sortedBy = null; ///////////////////////////////////////////////////////////////////////////// // // Deprecated public member variables // ///////////////////////////////////////////////////////////////////////////// /** * @deprecated No longer used. * @property isEmpty * */ YAHOO.widget.DataTable.prototype.isEmpty = false; /** * @deprecated No longer used. * @property isEmpty */ YAHOO.widget.DataTable.prototype.isLoading = false; /** * @deprecated No longer used. * @property startRecordIndex */ YAHOO.widget.DataTable.prototype.startRecordIndex = 1; /** * @deprecated No longer used. * @property pageLinksStart */ YAHOO.widget.DataTable.prototype.pageLinksStart = 1; /** * @deprecated Deprecated in favor of paginatorOptions.currentPage * @property pageCurrent */ YAHOO.widget.DataTable.prototype.pageCurrent = 1; /** * @deprecated Deprecated in favor of paginatorOptions.rowsPerPage * @property rowsPerPage */ YAHOO.widget.DataTable.prototype.rowsPerPage = 500; /** * @deprecated Deprecated in favor of paginatorOptions.pageLinks * @property pageLinksLength */ YAHOO.widget.DataTable.prototype.pageLinksLength = -1; /** * @deprecated Deprecated in favor of paginatorOptions.dropdownOptions * @property rowsPerPageDropdown */ YAHOO.widget.DataTable.prototype.rowsPerPageDropdown = null; /** * @deprecated Deprecated in favor of paginatorOptions.containers * @property pagers */ YAHOO.widget.DataTable.prototype.pagers = null; ///////////////////////////////////////////////////////////////////////////// // // Public methods // ///////////////////////////////////////////////////////////////////////////// /** * Public accessor to the unique name of the DataSource instance. * * @method toString * @return {String} Unique name of the DataSource instance. */ YAHOO.widget.DataTable.prototype.toString = function() { return "DataTable " + this._sName; }; /** * Returns element reference to TABLE. * * @method getTable * @return {HTMLElement} Reference to TABLE element. */ YAHOO.widget.DataTable.prototype.getTable = function() { return(this._elTable); }; /** * Returns element reference to THEAD. * * @method getHead * @return {HTMLElement} Reference to THEAD element. */ YAHOO.widget.DataTable.prototype.getHead = function() { return(this._elHead); }; /** * Returns element reference to TBODY. * * @method getBody * @return {HTMLElement} Reference to TBODY element. */ YAHOO.widget.DataTable.prototype.getBody = function() { return(this._elBody); }; /** * Returns element reference to TR element at given index. * * @method getRow * @param index {Number} Row number. * @return {HTMLElement} Reference to TR element. */ YAHOO.widget.DataTable.prototype.getRow = function(index) { if(YAHOO.lang.isNumber(index) && (index > -1)) { return(this._elBody.rows[index]); } return null; }; /** * Returns element reference to first TR element. * * @method getFirstRow * @return {HTMLElement} Reference to first TR element. */ YAHOO.widget.DataTable.prototype.getFirstRow = function() { return this._elFirstRow; }; /** * Returns element reference to last TR element. * * @method getLastRow * @return {HTMLElement} Reference to last TR element. */ YAHOO.widget.DataTable.prototype.getLastRow = function() { return this._elLastRow; }; /** * Returns element reference to TD element at given row and column positions. * * @method getCell * @param rowIndex {Number} Row index. * @param colIndex {Number} Column index. * @return {HTMLElement} Reference to TD element. */ YAHOO.widget.DataTable.prototype.getCell = function(rowIndex, colIndex) { if(YAHOO.lang.isNumber(rowIndex) && YAHOO.lang.isNumber(colIndex) && (rowIndex > -1) && (colIndex > -1)) { return(this._elBody.rows[rowIndex].cells[colIndex]); } return null; }; /** * Displays placeholder row with a message when there are no data rows. * * @method showTableMessage * @param sHTML {String} (optional) Value for innerHTML. * @param sClassName {String} (optional) Classname. */ YAHOO.widget.DataTable.prototype.showTableMessage = function(sHTML, sClassName) { var elCell = this._elMsgCell; if(YAHOO.lang.isString(sHTML)) { elCell.innerHTML = sHTML; } if(YAHOO.lang.isString(sClassName)) { elCell.className = sClassName; } this._elMsgBody.style.display = ""; }; /** * Hide placeholder message. * * @method hideTableMessage */ YAHOO.widget.DataTable.prototype.hideTableMessage = function() { this._elMsgBody.style.display = "none"; }; /** * @deprecated Deprecated in favor of showTableMessage(). * @method showEmptyMessage * */ YAHOO.widget.DataTable.prototype.showEmptyMessage = function() { if(this.isEmpty) { return; } if(this.isLoading) { this.hideTableMessages(); } this._elMsgBody.style.display = ""; var elCell = this._elMsgCell; elCell.className = YAHOO.widget.DataTable.CLASS_EMPTY; elCell.innerHTML = YAHOO.widget.DataTable.MSG_EMPTY; this.isEmpty = true; }; /** * @deprecated Deprecated in favor of showTableMessage(). * * @method showLoadingMessage */ YAHOO.widget.DataTable.prototype.showLoadingMessage = function() { if(this.isLoading) { return; } if(this.isEmpty) { this.hideTableMessages(); } this._elMsgBody.style.display = ""; var elCell = this._elMsgCell; elCell.className = YAHOO.widget.DataTable.CLASS_LOADING; elCell.innerHTML = YAHOO.widget.DataTable.MSG_LOADING; this.isLoading = true; }; /** * @deprecated Deprecated in favor of hideTableMessage(). * @method hideTableMessages * */ YAHOO.widget.DataTable.prototype.hideTableMessages = function() { if(!this.isEmpty && !this.isLoading) { return; } this._elMsgBody.style.display = "none"; this.isEmpty = false; this.isLoading = false; }; /** * Sets focus on the TABLE element. * * @method focusTable */ YAHOO.widget.DataTable.prototype.focusTable = function() { var elTable = this._elTable; if(!this._bFocused) { // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing // strange unexpected things as the user clicks on buttons and other controls. setTimeout(function() { elTable.focus(); },0); this._bFocused = true; this.fireEvent("tableFocusEvent"); } }; /** * Overridable method gives implementers a hook to access data before * it gets added to RecordSet and rendered to the TBODY. * * @method doBeforeLoadData * @param sRequest {String} Original request. * @param oResponse {Object} Response object. * @return {Boolean} Return true to continue loading data into RecordSet and * updating DataTable with new Records, false to cancel. */ YAHOO.widget.DataTable.prototype.doBeforeLoadData = function(sRequest, oResponse) { return true; }; /** * Add rows to bottom of table body. * * @method appendRows * @param aRecords {YAHOO.widget.Record[]} Array of Records. */ YAHOO.widget.DataTable.prototype.appendRows = function(aRecords) { if(YAHOO.lang.isArray(aRecords) && (aRecords.length > 0)) { this.hideTableMessage(); var rowIds = []; for(var i=0; i 0)) { this.hideTableMessage(); var rowIds = []; for(var i=0; i 0)) { this.hideTableMessage(); var elBody = this._elBody; var elRows = this._elBody.rows; // Remove extra rows from the bottom so as to preserve ID order while(elBody.hasChildNodes() && (elRows.length > aRecords.length)) { elBody.deleteRow(elRows.length-1); } // Unselect rows in the UI but keep tracking selected rows var selectedRecords = this.getSelectedRecordIds(); if(selectedRecords.length > 0) { this._unselectAllRows(); } var rowIds = []; // Format in-place existing rows for(i=0; i 0) { // ...using Array.indexOf if available... if(tracker.indexOf && (tracker.indexOf(recordId) > -1)) { tracker.splice(tracker.indexOf(recordId), 1); } // ...or do it the old-fashioned way else { for(var i=0; i 0) { // ...using Array.indexOf if available... if(tracker.indexOf && (tracker.indexOf(recordId) > -1)) { tracker.splice(tracker.indexOf(recordId), 1); } // ...or do it the old-fashioned way else { for(var i=0; i -1)) { tracker.splice(tracker.indexOf(id),1); } // ...or do it the old-fashioned way else { for(var j=0; j -1)) { tracker.splice(tracker.indexOf(id),1); } // ...or do it the old-fashioned way else { for(var j=0; j *
  • currentPage: current page number
  • *
  • dropdownOptions: array of numbers to show in dropdown
  • *
  • elements: array of object literals that define where to show * paginator UI with following properties: *
      *
    • container: element reference to paginator container
    • *
    • links: element reference to page links container
    • *
    • select: element reference to dropdown
    • *
    *
  • *
  • pageLinks: number of page links displayed
  • *
  • rowsPerPage: number of rows displayed
  • *
  • totalPages: total number of pages
  • * */ YAHOO.widget.DataTable.prototype.getPaginator = function() { return this._paginator; }; /** * Displays a specific page of a paginated DataTable. * * @method showPage * @param nPage {Number} Which page. */ YAHOO.widget.DataTable.prototype.showPage = function(nPage) { // Validate input if(!YAHOO.lang.isNumber(nPage) || (nPage < 1) || (nPage > this._paginator.totalPages)) { nPage = 1; } this._paginator.currentPage = nPage; this.populateTable(); }; /** * Updates paginator links container with markup. * * @method formatPaginatorLinks */ YAHOO.widget.DataTable.prototype.formatPaginatorLinks = function(elLinksContainer, nCurrentPage, nPageLinksStart, nPageLinksLength, nTotalPages) { // Markup for page links var isFirstPage = (nCurrentPage == 1) ? true : false; var isLastPage = (nCurrentPage == nTotalPages) ? true : false; var firstPageLink = (isFirstPage) ? " << " : " << "; var prevPageLink = (isFirstPage) ? " < " : " < " ; var nextPageLink = (isLastPage) ? " > " : " > " ; var lastPageLink = (isLastPage) ? " >> " : " >> "; markup = firstPageLink + prevPageLink; var maxLinks = (nPageLinksStart+nPageLinksLength < nTotalPages) ? nPageLinksStart+nPageLinksLength-1 : nTotalPages; for(var i=nPageLinksStart; i<=maxLinks; i++) { if(i != nCurrentPage) { markup += " " + i + " "; } else { markup += " " + i + ""; } } markup += nextPageLink + lastPageLink; elLinksContainer.innerHTML = markup; }; /** * @deprecated Deprecated in favor of populateTable(). * @method populateTable */ YAHOO.widget.DataTable.prototype.paginateRows = function() { this.populateTable(); }; /** * Populates TBODY rows with data from RecordSet. If pagination is enabled, * displays only rows for current page and updates paginator UI, otherwise * displays all rows. * * @method populateTable */ YAHOO.widget.DataTable.prototype.populateTable = function() { // Records with which to populate the table var records; // Paginator is disabled if(!this.paginator) { // Paginator must be destroyed if(this._paginator !== null) { //TODO: this.destroyPaginator(); } } // Paginator is enabled if(this.paginator) { // Paginator must be initialized if(this._paginator === null) { this._initPaginator(); } // How many total Records var recordsLength = this._oRecordSet.getLength(); // If rowsPerPage < 1, show all rows var rowsPerPage = (this._paginator.rowsPerPage > 0) ? this._paginator.rowsPerPage : recordsLength; // How many rows this page var maxRows = (rowsPerPage < recordsLength) ? rowsPerPage : recordsLength; // How many total pages this._paginator.totalPages = Math.ceil(recordsLength / maxRows); // What is current page var currentPage = this._paginator.currentPage; // First row of this page var startRecordIndex = (currentPage-1) * rowsPerPage; // How many page links to display var pageLinksLength = this._paginator.pageLinks; // Show all links if(pageLinksLength === 0) { pageLinksLength = this._paginator.totalPages; } // Page links are enabled if(pageLinksLength > -1) { // First page link for this page var pageLinksStart = (pageLinksLength == 1) ? currentPage : (Math.ceil(currentPage/pageLinksLength-1) * pageLinksLength) + 1; } // Show rows for this page records = this._oRecordSet.getRecords(startRecordIndex, rowsPerPage); // Update UI of each paginator for(var i=0; i -1)) { this.formatPaginatorLinks(this._paginator.elements[i].links, currentPage, pageLinksStart, pageLinksLength, this._paginator.totalPages); } // Update SELECT dropdown if(this._paginator.elements[i].select && this._paginator.elements[i].select.options) { var options = this._paginator.elements[i].select.options; for(var j=0; j nodeLevelMaxChildren) { nodeLevelMaxChildren = tmpMax; } } }; recurseChildren(nodeList); // Parse each node for attributes and any children for(var j=0; j 0) { // Children of siblings increase the rowspan of the Column oColumn._rowspan += nodeLevelMaxChildren; //if(oColumn.key) { oColumn._index = keys.length; keys.push(oColumn); //} } // This entire node level does not have any children else { //if(oColumn.key) { oColumn._index = keys.length; keys.push(oColumn); //} } // Add the Column to the top-down tree tree[nodelevel].push(oColumn); } nodelevel--; }; // Do the parsing if(aHeaders.length > 0) { parseColumns(aHeaders); } // Store header nesting in an array var recurseAncestors = function(i, oColumn) { headers[i].push(oColumn._id); if(oColumn._parent) { recurseAncestors(i, oColumn._parent); } }; for(var i=0; i"+oData.toString()+"" : ""; classname = YAHOO.widget.DataTable.CLASS_STRING; break; } YAHOO.util.Dom.addClass(elCell, classname); if(this.className) { YAHOO.util.Dom.addClass(elCell, this.className); } } if(this.editor) { YAHOO.util.Dom.addClass(elCell,YAHOO.widget.DataTable.CLASS_EDITABLE); } }; /** * Formats cells in Columns of type "checkbox". * * @method formatCheckbox * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatCheckbox = function(elCell, oRecord, oColumn, oData) { var bChecked = oData; bChecked = (bChecked) ? " checked" : ""; elCell.innerHTML = ""; }; /** * Formats cells in Columns of type "currency". Can be overridden for custom formatting. * * @method formatCurrency * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatCurrency = function(elCell, oRecord, oColumn, oData) { // Make it dollars var nAmount = oData; var markup; if((nAmount !== undefined) && (nAmount !== null) && !isNaN(parseFloat(nAmount))) { // Round to the penny nAmount = Math.round(nAmount*100)/100; markup = "$"+nAmount; // Normalize digits var dotIndex = markup.indexOf("."); if(dotIndex < 0) { markup += ".00"; } else { while(dotIndex > markup.length-3) { markup += "0"; } } } else { markup = ""; } elCell.innerHTML = markup; }; /** * Formats cells in Columns of type "date". * * @method formatDate * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatDate = function(elCell, oRecord, oColumn, oData) { var oDate = oData; if(oDate instanceof Date) { elCell.innerHTML = (oDate.getMonth()+1) + "/" + oDate.getDate() + "/" + oDate.getFullYear(); } else { elCell.innerHTML = ""; } }; /** * Formats cells in Columns of type "email". * * @method formatEmail * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatEmail = function(elCell, oRecord, oColumn, oData) { var sEmail = oData; if(sEmail) { elCell.innerHTML = "" + sEmail + ""; } else { elCell.innerHTML = ""; } }; /** * Formats cells in Columns of type "link". * * @method formatLink * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatLink = function(elCell, oRecord, oColumn, oData) { var sLink = oData; if(sLink) { elCell.innerHTML = "" + sLink + ""; } else { elCell.innerHTML = ""; } }; /** * Formats cells in Columns of type "number". * * @method formatNumber * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatNumber = function(elCell, oRecord, oColumn, oData) { var nNumber = oData; if((nNumber !== undefined) && (nNumber !== null)) { elCell.innerHTML = nNumber.toString(); } else { elCell.innerHTML = ""; } }; /** * Formats cells in Columns of type "select". * * @method formatSelect * @param elCell {HTMLElement} Table cell element. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} Data value for the cell, or null * @static */ YAHOO.widget.Column.formatSelect = function(elCell, oRecord, oColumn, oData) { var selectedValue = oData; var options = oColumn.selectOptions; var markup = ""; elCell.innerHTML = markup; }; /** * Takes innerHTML from TD and parses out data for storage in RecordSet. * * @method parse * @param sMarkup {String} The TD's innerHTML value. * @return {Object} Data. */ YAHOO.widget.Column.prototype.parse = function(sMarkup) { if(this.parser) { return this.parser(sMarkup); } else { var data = null; switch(this.type) { case "checkbox": data = YAHOO.widget.Column.parseCheckbox(sMarkup); break; case "currency": data = YAHOO.widget.Column.parseCurrency(sMarkup); break; case "date": data = YAHOO.widget.Column.parseDate(sMarkup); break; case "number": data = YAHOO.widget.Column.parseNumber(sMarkup); break; case "select": data = YAHOO.widget.Column.parseSelect(sMarkup); break; default: if(sMarkup) { data = sMarkup; } break; } return data; } }; /** * Default parse function for Columns of type "checkbox" takes markup and * extracts data. Can be overridden for custom parsing. * * @method parseCheckbox * @param sMarkup * @return {bChecked} True if checkbox is checked. */ YAHOO.widget.Column.parseCheckbox = function(sMarkup) { return (sMarkup.indexOf("checked") < 0) ? false : true; }; /** * Default parse function for Columns of type "currency" takes markup and * extracts data. Can be overridden for custom parsing. * * @method parseCurrency * @param sMarkup * @return {nAmount} Floating point amount. */ YAHOO.widget.Column.parseCurrency = function(sMarkup) { return parseFloat(sMarkup.substring(1)); }; /** * Default parse function for Columns of type "date" takes markup and extracts * data. Can be overridden for custom parsing. * * @method parseDate * @param sMarkup * @return {oDate} Date instance. */ YAHOO.widget.Column.parseDate = function(sMarkup) { var mm = sMarkup.substring(0,sMarkup.indexOf("/")); sMarkup = sMarkup.substring(sMarkup.indexOf("/")+1); var dd = sMarkup.substring(0,sMarkup.indexOf("/")); var yy = sMarkup.substring(sMarkup.indexOf("/")+1); return new Date(yy, mm, dd); }; /** * Default parse function for Columns of type "number" takes markup and extracts * data. Can be overridden for custom parsing. * * @method parseNumber * @param sMarkup * @return {nNumber} Number. */ YAHOO.widget.Column.parseNumber = function(sMarkup) { return parseFloat(sMarkup); }; /** * Default parse function for Columns of type "select" takes markup and extracts * data. Can be overridden for custom parsing. * * @method parseSelect * @param sMarkup * @return {sValue} Value of selected option. */ YAHOO.widget.Column.parseSelect = function(sMarkup) { //return (sMarkup.indexOf("checked") < 0) ? false : true; }; /** * Outputs editor markup into the given TD based on given Record. * * @method showEditor * @param elCell {HTMLElement} The cell to edit. * @param oRecord {YAHOO.widget.Record} The DataTable Record of the cell. * @return YAHOO.widget.ColumnEditor */ YAHOO.widget.Column.prototype.getEditor = function(elCell, oRecord) { //Sync up the arg signature for ColumnEditor constructor and show() var oEditor = this.editor; if(YAHOO.lang.isString(oEditor)) { oEditor = new YAHOO.widget.ColumnEditor(this.editor); oEditor.show(elCell, oRecord, this); this.editor = oEditor; } else if(oEditor instanceof YAHOO.widget.ColumnEditor) { oEditor.show(elCell, oRecord, this); } return oEditor; }; /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /** * The ColumnEditor defines and manages inline editing functionality for a * DataTable Column. * * @class ColumnEditor * @constructor * @param elCell {HTMLElement} The cell to edit. * @param oRecord {YAHOO.widget.Record} The DataTable Record of the cell. * @param oColumn {YAHOO.widget.Column} The DataTable Column of the cell. * @parem sType {String} Type identifier */ YAHOO.widget.ColumnEditor = function(sType) { this.type = sType; //TODO: make sure ColumnEditors get destroyed if widget gets destroyed // Works better to attach ColumnEditor to document.body // rather than the DataTable container // elTable comes in as a cell. Traverse up DOM to find the table. // TODO: safety net in case table is never found. //while(elCell.nodeName.toLowerCase() != "table") { // elCell = elCell.parentNode; //} //this.tableContainer = elCell.parentNode; var container = document.body.appendChild(document.createElement("div"));//this.tableContainer.appendChild(document.createElement("div")); container.style.position = "absolute"; container.style.zIndex = 9000; container.id = "yui-dt-editor" + YAHOO.widget.ColumnEditor._nCount; container.className = YAHOO.widget.DataTable.CLASS_EDITOR; this.container = container; switch(this.type) { case "textbox": this.createTextboxEditor(); break; case "textarea": this.createTextareaEditor(); break; default: break; } YAHOO.widget.ColumnEditor._nCount++; }; ///////////////////////////////////////////////////////////////////////////// // // Private member variables // ///////////////////////////////////////////////////////////////////////////// /** * Internal instance counter. * * @property _nCount * @type Number * @static * @default 0 */ YAHOO.widget.ColumnEditor._nCount =0; ///////////////////////////////////////////////////////////////////////////// // // Public member variables // ///////////////////////////////////////////////////////////////////////////// /** * Reference to the container DOM element for the ColumnEditor. * * @property container * @type HTMLElement */ YAHOO.widget.ColumnEditor.prototype.container = null; /** * Reference to the ColumnEditor's Column instance. * * @property column * @type YAHOO.widget.Column */ YAHOO.widget.ColumnEditor.prototype.column = null; /** * Type of editor: "textbox", etc. * * @property type * @type String */ YAHOO.widget.ColumnEditor.prototype.type = null; /** * Reference to form element(s) of the ColumnEditor. * * @property input * @type HTMLElement || HTMLElement[] */ YAHOO.widget.ColumnEditor.prototype.input = null; ///////////////////////////////////////////////////////////////////////////// // // Public methods // ///////////////////////////////////////////////////////////////////////////// /** * Shows ColumnEditor. * * @method show * @param elCell {HTMLElement} The cell to edit. * @param oRecord {YAHOO.widget.Record} The DataTable Record of the cell. * @param oColumn {YAHOO.widget.Column} The DataTable Column of the cell. */ YAHOO.widget.ColumnEditor.prototype.show = function(elCell, oRecord, oColumn) { this.cell = elCell; this.record = oRecord; this.column = oColumn; switch(this.type) { case "textbox": this.showTextboxEditor(elCell, oRecord, oColumn); break; case "textarea": this.showTextareaEditor(elCell, oRecord, oColumn); break; default: break; } }; /** * Positions container over given element, aligning upper-left corners. * * @method moveContainerTo * @param elCell {HTMLElement} The element. */ YAHOO.widget.ColumnEditor.prototype.moveContainerTo = function(el) { var x,y; // Don't use getXY for Opera if(navigator.userAgent.toLowerCase().indexOf("opera") != -1) { x = el.offsetLeft; y = el.offsetTop; while(el.offsetParent) { x += el.offsetParent.offsetLeft; y += el.offsetParent.offsetTop; el = el.offsetParent; } } else { x = parseInt(YAHOO.util.Dom.getX(el),10);//xy[0] + 1; y = parseInt(YAHOO.util.Dom.getY(el),10);//xy[1] + 1; } this.container.style.left = x + "px"; this.container.style.top = y + "px"; }; /** * Returns ColumnEditor data value. * * @method getValue * @return Object */ YAHOO.widget.ColumnEditor.prototype.getValue = function() { var value; switch(this.type) { case "textbox": value = this.getTextboxEditorValue(); break; case "textarea": value = this.getTextareaEditorValue(); break; default: break; } return value; }; /** * Creates a textbox editor in the DOM. * * @method createTextboxEditor * @return {HTML} ??? */ YAHOO.widget.ColumnEditor.prototype.createTextboxEditor = function() { var elTextbox = this.container.appendChild(document.createElement("input")); // For FF bug 236791 elTextbox.setAttribute("autocomplete","off"); this.input = elTextbox; }; /** * Creates a textarea editor in the DOM. * * @method createTextareaEditor * @return {HTML} ??? */ YAHOO.widget.ColumnEditor.prototype.createTextareaEditor = function() { var elTextarea = this.container.appendChild(document.createElement("textarea")); this.input = elTextarea; }; /** * Shows textbox. * * @method showTextboxEditor * @param elCell {HTMLElement} The cell to edit. * @param oRecord {YAHOO.widget.Record} The DataTable Record of the cell. * @param oColumn {YAHOO.widget.Column} The DataTable Column of the cell. */ YAHOO.widget.ColumnEditor.prototype.showTextboxEditor = function(elCell, oRecord, oColumn) { // Position container this.moveContainerTo(elCell); // Update form field this.input.style.width = (parseInt(elCell.offsetWidth,10)) + "px"; this.input.style.height = (parseInt(elCell.offsetHeight,10)) + "px"; this.input.value = elCell.innerHTML || ""; this.input.tabIndex = 0; // Display container this.container.style.display = "block"; // Highlight input this.input.focus(); this.input.select(); }; /** * Shows textarea. * * @method showTextareaEditor * @param elCell {HTMLElement} The cell to edit. * @param oRecord {YAHOO.widget.Record} The DataTable Record of the cell. * @param oColumn {YAHOO.widget.Column} The DataTable Column of the cell. */ YAHOO.widget.ColumnEditor.prototype.showTextareaEditor = function(elCell, oRecord, oColumn) { // Position container this.moveContainerTo(elCell); // Update form field this.input.style.width = (parseInt(elCell.offsetWidth,10)) + "px"; this.input.style.height = 4*(parseInt(elCell.offsetHeight,10)) + "px"; this.input.value = elCell.innerHTML || ""; this.input.tabIndex = 0; // Display container this.container.style.display = "block"; // Highlight input this.input.focus(); this.input.select(); }; /** * Hides ColumnEditor * * @method hide */ YAHOO.widget.ColumnEditor.prototype.hide = function() { this.input.tabIndex = -1; this.container.style.display = "none"; }; /** * Returns ColumnEditor value * * @method getTextboxEditorValue * @return String */ YAHOO.widget.ColumnEditor.prototype.getTextboxEditorValue = function() { return this.input.value; }; /** * Returns ColumnEditor value * * @method getTextareaEditorValue * @return String */ YAHOO.widget.ColumnEditor.prototype.getTextareaEditorValue = function() { return this.input.value; }; /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /** * Sort static utility to support Column sorting. * * @class Sort * @static */ YAHOO.util.Sort = { ///////////////////////////////////////////////////////////////////////////// // // Public methods // ///////////////////////////////////////////////////////////////////////////// /** * Comparator function for sort in ascending order. String sorting is case insensitive. * * @method compareAsc * @param a {object} First sort argument. * @param b {object} Second sort argument. */ compareAsc: function(a, b) { if((a === null) || (typeof a == "undefined")) { if((b === null) || (typeof b == "undefined")) { return 0; } else { return 1; } } else if((b === null) || (typeof b == "undefined")) { return -1; } if(a.constructor == String) { a = a.toLowerCase(); } if(b.constructor == String) { b = b.toLowerCase(); } if(a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } }, /** * Comparator function for sort in descending order. String sorting is case insensitive. * * @method compareDesc * @param a {object} First sort argument. * @param b {object} Second sort argument. */ compareDesc: function(a, b) { if((a === null) || (typeof a == "undefined")) { if((b === null) || (typeof b == "undefined")) { return 0; } else { return -1; } } else if((b === null) || (typeof b == "undefined")) { return 1; } if(a.constructor == String) { a = a.toLowerCase(); } if(b.constructor == String) { b = b.toLowerCase(); } if(a < b) { return 1; } else if (a > b) { return -1; } else { return 0; } } }; /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /** * WidthResizer subclasses DragDrop to support resizeable Columns. * * @class WidthResizer * @extends YAHOO.util.DragDrop * @constructor * @param colElId {string} ID of the Column's TH element being resized * @param handleElId {string} ID of the handle element that causes the resize * @param sGroup {string} Group name of related DragDrop items */ YAHOO.util.WidthResizer = function(oDataTable, colId, handleId, sGroup, config) { if (colId) { this.cell = YAHOO.util.Dom.get(colId); this.init(handleId, sGroup, config); //this.initFrame(); this.datatable = oDataTable; this.setYConstraint(0,0); } else { } }; if(YAHOO.util.DD) { YAHOO.extend(YAHOO.util.WidthResizer, YAHOO.util.DD); } ///////////////////////////////////////////////////////////////////////////// // // Public DOM event handlers // ///////////////////////////////////////////////////////////////////////////// /** * Handles mousedown events on the Column resizer. * * @method onMouseDown * @param e {string} The mousedown event */ YAHOO.util.WidthResizer.prototype.onMouseDown = function(e) { this.startWidth = this.cell.offsetWidth; this.startPos = YAHOO.util.Dom.getX(this.getDragEl()); if(this.datatable.fixedWidth) { var cellText = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_HEADTEXT,"span",this.cell)[0]; this.minWidth = cellText.offsetWidth + 6; var sib = this.cell.nextSibling; var sibCellText = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_HEADTEXT,"span",sib)[0]; this.sibMinWidth = sibCellText.offsetWidth + 6; //!! var left = ((this.startWidth - this.minWidth) < 0) ? 0 : (this.startWidth - this.minWidth); var right = ((sib.offsetWidth - this.sibMinWidth) < 0) ? 0 : (sib.offsetWidth - this.sibMinWidth); this.setXConstraint(left, right); } }; /** * Handles mouseup events on the Column resizer. * * @method onMouseUp * @param e {string} The mouseup event */ YAHOO.util.WidthResizer.prototype.onMouseUp = function(e) { //TODO: replace the resizer where it belongs: var resizeStyle = YAHOO.util.Dom.get(this.handleElId).style; resizeStyle.left = "auto"; resizeStyle.right = 0; resizeStyle.marginRight = "-6px"; resizeStyle.width = "6px"; //.yui-dt-headresizer {position:absolute;margin-right:-6px;right:0;bottom:0;width:6px;height:100%;cursor:w-resize;cursor:col-resize;} //var cells = this.datatable._elTable.tHead.rows[this.datatable._elTable.tHead.rows.length-1].cells; //for(var i=0; i0; i--) { record = this._records[i]; if(record && (record.extid == extId)) { return record; } } return null; };*/ /** * Updates given Record at given key with given data. * * @method updateRecord * @param oRecord {YAHOO.widget.Record} A Record instance. * @param sKey {String} Key. * @param oData {Object) New data. */ YAHOO.widget.RecordSet.prototype.updateRecord = function(oRecord, sKey, oData) { var oldData = oRecord[sKey]; oRecord[sKey] = oData; this.fireEvent("recordUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}); }; /** * Adds one Record to the RecordSet at the given index. If index is null, * then adds the Record to the end of the RecordSet. * * @method addRecord * @param oObjectLiteral {Object} An object literal of data. * @param index {Number} (optional) Position index. * @return {YAHOO.widget.Record} A Record instance. */ YAHOO.widget.RecordSet.prototype.addRecord = function(oObjectLiteral, index) { if(oObjectLiteral && (oObjectLiteral.constructor == Object)) { var oRecord = new YAHOO.widget.Record(oObjectLiteral); if(YAHOO.lang.isNumber(index) && (index > -1)) { this._records.splice(index,0,oRecord); } else { this._records.push(oRecord); } this._length++; return oRecord; } else { return null; } }; /** * Adds multiple Records to the RecordSet at the given index. If index is null, * then adds the Records to the end of the RecordSet. * * @method addRecords * @param data {Object[]} An array of object literal data. * @param index {Number} (optional) Position index. * @return {YAHOO.widget.Record} An array of Record instances. */ YAHOO.widget.RecordSet.prototype.addRecords = function(data, index) { if(YAHOO.lang.isArray(data)) { var newRecords = []; // Can't go backwards bc we need to preserve order for(var i=0; i-1; i--) { var record = this.addRecord(data[i], 0); newRecords.push(record); } return newRecords; } else if(data && (data.constructor == Object)) { return this.addRecord(data, 0); } else { return null; } }; /** * Replaces all Records in RecordSet with new data. * * @method replace * @param data {Object || Object[]} An object literal or array or data. * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record or array of Records. */ YAHOO.widget.RecordSet.prototype.replace = function(data) { if(data) { this.reset(); return this.append(data); } else { return null; } }; /** * Sorts RecordSet by given function. * * @method sort * @param fnSort {Function} Reference to a sort function. * @return {Array} Sorted array of Records */ YAHOO.widget.RecordSet.prototype.sort = function(fnSort) { return this._records.sort(fnSort); }; /** * Removes the record at the given index from the RecordSet. If a range is * given, starts at the given index and removes all records in the range. * * @method deleteRecord * @param i {Number} Record index * @param range {Number} (optional) Range of records to remove, or null. */ YAHOO.widget.RecordSet.prototype.deleteRecord = function(i, range) { if(!YAHOO.lang.isNumber(range)) { range = 1; } // TODO: validate for negative values if(!YAHOO.lang.isNumber(i)) { this._records.splice(i, range); this._length = this._length - range; } }; /** * Removes all Records from the RecordSet. * * @method reset */ YAHOO.widget.RecordSet.prototype.reset = function() { this._records = []; this._length = 0; }; /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /** * The Record class defines a DataTable record. * * @class Record * @constructor * @param oConfigs {Object} (optional) Object literal of key/value pairs. */ YAHOO.widget.Record = function(oLiteral) { if(oLiteral && (oLiteral.constructor == Object)) { for(var sKey in oLiteral) { this[sKey] = oLiteral[sKey]; } this.yuiRecordId = "yui-dtrec"+YAHOO.widget.Record._nCount; YAHOO.widget.Record._nCount++; } }; ///////////////////////////////////////////////////////////////////////////// // // Private member variables // ///////////////////////////////////////////////////////////////////////////// /** * Internal class variable to index multiple data table instances. * * @property _nCount * @type number * @private * @static */ YAHOO.widget.Record._nCount = 0; ///////////////////////////////////////////////////////////////////////////// // // Public member variables // ///////////////////////////////////////////////////////////////////////////// /** * Unique name assigned at instantation, indicates original order. * * @property yuiRecordId * @type string */ YAHOO.widget.Record.prototype.yuiRecordId = null; YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.2.2", build: "204"});