/* * (c) 2005-2011 Richard Cowin (http://openrico.org) * (c) 2005-2011 Matt Brown (http://dowdybrown.com) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ if(typeof Rico=='undefined') throw("LiveGridAjax requires the Rico JavaScript framework"); if (!Rico.Buffer) Rico.Buffer = {}; Rico.Buffer.AjaxLoadOnce = function(url,options,ajaxOptions) { this.initialize(url,options,ajaxOptions); } Rico.Buffer.AjaxLoadOnce.prototype = { /** * @class Implements buffer for LiveGrid. Loads data from server via a single AJAX call. * @extends Rico.Buffer.Base * @constructs */ initialize: function(url,options,ajaxOptions) { Rico.extend(this, new Rico.Buffer.Base()); Rico.extend(this, Rico.Buffer.AjaxXMLMethods); this.dataSource=url; this.options.bufferTimeout=20000; // time to wait for ajax response (milliseconds) this.options.requestParameters=[]; this.options.waitMsg=Rico.getPhraseById("waitForData"); // replace this with an image tag if you prefer this.options.canFilter=true; this.options.fmt='xml'; Rico.extend(this.options, options || {}); this.ajaxOptions = { parameters: null, method : 'get' }; Rico.extend(this.ajaxOptions, ajaxOptions || {}); this.requestCount=0; this.processingRequest=false; this.pendingRequest=-2; this.fetchData=true; this.sortParm={}; } } Rico.Buffer.AjaxXMLMethods = { /** @lends Rico.Buffer.AjaxLoadOnce# */ fetch: function(offset) { if (this.fetchData) { this.foundRowCount=true; this.fetchData=false; this.processingRequest=true; this.liveGrid.showMsg(this.options.waitMsg); this.timeoutHandler = Rico.runLater(this.options.bufferTimeout,this,'handleTimedOut'); this.ajaxOptions.parameters = this.formQueryHashXML(0,-1); Rico.log('sending request'); var self=this; if (typeof this.dataSource=='string') { this.ajaxOptions.onComplete = function(xhr) { self.ajaxUpdate(offset,xhr); }; new Rico.ajaxRequest(this.dataSource, this.ajaxOptions); } else { this.ajaxOptions.onComplete = function(newRows, newStyle, totalRows, errMsg) { self.jsUpdate(offset, newRows, newStyle, totalRows, errMsg); }; this.dataSource(this.ajaxOptions); } } else { if (offset < 0) { this.applyFilters(); this.setTotalRows(this.size); offset=0; } this.liveGrid.refreshContents(offset); } }, /** * Server did not respond in time... assume that there could have been * an error, and allow requests to be processed again. */ handleTimedOut: function() { Rico.log("Request Timed Out"); this.liveGrid.showMsg(Rico.getPhraseById("requestTimedOut")); }, formQueryHashXML: function(startPos,fetchSize) { var queryHash= { id: this.liveGrid.tableId, page_size: (typeof fetchSize=='number') ? fetchSize : this.totalRows, offset: startPos.toString() }; queryHash[this.liveGrid.actionId]="query"; if (this.options.requestParameters) { for ( var i=0; i < this.options.requestParameters.length; i++ ) { var anArg = this.options.requestParameters[i]; if ( anArg.name != undefined && anArg.value != undefined ) { queryHash[anArg.name]=anArg.value; } else { var ePos = anArg.indexOf('='); var argName = anArg.substring( 0, ePos ); var argValue = anArg.substring( ePos + 1 ); queryHash[argName]=argValue; } } } return queryHash; }, clearTimer: function() { if(typeof this.timeoutHandler != "number") return; window.clearTimeout(this.timeoutHandler); delete this.timeoutHandler; }, // used by both LoadOnce and SQL buffers jsUpdate: function(startPos, newRows, newStyle, totalRows, errMsg) { this.clearTimer(); this.processingRequest=false; Rico.log("jsUpdate: "+arguments.length); if (errMsg) { Rico.log("jsUpdate: received error="+errMsg); this.liveGrid.showMsg(Rico.getPhraseById("requestError",errMsg)); return; } this.rcvdRows = newRows.length; if (typeof totalRows=='number') { this.rowcntContent = totalRows.toString(); this.rcvdRowCount = true; this.foundRowCount = true; Rico.log("jsUpdate: found RowCount="+this.rowcntContent); } this.updateBuffer(startPos, newRows, newStyle); if (this.options.onAjaxUpdate) this.options.onAjaxUpdate(); this.updateGrid(startPos); if (this.options.TimeOut && this.timerMsg) this.restartSessionTimer(); if (this.pendingRequest>=-1) { var offset=this.pendingRequest; Rico.log("jsUpdate: found pending request for offset="+offset); this.pendingRequest=-2; this.fetch(offset); } }, // used by both LoadOnce and SQL buffers ajaxUpdate: function(startPos,xhr) { this.clearTimer(); this.processingRequest=false; if (xhr.status != 200) { Rico.log("ajaxUpdate: received http error="+xhr.status); this.liveGrid.showMsg(Rico.getPhraseById("httpError",xhr.status)); return; } Rico.log("ajaxUpdate: startPos="+startPos); this._responseHandler=this['processResponse'+this.options.fmt.toUpperCase()]; if (!this._responseHandler(startPos,xhr)) return; if (this.options.onAjaxUpdate) this.options.onAjaxUpdate(); this.updateGrid(startPos); if (this.options.TimeOut && this.timerMsg) this.restartSessionTimer(); if (this.pendingRequest>=-1) { var offset=this.pendingRequest; Rico.log("ajaxUpdate: found pending request for offset="+offset); this.pendingRequest=-2; this.fetch(offset); } }, // used by both LoadOnce and SQL buffers processResponseXML: function(startPos,request) { // The response text may contain META DATA for debugging if client side debugging is enabled in VS var xmlDoc = request.responseXML; if (request.responseText.substring(0, 4) == ""); if (nEnd == -1) { this.liveGrid.showMsg('Web server error - client side debugging may be enabled'); return false; } xmlDoc = Rico.createXmlDocument(); xmlDoc.loadXML(request.responseText.substring(nEnd+3)); } if (!xmlDoc) { alert("Data provider returned an invalid XML response"); Rico.log("Data provider returned an invalid XML response"); return false; } // process children of var response = xmlDoc.getElementsByTagName("ajax-response"); if (response == null || response.length != 1) { alert("Received invalid response from server"); return false; } Rico.log("Processing ajax-response"); this.rcvdRows = 0; this.rcvdRowCount = false; var ajaxResponse=response[0]; var debugtags = ajaxResponse.getElementsByTagName('debug'); for (var i=0; i 0) { var msg=Rico.getContentAsString(error[0],this.options.isEncoded); alert("Data provider returned an error:\n"+msg); Rico.log("Data provider returned an error:\n"+msg); return false; } var rowsElement = ajaxResponse.getElementsByTagName('rows')[0]; if (!rowsElement) { Rico.log("ajaxUpdate: invalid response"); this.liveGrid.showMsg(Rico.getPhraseById("invalidResponse")); return false; } var rowcnttags = ajaxResponse.getElementsByTagName('rowcount'); if (rowcnttags && rowcnttags.length==1) { this.rowcntContent = Rico.getContentAsString(rowcnttags[0],this.options.isEncoded); this.rcvdRowCount = true; this.foundRowCount = true; Rico.log("ajaxUpdate: found RowCount="+this.rowcntContent); } // process this.updateUI = rowsElement.getAttribute("update_ui") == "true"; this.rcvdOffset = rowsElement.getAttribute("offset"); Rico.log("ajaxUpdate: rcvdOffset="+this.rcvdOffset); var newRows = this.dom2jstable(rowsElement); var newStyle = (this.options.acceptStyle) ? this.dom2jstableStyle(rowsElement) : false; this.rcvdRows = newRows.length; this.updateBuffer(startPos, newRows, newStyle); return true; }, dom2jstableStyle: function(rowsElement,firstRow) { Rico.log("dom2jstableStyle start"); var newRows = []; var trs = rowsElement.getElementsByTagName("tr"); for ( var i=firstRow || 0; i < trs.length; i++ ) { var row = []; var cells = trs[i].getElementsByTagName("td"); for ( var j=0; j < cells.length ; j++ ) { row[j]=cells[j].getAttribute('style') || ''; } newRows.push( row ); } Rico.log("dom2jstableStyle end"); return newRows; }, processResponseJSON: function(startPos,request) { var json = Rico.getJSON(request); if (!json || json == null) { alert("Data provider returned an invalid JSON response"); Rico.log("Data provider returned an invalid JSON response"); return false; } if (json.debug) { for (var i=0; i this.sessionEndTime) { this.displaySessionTimer(Rico.getPhraseById("sessionExpired")); this.timerMsg.style.backgroundColor="red"; this.sessionExpired=true; } else { var timeRemaining=Math.ceil((this.sessionEndTime - now) / 60000); this.displaySessionTimer(timeRemaining); this.sessionTimer=Rico.runLater(10000,this,'updateSessionTimer'); } }, displaySessionTimer: function(msg) { this.timerMsg.innerHTML=' '+msg+' '; }, /** * Update the grid with fresh data from the database, maintaining scroll position. * @param resetRowCount indicates whether the total row count should be refreshed as well */ refresh: function(resetRowCount) { var lastGridPos=this.liveGrid.lastRowPos; this.clear(); if (resetRowCount) { this.setTotalRows(0); this.foundRowCount = false; } this.liveGrid.clearBookmark(); this.liveGrid.clearRows(); this.fetch(lastGridPos); }, /** * Fetch data from database. * @param offset position (row) within the dataset (-1=clear existing buffer before issuing request) */ fetch: function(offset) { Rico.log("AjaxSQL fetch: offset="+offset+', lastOffset='+this.lastOffset); if (this.processingRequest) { Rico.log("AjaxSQL fetch: queue request"); this.pendingRequest=offset; return; } if ((typeof offset == 'undefined') || (offset < 0)) { this.clear(); this.setTotalRows(0); this.foundRowCount = false; offset=0; } var lastOffset = this.lastOffset; this.lastOffset = offset; if (this.isInRange(offset)) { Rico.log("AjaxSQL fetch: in buffer"); this.liveGrid.refreshContents(offset); if (offset > lastOffset) { if (offset+this.liveGrid.pageSize < this.endPos()-this.nearLimit) return; if (this.endPos()==this.totalRows && this.foundRowCount) return; } else if (offset < lastOffset) { if (offset > this.startPos+this.nearLimit) return; if (this.startPos==0) return; } else return; } if (offset >= this.totalRows && this.foundRowCount) return; this.processingRequest=true; Rico.log("AjaxSQL fetch: processing offset="+offset); var bufferStartPos = this.getFetchOffset(offset); var fetchSize = this.getFetchSize(bufferStartPos); var partialLoaded = false; this.liveGrid.showMsg(this.options.waitMsg); this.timeoutHandler = Rico.runLater(this.options.bufferTimeout, this, 'handleTimedOut'); this.ajaxOptions.parameters = this.formQueryHashSQL(bufferStartPos,fetchSize,this.options.fmt); this.requestCount++; Rico.log('sending req #'+this.requestCount); var self=this; if (typeof this.dataSource=='string') { this.ajaxOptions.onComplete = function(xhr) { self.ajaxUpdate(bufferStartPos, xhr); }; new Rico.ajaxRequest(this.dataSource, this.ajaxOptions); } else { this.ajaxOptions.onComplete = function(newRows, newStyle, totalRows, errMsg) { self.jsUpdate(bufferStartPos, newRows, newStyle, totalRows, errMsg); }; this.dataSource(this.ajaxOptions); } }, formQueryHashSQL: function(startPos,fetchSize,fmt) { var queryHash=this.formQueryHashXML(startPos,fetchSize); if (!this.foundRowCount) queryHash['get_total']='true'; if (fmt) queryHash._fmt=fmt; // sort Rico.extend(queryHash,this.sortParm); // filters for (var n=0; n= this.startPos) { //appending var endFetchOffset = this.maxFetchSize + adjustedOffset; adjustedSize = endFetchOffset - adjustedOffset; if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize) adjustedSize = this.maxFetchSize; Rico.log("getFetchSize/append, adjustedSize="+adjustedSize+" adjustedOffset="+adjustedOffset+' endFetchOffset='+endFetchOffset); } else { //prepending adjustedSize = Math.min(this.startPos - adjustedOffset,this.maxFetchSize); } return adjustedSize; }, getFetchOffset: function(offset) { var adjustedOffset = offset; if (offset > this.startPos) adjustedOffset = Math.max(offset, this.endPos()); //appending else if (offset + this.maxFetchSize >= this.startPos) adjustedOffset = Math.max(this.startPos - this.maxFetchSize, 0); //prepending return adjustedOffset; }, updateBuffer: function(start, newRows, newStyle) { Rico.log("updateBuffer: start="+start+", # of rows="+this.rcvdRows); if (this.rows.length == 0) { // initial load this.rows = newRows; this.attr = newStyle; this.startPos = start; } else if (start > this.startPos) { //appending if (this.startPos + this.rows.length < start) { this.rows = newRows; this.attr = newStyle; this.startPos = start; } else { this.rows = this.rows.concat( newRows.slice(0, newRows.length)); if (this.attr && newStyle) this.attr = this.attr.concat( newStyle.slice(0, newStyle.length)); if (this.rows.length > this.maxBufferSize) { var fullSize = this.rows.length; this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length); if (this.attr) this.attr = this.attr.slice(this.attr.length - this.maxBufferSize, this.attr.length); this.startPos = this.startPos + (fullSize - this.rows.length); } } } else { //prepending if (start + newRows.length < this.startPos) { this.rows = newRows; this.attr = newStyle; } else { this.rows = newRows.slice(0, this.startPos).concat(this.rows); if (newStyle) this.attr = newStyle.slice(0, this.startPos).concat(this.attr); if (this.maxBufferSize && this.rows.length > this.maxBufferSize) { this.rows = this.rows.slice(0, this.maxBufferSize); if (this.attr) this.attr = this.attr.slice(0, this.maxBufferSize); } } this.startPos = start; } this.size = this.rows.length; }, sortBuffer: function(colnum) { this.sortParm={}; var col=this.liveGrid.columns[colnum]; if (this.options.sortParmFmt) { this.sortParm['sort_col']=col[this.options.sortParmFmt]; this.sortParm['sort_dir']=col.getSortDirection(); } else { this.sortParm['s'+colnum]=col.getSortDirection(); } this.clear(); }, printAllSQL: function(exportType) { var parms=this.formQueryHashSQL(0,this.liveGrid.options.maxPrint,exportType); parms.hidden=this.liveGrid.listInvisible('index').join(','); var url=this.dataSource+'?'+Rico.toQueryString(parms); window.open(url,'',this.liveGrid.options.exportWindow); }, printVisibleSQL: function(exportType) { var parms=this.formQueryHashSQL(this.liveGrid.contentStartPos-1, this.liveGrid.pageSize, exportType); parms.hidden=this.liveGrid.listInvisible('index').join(','); var url=this.dataSource+'?'+Rico.toQueryString(parms); window.open(url,'',this.liveGrid.options.exportWindow); }, // for datasource that is a javascript function _printAll: function() { this.liveGrid.exportStart(); this.ajaxOptions.parameters = this.formQueryHashSQL(0,this.liveGrid.options.maxPrint); var self=this; this.ajaxOptions.onComplete = function() { self._jsExport(); }; this.dataSource(this.ajaxOptions); }, _jsExport: function(newRows, newStyle, totalRows, errMsg) { Rico.log("_jsExport: "+arguments.length); if (errMsg) { Rico.log("_jsExport: received error="+errMsg); this.liveGrid.showMsg(Rico.getPhraseById("requestError",errMsg)); return; } this.exportBuffer(newRows,0); this.liveGrid.exportFinish(); } };