// SpryHTMLDataSet.js - version 0.18 - Spry Pre-Release 1.6 // // Copyright (c) 2006. Adobe Systems Incorporated. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Adobe Systems Incorporated nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. ////////////////////////////////////////////////////////////////////// // // Spry.Data.HTMLDataSet // ////////////////////////////////////////////////////////////////////// Spry.Data.HTMLDataSet = function(dataSetURL, sourceElementID, dataSetOptions) { this.sourceElementID = sourceElementID; // ID of the html element to be used as a data source this.sourceElement = null; // The actual html element to be used as a data source this.sourceWasInitialized = false; this.usesExternalFile = (dataSetURL != null) ? true : false; this.firstRowAsHeaders = true; this.useColumnsAsRows = false; this.columnNames = null; this.hideDataSourceElement = true; this.rowSelector = null; this.dataSelector = null; Spry.Data.HTTPSourceDataSet.call(this, dataSetURL, dataSetOptions); }; Spry.Data.HTMLDataSet.prototype = new Spry.Data.HTTPSourceDataSet(); Spry.Data.HTMLDataSet.prototype.constructor = Spry.Data.HTMLDataSet; Spry.Data.HTMLDataSet.prototype.getDataRefStrings = function() { var dep = []; if (this.url) dep.push(this.url); if (typeof this.sourceElementID == "string") dep.push(this.sourceElementID); return dep; }; Spry.Data.HTMLDataSet.prototype.setDisplay = function(ele, display) { if( ele ) ele.style.display = display; }; Spry.Data.HTMLDataSet.prototype.initDataSource = function(callLoadData) { if (!this.loadDependentDataSets()) return; if (!this.usesExternalFile) { this.setSourceElement(); if (this.hideDataSourceElement) this.setDisplay(this.sourceElement, "none"); } //this.sourceWasInitialized = true; }; Spry.Data.HTMLDataSet.prototype.setSourceElement = function (externalDataElement) { // externalDataElement is the container that holds the data imported from the external file. this.sourceElement = null; if (!this.sourceElementID) { if (externalDataElement) this.sourceElement = externalDataElement; else { this.hideDataSourceElement = false; this.sourceElement = document.body; } return; } var sourceElementID = Spry.Data.Region.processDataRefString(null, this.sourceElementID, this.dataSetsForDataRefStrings); if (!this.usesExternalFile) this.sourceElement = Spry.$(sourceElementID); else if (externalDataElement) { var foundElement = false; // looking for the specified ID in the current element node var sources = Spry.Utils.getNodesByFunc(externalDataElement, function(node) { if (foundElement) return false; if (node.nodeType != 1) return false; if (node.id && node.id.toLowerCase() == sourceElementID.toLowerCase()) { foundElement = true; return true; } }); this.sourceElement = sources[0]; } if (!this.sourceElement) Spry.Debug.reportError("Spry.Data.HTMLDataSet: '" + sourceElementID + "' is not a valid element ID"); }; Spry.Data.HTMLDataSet.prototype.getSourceElement = function() { return this.sourceElement; }; Spry.Data.HTMLDataSet.prototype.getSourceElementID = function() { return this.sourceElementID; }; Spry.Data.HTMLDataSet.prototype.setSourceElementID = function(sourceElementID) { if (this.sourceElementID != sourceElementID) { this.sourceElementID = sourceElementID; this.recalculateDataSetDependencies(); this.dataWasLoaded = false; } }; Spry.Data.HTMLDataSet.prototype.getDataSelector = function() { return this.dataSelector; }; Spry.Data.HTMLDataSet.prototype.setDataSelector = function(dataSelector) { if (this.dataSelector != dataSelector) { this.dataSelector = dataSelector; this.dataWasLoaded = false; } }; Spry.Data.HTMLDataSet.prototype.getRowSelector = function() { return this.rowSelector; }; Spry.Data.HTMLDataSet.prototype.setRowSelector = function(rowSelector) { if (this.rowSelector != rowSelector) { this.rowSelector = rowSelector; this.dataWasLoaded = false; } }; Spry.Data.HTMLDataSet.prototype.loadDataIntoDataSet = function(rawDataDoc) { var responseText = rawDataDoc; responseText = Spry.Data.HTMLDataSet.cleanupSource(responseText); var div = document.createElement("div"); div.id = "htmlsource" + this.internalID; div.innerHTML = responseText; this.setSourceElement(div); if (this.sourceElement) { var parsedStructure = this.getDataFromSourceElement(); if (parsedStructure) { this.dataHash = parsedStructure.dataHash; this.data = parsedStructure.data; } } this.dataWasLoaded = true; div = null; }; Spry.Data.HTMLDataSet.prototype.loadDependentDataSets = function() { if (this.hasDataRefStrings) { var allDataSetsReady = true; for (var i = 0; i < this.dataSetsForDataRefStrings.length; i++) { var ds = this.dataSetsForDataRefStrings[i]; if (ds.getLoadDataRequestIsPending()) allDataSetsReady = false; else if (!ds.getDataWasLoaded()) { // Kick off the load of this data set! ds.loadData(); allDataSetsReady = false; } } // If our data sets aren't ready, just return. We'll // get called back to load our data when they are all // done. if (!allDataSetsReady) return false; } return true; }; Spry.Data.HTMLDataSet.prototype.loadData = function() { this.cancelLoadData(); this.initDataSource(); var self = this; if (!this.usesExternalFile) { this.notifyObservers("onPreLoad"); this.dataHash = new Object; this.data = new Array; this.dataWasLoaded = false; this.unfilteredData = null; this.curRowID = 0; this.pendingRequest = new Object; this.pendingRequest.timer = setTimeout(function() { self.pendingRequest = null; var parsedStructure = self.getDataFromSourceElement(); if (parsedStructure) { self.dataHash = parsedStructure.dataHash; self.data = parsedStructure.data; } self.dataWasLoaded = true; self.disableNotifications(); self.filterAndSortData(); self.enableNotifications(); self.notifyObservers("onPostLoad"); self.notifyObservers("onDataChanged"); }, 0); } else { var url = Spry.Data.Region.processDataRefString(null, this.url, this.dataSetsForDataRefStrings); var postData = this.requestInfo.postData; if (postData && (typeof postData) == "string") postData = Spry.Data.Region.processDataRefString(null, postData, this.dataSetsForDataRefStrings); this.notifyObservers("onPreLoad"); this.dataHash = new Object; this.data = new Array; this.dataWasLoaded = false; this.unfilteredData = null; this.curRowID = 0; var req = this.requestInfo.clone(); req.url = url; req.postData = postData; this.pendingRequest = new Object; this.pendingRequest.data = Spry.Data.HTTPSourceDataSet.LoadManager.loadData(req, this, this.useCache); } }; Spry.Data.HTMLDataSet.cleanupSource = function (source) { // Cleans the content by replacing the src/href with spry_src // This prevents browser to load the external resources. source = source.replace(/<(img|script|link|frame|iframe|input)([^>]+)>/gi, function(a,b,c) { //b=tag name,c=tag attributes return '<' + b + c.replace(/\b(src|href)\s*=/gi, function(a, b) { //b=attribute name return 'spry_'+ b + '='; }) + '>'; }); return source; }; Spry.Data.HTMLDataSet.undoCleanupSource = function (source) { // Undo cleanup. See the cleanupSource function source = source.replace(/<(img|script|link|frame|iframe|input)([^>]+)>/gi, function(a,b,c) { //b=tag name,c=tag attributes return '<' + b + c.replace(/\bspry_(src|href)\s*=/gi, function(a, b) { //b=attribute name return b + '='; }) + '>'; }); return source; }; Spry.Data.HTMLDataSet.normalizeColumnName = function(colName) { // Removes the tags from column names values // Replaces spaces with underscore colName = colName.replace(/(?:^[\s\t]+|[\s\t]+$)/gi, ""); colName = colName.replace(/<\/?([a-z]+)([^>]+)>/gi, ""); colName = colName.replace(/[\s\t]+/gi, "_"); return colName; }; Spry.Data.HTMLDataSet.prototype.getDataFromSourceElement = function() { if (!this.sourceElement) return null; var extractedData; var usesTable = false; switch (this.sourceElement.nodeName.toLowerCase()) { case "table": usesTable = true; extractedData = this.getDataFromHTMLTable(); break; default: extractedData = this.getDataFromNestedStructure(); } if (!extractedData) return null; // Flip Columns / Rows if (this.useColumnsAsRows) { var flipedData = new Array; // Get columns and put them as rows for (var rowIdx = 0; rowIdx < extractedData.length; rowIdx++) { var row = extractedData[rowIdx]; for (var cellIdx = 0; cellIdx < row.length; cellIdx++) { if (!flipedData[cellIdx]) flipedData[cellIdx] = new Array; flipedData[cellIdx][rowIdx]= row[cellIdx]; } } extractedData = flipedData; } // Build the data structure for the DataSet var parsedStructure = new Object(); parsedStructure.dataHash = new Object; parsedStructure.data = new Array; if (extractedData.length == 0) return parsedStructure; // Get the column names // this.firstRowAsHeaders is used only if the source of data is a TABLE var columnNames = new Array; var firstRowOfData = extractedData[0]; for (var cellIdx=0; cellIdx < firstRowOfData.length; cellIdx++) { if (usesTable && this.firstRowAsHeaders) columnNames[cellIdx] = Spry.Data.HTMLDataSet.normalizeColumnName(firstRowOfData[cellIdx]); else columnNames[cellIdx] = "column" + cellIdx; } // Check if column names are being overwritten using the optional columnNames parameter if (this.columnNames && this.columnNames.length) { if (this.columnNames.length < columnNames.length) Spry.Debug.reportError("Too few elements in the columnNames array. The columNames length must match the actual number of columns." ); else for (var i=0; i < columnNames.length; i++) { if (this.columnNames[i]) columnNames[i] = this.columnNames[i]; } } // Place the extracted data into a dataset kind of structure var nextID = 0; var firstDataRowIndex = (usesTable && this.firstRowAsHeaders) ? 1: 0; for (var rowIdx = firstDataRowIndex; rowIdx < extractedData.length; rowIdx++) { var row = extractedData[rowIdx]; if (columnNames.length != row.length) { Spry.Debug.reportError("Unbalanced column names for row #" + (rowIdx+1) + ". Skipping row." ); continue; } var rowObj = {}; for (var cellIdx = 0; cellIdx < row.length; cellIdx++) rowObj[columnNames[cellIdx]] = row[cellIdx]; rowObj['ds_RowID'] = nextID++; parsedStructure.dataHash[rowObj['ds_RowID']] = rowObj; parsedStructure.data.push(rowObj); } return parsedStructure; }; Spry.Data.HTMLDataSet.getElementChildren = function(element) { var children = []; var child = element.firstChild; while (child) { if (child.nodeType == 1) children.push(child); child = child.nextSibling; } return children; }; // This method extracts data from a TABLE structure // It knows how to handle both colspan and rowspan Spry.Data.HTMLDataSet.prototype.getDataFromHTMLTable = function() { var tHead = this.sourceElement.tHead; var tBody = this.sourceElement.tBodies[0]; var rowsHead = []; var rowsBody = []; if (tHead) rowsHead = Spry.Data.HTMLDataSet.getElementChildren(tHead); if (tBody) rowsBody = Spry.Data.HTMLDataSet.getElementChildren(tBody); var extractedData = new Array; var rows = rowsHead.concat(rowsBody); if (this.rowSelector) rows = Spry.Data.HTMLDataSet.applySelector(rows, this.rowSelector); for (var rowIdx = 0; rowIdx < rows.length; rowIdx++) { var row = rows[rowIdx]; var dataRow; if (extractedData[rowIdx]) dataRow = extractedData[rowIdx]; else dataRow = new Array; var offset = 0; var cells = row.cells; if (this.dataSelector) cells = Spry.Data.HTMLDataSet.applySelector(cells, this.dataSelector); for (var cellIdx=0; cellIdx < cells.length; cellIdx++) { var cell = cells[cellIdx]; var nextCellIndex = cellIdx + offset; // Find the next available position while (dataRow[nextCellIndex]) { offset ++; nextCellIndex ++; } var cellValue = Spry.Data.HTMLDataSet.undoCleanupSource(cell.innerHTML); dataRow[nextCellIndex] = cellValue; // Handle collspan var colspan = cell.colSpan; if (colspan == 0) colspan = 1; var startOffset = offset; for (var offIdx = 1; offIdx < colspan; offIdx++) { offset ++; nextCellIndex = cellIdx + offset; dataRow[nextCellIndex] = cellValue; } // Handle rowspan var rowspan = cell.rowSpan; if (rowspan == 0) rowspan = 1; for (var rowOffIdx = 1; rowOffIdx < rowspan; rowOffIdx++) { nextRowIndex = rowIdx + rowOffIdx; var nextDataRow; if (extractedData[nextRowIndex]) nextDataRow = extractedData[nextRowIndex]; else nextDataRow = new Array; for (var offIdx = 0; offIdx < colspan; offIdx++) { nextCellIndex = cellIdx + startOffset; nextDataRow[nextCellIndex] = cellValue; startOffset ++; } extractedData[nextRowIndex] = nextDataRow; } } extractedData[rowIdx] = dataRow; } return extractedData; }; // This method extracts data from any HTML structure // It uses rowSelector and dataSelector in order to build a three level nested structure - // Either one: rowSelector or dataSelector can miss Spry.Data.HTMLDataSet.prototype.getDataFromNestedStructure = function() { var extractedData = new Array; if (this.sourceElementID && !this.rowSelector && !this.dataSelector) { // The whole sourceElementID is a single row, single cell structure; extractedData[0] = [Spry.Data.HTMLDataSet.undoCleanupSource(this.sourceElement.innerHTML)]; return extractedData; } var self = this; // Get the rows var rows = []; if (!this.rowSelector) // If no rowSelector, there will be only one row rows = [this.sourceElement]; else rows = Spry.Utils.getNodesByFunc(this.sourceElement, function(node) { return Spry.Data.HTMLDataSet.evalSelector(node, self.sourceElement, self.rowSelector); }); // Get the data columns for (var rowIdx = 0; rowIdx < rows.length; rowIdx++) { var row = rows[rowIdx]; // Get the cells that actually hold the data for each row var cells = []; if (!this.dataSelector) // If no dataSelector, the whole row is extracted as one cell row. cells = [row]; else cells = Spry.Utils.getNodesByFunc(row, function(node) { return Spry.Data.HTMLDataSet.evalSelector(node, row, self.dataSelector); }); extractedData[rowIdx] = new Array; for (var cellIdx = 0; cellIdx < cells.length; cellIdx ++) extractedData[rowIdx][cellIdx] = Spry.Data.HTMLDataSet.undoCleanupSource(cells[cellIdx].innerHTML); } return extractedData; }; // Applies a css selector on a collection and returns the resulting elements Spry.Data.HTMLDataSet.applySelector = function(collection, selector, root) { var newCollection = []; for (var idx = 0; idx < collection.length; idx++) { var node = collection[idx]; if (Spry.Data.HTMLDataSet.evalSelector(node, root?root:node.parentNode, selector)) newCollection.push(node); } return newCollection; }; // Checks if a specified node matches the specified css selector Spry.Data.HTMLDataSet.evalSelector = function (node, root, selector) { if (node.nodeType != 1) return false; if (node == root) return false; // Comma delimited selectors can be passed in // The node is selected if it matches one of the selectors // #myID1, div#myID2, #myID3 var selectors = selector.split(","); for (var idx = 0; idx < selectors.length; idx ++) { var currentSelector = selectors[idx].replace(/^\s+/, "").replace(/\s+$/, ""); var tagName = null; var className = null; var id = null; // Accepted values for the selector: // DIV.myClass | DIV | .myClass | *.myClass // DIV#myID | #myID // > DIV.myClass : > points to the direct descendents var selected = true; if (currentSelector.substring(0,1) == ">") { // Looking for direct descendants only if (node.parentNode != root) selected = false; else currentSelector = currentSelector.substring(1).replace(/^\s+/, ""); } if (selected) { tagName = currentSelector.toLowerCase(); if (currentSelector.indexOf(".") != -1) { var parts = currentSelector.split("."); tagName = parts[0]; className = parts[1]; } else if (currentSelector.indexOf("#") != -1) { var parts = currentSelector.split("#"); tagName = parts[0]; id = parts[1]; } } if (selected && tagName != '' && tagName != '*') if (node.nodeName.toLowerCase() != tagName) selected = false; if (selected && id && node.id != id) selected = false; if (selected && className && node.className.search(new RegExp('\\b' + className + '\\b', 'i')) ==-1) selected = false; if (selected) return true; } return false; };