| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244 |
- if (typeof _$ == 'undefined') {
- function _$(elementId) { return document.getElementById(elementId); }
- }
- /**
- * Creates a new column
- * @constructor
- * @class Represents a column in the editable grid
- * @param {Object} config
- */
- function Column(config)
- {
- // default properties
- var props = {
- name: "",
- label: "",
- editable: true,
- renderable: true,
- datatype: "string",
- unit: null,
- precision: -1, // means that all decimals are displayed
- nansymbol: '',
- decimal_point: ',',
- thousands_separator: '.',
- unit_before_number: false,
- bar: true, // is the column to be displayed in a bar chart ? relevant only for numerical columns
- hidden: false, // should the column be hidden by default
- headerRenderer: null,
- headerEditor: null,
- cellRenderer: null,
- cellEditor: null,
- cellValidators: [],
- enumProvider: null,
- optionValues: null,
- optionValuesForRender: null,
- columnIndex: -1
- };
- // override default properties with the ones given
- for (var p in props) this[p] = (typeof config == 'undefined' || typeof config[p] == 'undefined') ? props[p] : config[p];
- }
- Column.prototype.getOptionValuesForRender = function(rowIndex) {
- if (!this.enumProvider) {
- console.log('getOptionValuesForRender called on column ' + this.name + ' but there is no EnumProvider');
- return null;
- }
- var values = this.enumProvider.getOptionValuesForRender(this.editablegrid, this, rowIndex);
- return values ? values : this.optionValuesForRender;
- };
- Column.prototype.getOptionValuesForEdit = function(rowIndex) {
- if (!this.enumProvider) {
- console.log('getOptionValuesForEdit called on column ' + this.name + ' but there is no EnumProvider');
- return null;
- }
- var values = this.enumProvider.getOptionValuesForEdit(this.editablegrid, this, rowIndex);
- return values ? this.editablegrid._convertOptions(values) : this.optionValues;
- };
- Column.prototype.isValid = function(value) {
- for (var i = 0; i < this.cellValidators.length; i++) if (!this.cellValidators[i].isValid(value)) return false;
- return true;
- };
- Column.prototype.isNumerical = function() {
- return this.datatype =='double' || this.datatype =='integer';
- };
- /**
- * Creates a new enumeration provider
- * @constructor
- * @class Base class for all enumeration providers
- * @param {Object} config
- */
- function EnumProvider(config)
- {
- // default properties
- this.getOptionValuesForRender = function(grid, column, rowIndex) { return null; };
- this.getOptionValuesForEdit = function(grid, column, rowIndex) { return null; };
- // override default properties with the ones given
- for (var p in config) this[p] = config[p];
- }
- /**
- * Creates a new EditableGrid.
- * <p>You can specify here some configuration options (optional).
- * <br/>You can also set these same configuration options afterwards.
- * <p>These options are:
- * <ul>
- * <li>enableSort: enable sorting when clicking on column headers (default=true)</li>
- * <li>doubleclick: use double click to edit cells (default=false)</li>
- * <li>editmode: can be one of
- * <ul>
- * <li>absolute: cell editor comes over the cell (default)</li>
- * <li>static: cell editor comes inside the cell</li>
- * <li>fixed: cell editor comes in an external div</li>
- * </ul>
- * </li>
- * <li>editorzoneid: used only when editmode is set to fixed, it is the id of the div to use for cell editors</li>
- * <li>allowSimultaneousEdition: tells if several cells can be edited at the same time (default=false)<br/>
- * Warning: on some Linux browsers (eg. Epiphany), a blur event is sent when the user clicks on a 'select' input to expand it.
- * So practically, in these browsers you should set allowSimultaneousEdition to true if you want to use columns with option values and/or enum providers.
- * This also used to happen in older versions of Google Chrome Linux but it has been fixed, so upgrade if needed.</li>
- * <li>saveOnBlur: should be cells saved when clicking elsewhere ? (default=true)</li>
- * <li>invalidClassName: CSS class to apply to text fields when the entered value is invalid (default="invalid")</li>
- * <li>ignoreLastRow: ignore last row when sorting and charting the data (typically for a 'total' row)</li>
- * <li>caption: text to use as the grid's caption</li>
- * <li>dateFormat: EU or US (default="EU")</li>
- * <li>shortMonthNames: list of month names (default=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"])</li>
- * <li>smartColorsBar: colors used for rendering (stacked) bar charts</li>
- * <li>smartColorsPie: colors used for rendering pie charts</li>
- * <li>pageSize: maximum number of rows displayed (0 means we don't want any pagination, which is the default)</li>
- * <li>sortIconDown: icon used to show desc order</li>
- * <li>sortIconUp: icon used to show asc order</li>
- * </ul>
- * @constructor
- * @class EditableGrid
- */
- function EditableGrid(name, config) { if (name) this.init(name, config); }
- /**
- * Default properties
- */
- EditableGrid.prototype.enableSort = true;
- EditableGrid.prototype.enableStore = true;
- EditableGrid.prototype.doubleclick = false;
- EditableGrid.prototype.editmode = "absolute";
- EditableGrid.prototype.editorzoneid = "";
- EditableGrid.prototype.allowSimultaneousEdition = false;
- EditableGrid.prototype.saveOnBlur = true;
- EditableGrid.prototype.invalidClassName = "invalid";
- EditableGrid.prototype.ignoreLastRow = false;
- EditableGrid.prototype.caption = null;
- EditableGrid.prototype.dateFormat = "EU";
- EditableGrid.prototype.shortMonthNames = null;
- EditableGrid.prototype.smartColorsBar = ["#dc243c","#4040f6","#00f629","#efe100","#f93fb1","#6f8183","#111111"];
- EditableGrid.prototype.smartColorsPie = ["#FF0000","#00FF00","#0000FF","#FFD700","#FF00FF","#00FFFF","#800080"];
- EditableGrid.prototype.pageSize = 0; // client-side pagination, don't set this for server-side pagination!
- //server-side pagination, sorting and filtering
- EditableGrid.prototype.serverSide = false;
- EditableGrid.prototype.pageCount = 0;
- EditableGrid.prototype.totalRowCount = 0;
- EditableGrid.prototype.unfilteredRowCount = 0;
- EditableGrid.prototype.paginatorAttributes = null;
- EditableGrid.prototype.lastURL = null;
- EditableGrid.prototype.init = function (name, config)
- {
- if (typeof name != "string" || (typeof config != "object" && typeof config != "undefined")) {
- alert("The EditableGrid constructor takes two arguments:\n- name (string)\n- config (object)\n\nGot instead " + (typeof name) + " and " + (typeof config) + ".");
- };
- // override default properties with the ones given
- if (typeof config != 'undefined') for (var p in config) this[p] = config[p];
- this.Browser = {
- IE: !!(window.attachEvent && navigator.userAgent.indexOf('Opera') === -1),
- Opera: navigator.userAgent.indexOf('Opera') > -1,
- WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
- Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
- MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
- };
- if (typeof this.detectDir != 'function') {
- var error = new Error();
- alert("Who is calling me now ? " + error.stack);
- }
- // private data
- this.name = name;
- this.columns = [];
- this.data = [];
- this.dataUnfiltered = null; // non null means that data is filtered
- this.xmlDoc = null;
- this.sortedColumnName = -1;
- this.sortDescending = false;
- this.baseUrl = this.detectDir();
- this.nbHeaderRows = 1;
- this.lastSelectedRowIndex = -1;
- this.currentPageIndex = 0;
- this.currentFilter = null;
- this.currentContainerid = null;
- this.currentClassName = null;
- this.currentTableid = null;
- if (this.enableSort) {
- this.sortUpImage = new Image();
- if ( typeof config != "undefined" && typeof config['sortIconUp'] != "undefined" )
- this.sortUpImage.src = config['sortIconUp'];
- else
- this.sortUpImage.src ="/static/img/bullet_arrow_up.png";
- this.sortDownImage = new Image();
- if ( typeof config != "undefined" && typeof config['sortIconDown'] != "undefined" )
- this.sortDownImage.src = config['sortIconDown'];
- else
- this.sortDownImage.src = "/static/img/bullet_arrow_down.png";
- }
- // restore stored parameters, or use default values if nothing stored
- this.currentPageIndex = this.localisset('pageIndex') ? parseInt(this.localget('pageIndex')) : 0;
- this.sortedColumnName = this.localisset('sortColumnIndexOrName') ? this.localget('sortColumnIndexOrName') : -1;
- this.sortDescending = this.localisset('sortColumnIndexOrName') && this.localisset('sortDescending') ? this.localget('sortDescending') == 'true' : false;
- this.currentFilter = this.localisset('filter') ? this.localget('filter') : null;
- };
- /**
- * Callback functions
- */
- EditableGrid.prototype.tableLoaded = function() {};
- EditableGrid.prototype.chartRendered = function() {};
- EditableGrid.prototype.tableRendered = function(containerid, className, tableid) {};
- EditableGrid.prototype.tableSorted = function(columnIndex, descending) {};
- EditableGrid.prototype.tableFiltered = function() {};
- EditableGrid.prototype.modelChanged = function(rowIndex, columnIndex, oldValue, newValue, row) {};
- EditableGrid.prototype.rowSelected = function(oldRowIndex, newRowIndex) {};
- EditableGrid.prototype.isHeaderEditable = function(rowIndex, columnIndex) { return false; };
- EditableGrid.prototype.isEditable =function(rowIndex, columnIndex) { return true; };
- EditableGrid.prototype.readonlyWarning = function() {};
- /** Notifies that a row has been deleted */
- EditableGrid.prototype.rowRemoved = function(oldRowIndex, rowId) {};
- /**
- * Load metadata and/or data from an XML url
- * The callback "tableLoaded" is called when loading is complete.
- */
- EditableGrid.prototype.loadXML = function(url, callback, dataOnly)
- {
- this.lastURL = url;
- var self = this;
- // IE
- if (window.ActiveXObject)
- {
- this.xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
- this.xmlDoc.onreadystatechange = function() {
- if (self.xmlDoc.readyState == 4) {
- self.processXML();
- self._callback('xml', callback);
- }
- };
- this.xmlDoc.load(this._addUrlParameters(url, dataOnly));
- }
- // generic Ajax
- else if (window.XMLHttpRequest)
- {
- this.xmlDoc = new XMLHttpRequest();
- this.xmlDoc.onreadystatechange = function () {
- if (this.readyState == 4) {
- self.xmlDoc = this.responseXML;
- if (!self.xmlDoc) { console.error("Could not load XML from url '" + url + "'"); return false; }
- self.processXML();
- self._callback('xml', callback);
- }
- };
- this.xmlDoc.open("GET", this._addUrlParameters(url, dataOnly), true);
- this.xmlDoc.send("");
- }
- // Firefox (and some other browsers)
- else if (document.implementation && document.implementation.createDocument)
- {
- this.xmlDoc = document.implementation.createDocument("", "", null);
- this.xmlDoc.onload = function() {
- self.processXML();
- self._callback('xml', callback);
- };
- this.xmlDoc.load(this._addUrlParameters(url, dataOnly));
- }
- // should never happen
- else {
- alert("Cannot load a XML url with this browser!");
- return false;
- }
- return true;
- };
- /**
- * Load metadata and/or data from an XML string
- * No callback "tableLoaded" is called since this is a synchronous operation.
- *
- * Contributed by Tim Consolazio of Tcoz Tech Services, tcoz@tcoz.com
- * http://tcoztechwire.blogspot.com/2012/04/setxmlfromstring-extension-for.html
- */
- EditableGrid.prototype.loadXMLFromString = function(xml)
- {
- if (window.DOMParser) {
- var parser = new DOMParser();
- this.xmlDoc = parser.parseFromString(xml, "application/xml");
- }
- else {
- this.xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); // IE
- this.xmlDoc.async = "false";
- this.xmlDoc.loadXML(xml);
- }
- this.processXML();
- };
- /**
- * Process the XML content
- * @private
- */
- EditableGrid.prototype.processXML = function()
- {
- with (this) {
- // clear model and pointer to current table
- this.data = [];
- this.dataUnfiltered = null;
- this.table = null;
- // load metadata (only one tag <metadata> --> metadata[0])
- var metadata = xmlDoc.getElementsByTagName("metadata");
- if (metadata && metadata.length >= 1) {
- this.columns = [];
- var columnDeclarations = metadata[0].getElementsByTagName("column");
- for (var i = 0; i < columnDeclarations.length; i++) {
- // get column type
- var col = columnDeclarations[i];
- var datatype = col.getAttribute("datatype");
- // get enumerated values if any
- var optionValuesForRender = null;
- var optionValues = null;
- var enumValues = col.getElementsByTagName("values");
- if (enumValues.length > 0) {
- optionValues = [];
- optionValuesForRender = {};
- var enumGroups = enumValues[0].getElementsByTagName("group");
- if (enumGroups.length > 0) {
- for (var g = 0; g < enumGroups.length; g++) {
- var groupOptionValues = [];
- enumValues = enumGroups[g].getElementsByTagName("value");
- for (var v = 0; v < enumValues.length; v++) {
- var _value = enumValues[v].getAttribute("value");
- var _label = enumValues[v].firstChild ? enumValues[v].firstChild.nodeValue : "";
- optionValuesForRender[_value] = _label;
- groupOptionValues.push({ value: _value, label: _label });
- }
- optionValues.push({ label: enumGroups[g].getAttribute("label"), values: groupOptionValues});
- }
- }
- else {
- enumValues = enumValues[0].getElementsByTagName("value");
- for (var v = 0; v < enumValues.length; v++) {
- var _value = enumValues[v].getAttribute("value");
- var _label = enumValues[v].firstChild ? enumValues[v].firstChild.nodeValue : "";
- optionValuesForRender[_value] = _label;
- optionValues.push({ value: _value, label: _label });
- }
- }
- }
- // create new column
- columns.push(new Column({
- name: col.getAttribute("name"),
- label: (typeof col.getAttribute("label") == 'string' ? col.getAttribute("label") : col.getAttribute("name")),
- datatype: (col.getAttribute("datatype") ? col.getAttribute("datatype") : "string"),
- editable: col.getAttribute("editable") == "true",
- bar: (col.getAttribute("bar") ? col.getAttribute("bar") == "true" : true),
- hidden: (col.getAttribute("hidden") ? col.getAttribute("hidden") == "true" : false),
- optionValuesForRender: optionValuesForRender,
- optionValues: optionValues
- }));
- }
- // process columns
- processColumns();
- }
- // load server-side pagination data
- var paginator = xmlDoc.getElementsByTagName("paginator");
- if (paginator && paginator.length >= 1) {
- this.paginatorAttributes = null; // TODO: paginator[0].getAllAttributesAsPOJO;
- this.pageCount = paginator[0].getAttribute('pagecount');
- this.totalRowCount = paginator[0].getAttribute('totalrowcount');
- this.unfilteredRowCount = paginator[0].getAttribute('unfilteredrowcount');
- }
- // if no row id is provided, we create one since we need one
- var defaultRowId = 1;
- // load content
- var rows = xmlDoc.getElementsByTagName("row");
- for (var i = 0; i < rows.length; i++)
- {
- // get all defined cell values
- var cellValues = {};
- var cols = rows[i].getElementsByTagName("column");
- for (var j = 0; j < cols.length; j++) {
- var colname = cols[j].getAttribute("name");
- if (!colname) {
- if (j >= columns.length) console.error("You defined too many columns for row " + (i+1));
- else colname = columns[j].name;
- }
- cellValues[colname] = cols[j].firstChild ? cols[j].firstChild.nodeValue : "";
- }
- // for each row we keep the orginal index, the id and all other attributes that may have been set in the XML
- var rowData = { visible: true, originalIndex: i, id: rows[i].getAttribute("id") ? rows[i].getAttribute("id") : defaultRowId++ };
- for (var attrIndex = 0; attrIndex < rows[i].attributes.length; attrIndex++) {
- var node = rows[i].attributes.item(attrIndex);
- if (node.nodeName != "id") rowData[node.nodeName] = node.nodeValue;
- }
- // get column values for this rows
- rowData.columns = [];
- for (var c = 0; c < columns.length; c++) {
- var cellValue = columns[c].name in cellValues ? cellValues[columns[c].name] : "";
- rowData.columns.push(getTypedValue(c, cellValue));
- }
- // add row data in our model
- data.push(rowData);
- }
- }
- return true;
- };
- /**
- * Load metadata and/or data from a JSON url
- * The callback "tableLoaded" is called when loading is complete.
- */
- EditableGrid.prototype.loadJSON = function(url, callback, dataOnly)
- {
- this.lastURL = url;
- var self = this;
- // should never happen
- if (!window.XMLHttpRequest) {
- alert("Cannot load a JSON url with this browser!");
- return false;
- }
- var ajaxRequest = new XMLHttpRequest();
- ajaxRequest.onreadystatechange = function () {
- if (this.readyState == 4) {
- if (!this.responseText) { console.error("Could not load JSON from url '" + url + "'"); return false; }
- if (!self.processJSON(this.responseText)) { console.error("Invalid JSON data obtained from url '" + url + "'"); return false; }
- self._callback('json', callback);
- }
- };
- ajaxRequest.open("GET", this._addUrlParameters(url, dataOnly), true);
- ajaxRequest.send("");
- return true;
- };
- EditableGrid.prototype._addUrlParameters = function(baseUrl, dataOnly)
- {
- // we add a dummy timestamp parameter to avoid getting an old version from the browser's cache
- var sep = baseUrl.indexOf('?') >= 0 ? '&' : '?';
- baseUrl += sep + (new Date().getTime());
- if (!this.serverSide) return baseUrl;
- // add pagination, filtering and sorting parameters to the base url
- return baseUrl
- + "&page=" + (this.currentPageIndex + 1)
- + "&filter=" + (this.currentFilter ? encodeURIComponent(this.currentFilter) : "")
- + "&sort=" + (this.sortedColumnName && this.sortedColumnName != -1 ? encodeURIComponent(this.sortedColumnName) : "")
- + "&asc=" + (this.sortDescending ? 0 : 1)
- + (dataOnly ? '&data_only=1' : '');
- };
- EditableGrid.prototype._callback = function(type, callback)
- {
- if (callback) callback.call(this);
- else {
- if (this.serverSide) {
- // deferred refreshGrid: first load the updated data from the server then call the original refreshGrid
- this.refreshGrid = function(baseUrl) {
- var callback = function() { EditableGrid.prototype.refreshGrid.call(this); };
- var load = type == 'xml' ? this.loadXML : this.loadJSON;
- load.call(this, baseUrl || this.lastURL, callback, true);
- };
- }
- this.tableLoaded();
- }
- };
- /**
- * Load metadata and/or data from a JSON string
- * No callback "tableLoaded" is called since this is a synchronous operation.
- */
- EditableGrid.prototype.loadJSONFromString = function(json)
- {
- return this.processJSON(json);
- };
- /**
- * Load metadata and/or data from a Javascript object
- * No callback "tableLoaded" is called since this is a synchronous operation.
- */
- EditableGrid.prototype.load = function(object)
- {
- return this.processJSON(object);
- };
- /**
- * Update and render data for given rows from a Javascript object
- */
- EditableGrid.prototype.update = function(object)
- {
- if (object.data) for (var i = 0; i < object.data.length; i++)
- {
- var row = object.data[i];
- if (!row.id || !row.values) continue;
- // get row to update in our model
- var rowIndex = this.getRowIndex(row.id);
- var rowData = this.data[rowIndex];
- // row values can be given as an array (same order as columns) or as an object (associative array)
- if (Object.prototype.toString.call(row.values) !== '[object Array]' ) cellValues = row.values;
- else {
- cellValues = {};
- for (var j = 0; j < row.values.length && j < this.columns.length; j++) cellValues[this.columns[j].name] = row.values[j];
- }
- // set all attributes that may have been set in the JSON
- for (var attributeName in row) if (attributeName != "id" && attributeName != "values") rowData[attributeName] = row[attributeName];
- // get column values for this rows
- rowData.columns = [];
- for (var c = 0; c < this.columns.length; c++) {
- var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
- rowData.columns.push(this.getTypedValue(c, cellValue));
- }
- // render row
- var tr = this.getRow(rowIndex);
- for (var j = 0; j < tr.cells.length && j < this.columns.length; j++) if (this.columns[j].renderable) this.columns[j].cellRenderer._render(rowIndex, j, tr.cells[j], this.getValueAt(rowIndex,j));
- this.tableRendered(this.currentContainerid, this.currentClassName, this.currentTableid);
- }
- };
- /**
- * Process the JSON content
- * @private
- */
- EditableGrid.prototype.processJSON = function(jsonData)
- {
- if (typeof jsonData == "string") jsonData = eval("(" + jsonData + ")");
- if (!jsonData) return false;
- // clear model and pointer to current table
- this.data = [];
- this.dataUnfiltered = null;
- this.table = null;
- // load metadata
- if (jsonData.metadata) {
- // create columns
- this.columns = [];
- for (var c = 0; c < jsonData.metadata.length; c++) {
- var columndata = jsonData.metadata[c];
- var optionValues = columndata.values ? this._convertOptions(columndata.values) : null;
- var optionValuesForRender = null;
- if (optionValues) {
- // build a fast lookup structure for rendering
- var optionValuesForRender = {};
- for (var optionIndex = 0; optionIndex < optionValues.length; optionIndex++) {
- var optionValue = optionValues[optionIndex];
- if (typeof optionValue.values == 'object') {
- for (var groupOptionIndex = 0; groupOptionIndex < optionValue.values.length; groupOptionIndex++) {
- var groupOptionValue = optionValue.values[groupOptionIndex];
- optionValuesForRender[groupOptionValue.value] = groupOptionValue.label;
- }
- }
- else optionValuesForRender[optionValue.value] = optionValue.label;
- }
- }
- this.columns.push(new Column({
- name: columndata.name,
- label: (columndata.label ? columndata.label : columndata.name),
- datatype: (columndata.datatype ? columndata.datatype : "string"),
- editable: (columndata.editable ? true : false),
- bar: (typeof columndata.bar == 'undefined' ? true : (columndata.bar || false)),
- hidden: (typeof columndata.hidden == 'undefined' ? false : (columndata.hidden ? true : false)),
- optionValuesForRender: optionValuesForRender,
- optionValues: optionValues
- }));
- }
- // process columns
- this.processColumns();
- }
- // load server-side pagination data
- if (jsonData.paginator) {
- this.paginatorAttributes = jsonData.paginator;
- this.pageCount = jsonData.paginator.pagecount;
- this.totalRowCount = jsonData.paginator.totalrowcount;
- this.unfilteredRowCount = jsonData.paginator.unfilteredrowcount;
- }
- // if no row id is provided, we create one since we need one
- var defaultRowId = 1;
- // load content
- if (jsonData.data) for (var i = 0; i < jsonData.data.length; i++)
- {
- var row = jsonData.data[i];
- if (!row.values) continue;
- // row values can be given as an array (same order as columns) or as an object (associative array)
- if (Object.prototype.toString.call(row.values) !== '[object Array]' ) cellValues = row.values;
- else {
- cellValues = {};
- for (var j = 0; j < row.values.length && j < this.columns.length; j++) cellValues[this.columns[j].name] = row.values[j];
- }
- // for each row we keep the orginal index, the id and all other attributes that may have been set in the JSON
- var rowData = { visible: true, originalIndex: i, id: row.id ? row.id : defaultRowId++ };
- for (var attributeName in row) if (attributeName != "id" && attributeName != "values") rowData[attributeName] = row[attributeName];
- // get column values for this rows
- rowData.columns = [];
- for (var c = 0; c < this.columns.length; c++) {
- var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
- rowData.columns.push(this.getTypedValue(c, cellValue));
- }
- // add row data in our model
- this.data.push(rowData);
- }
- return true;
- };
- /**
- * Process columns
- * @private
- */
- EditableGrid.prototype.processColumns = function()
- {
- for (var columnIndex = 0; columnIndex < this.columns.length; columnIndex++) {
- var column = this.columns[columnIndex];
- // set column index and back pointer
- column.columnIndex = columnIndex;
- column.editablegrid = this;
- // parse column type
- this.parseColumnType(column);
- // create suited enum provider if none given
- if (!column.enumProvider) column.enumProvider = column.optionValues ? new EnumProvider() : null;
- // create suited cell renderer if none given
- if (!column.cellRenderer) this._createCellRenderer(column);
- if (!column.headerRenderer) this._createHeaderRenderer(column);
- // create suited cell editor if none given
- if (!column.cellEditor) this._createCellEditor(column);
- if (!column.headerEditor) this._createHeaderEditor(column);
- // add default cell validators based on the column type
- this._addDefaultCellValidators(column);
- }
- };
- /**
- * Parse column type
- * @private
- */
- EditableGrid.prototype.parseColumnType = function(column)
- {
- // reset
- column.unit = null;
- column.precision = -1;
- column.decimal_point = ',';
- column.thousands_separator = '.';
- column.unit_before_number = false;
- column.nansymbol = '';
- // extract precision, unit and number format from type if 6 given
- if (column.datatype.match(/(.*)\((.*),(.*),(.*),(.*),(.*),(.*)\)$/)) {
- column.datatype = RegExp.$1;
- column.unit = RegExp.$2;
- column.precision = parseInt(RegExp.$3);
- column.decimal_point = RegExp.$4;
- column.thousands_separator = RegExp.$5;
- column.unit_before_number = RegExp.$6;
- column.nansymbol = RegExp.$7;
- // trim should be done after fetching RegExp matches beacuse it itself uses a RegExp and causes interferences!
- column.unit = column.unit.trim();
- column.decimal_point = column.decimal_point.trim();
- column.thousands_separator = column.thousands_separator.trim();
- column.unit_before_number = column.unit_before_number.trim() == '1';
- column.nansymbol = column.nansymbol.trim();
- }
- // extract precision, unit and number format from type if 5 given
- else if (column.datatype.match(/(.*)\((.*),(.*),(.*),(.*),(.*)\)$/)) {
- column.datatype = RegExp.$1;
- column.unit = RegExp.$2;
- column.precision = parseInt(RegExp.$3);
- column.decimal_point = RegExp.$4;
- column.thousands_separator = RegExp.$5;
- column.unit_before_number = RegExp.$6;
- // trim should be done after fetching RegExp matches beacuse it itself uses a RegExp and causes interferences!
- column.unit = column.unit.trim();
- column.decimal_point = column.decimal_point.trim();
- column.thousands_separator = column.thousands_separator.trim();
- column.unit_before_number = column.unit_before_number.trim() == '1';
- }
- // extract precision, unit and nansymbol from type if 3 given
- else if (column.datatype.match(/(.*)\((.*),(.*),(.*)\)$/)) {
- column.datatype = RegExp.$1;
- column.unit = RegExp.$2.trim();
- column.precision = parseInt(RegExp.$3);
- column.nansymbol = RegExp.$4.trim();
- }
- // extract precision and unit from type if two given
- else if (column.datatype.match(/(.*)\((.*),(.*)\)$/)) {
- column.datatype = RegExp.$1.trim();
- column.unit = RegExp.$2.trim();
- column.precision = parseInt(RegExp.$3);
- }
- // extract precision or unit from type if any given
- else if (column.datatype.match(/(.*)\((.*)\)$/)) {
- column.datatype = RegExp.$1.trim();
- var unit_or_precision = RegExp.$2.trim();
- if (unit_or_precision.match(/^[0-9]*$/)) column.precision = parseInt(unit_or_precision);
- else column.unit = unit_or_precision;
- }
- if (column.decimal_point == 'comma') column.decimal_point = ',';
- if (column.decimal_point == 'dot') column.decimal_point = '.';
- if (column.thousands_separator == 'comma') column.thousands_separator = ',';
- if (column.thousands_separator == 'dot') column.thousands_separator = '.';
- if (isNaN(column.precision)) column.precision = -1;
- if (column.unit == '') column.unit = null;
- if (column.nansymbol == '') column.nansymbol = null;
- };
- /**
- * Get typed value
- * @private
- */
- EditableGrid.prototype.getTypedValue = function(columnIndex, cellValue)
- {
- if (cellValue === null) return cellValue;
- var colType = this.getColumnType(columnIndex);
- if (colType == 'boolean') cellValue = (cellValue && cellValue != 0 && cellValue != "false") ? true : false;
- if (colType == 'integer') { cellValue = parseInt(cellValue, 10); }
- if (colType == 'double') { cellValue = parseFloat(cellValue); }
- if (colType == 'string') { cellValue = "" + cellValue; }
- return cellValue;
- };
- /**
- * Attach to an existing HTML table.
- * The second parameter can be used to give the column definitions.
- * This parameter is left for compatibility, but is deprecated: you should now use "load" to setup the metadata.
- */
- EditableGrid.prototype.attachToHTMLTable = function(_table, _columns)
- {
- // clear model and pointer to current table
- this.data = [];
- this.dataUnfiltered = null;
- this.table = null;
- // process columns if given
- if (_columns) {
- this.columns = _columns;
- for (var columnIndex = 0; columnIndex < this.columns.length; columnIndex++) this.columns[columnIndex].optionValues = this._convertOptions(this.columns[columnIndex].optionValues); // convert options from old format if needed
- this.processColumns();
- }
- // get pointers to table components
- this.table = typeof _table == 'string' ? _$(_table) : _table ;
- if (!this.table) console.error("Invalid table given: " + _table);
- this.tHead = this.table.tHead;
- this.tBody = this.table.tBodies[0];
- // create table body if needed
- if (!this.tBody) {
- this.tBody = document.createElement("TBODY");
- this.table.insertBefore(this.tBody, this.table.firstChild);
- }
- // create table header if needed
- if (!this.tHead) {
- this.tHead = document.createElement("THEAD");
- this.table.insertBefore(this.tHead, this.tBody);
- }
- // if header is empty use first body row as header
- if (this.tHead.rows.length == 0 && this.tBody.rows.length > 0)
- this.tHead.appendChild(this.tBody.rows[0]);
- // get number of rows in header
- this.nbHeaderRows = this.tHead.rows.length;
- // load header labels
- var rows = this.tHead.rows;
- for (var i = 0; i < rows.length; i++) {
- var cols = rows[i].cells;
- var columnIndexInModel = 0;
- for (var j = 0; j < cols.length && columnIndexInModel < this.columns.length; j++) {
- if (!this.columns[columnIndexInModel].label || this.columns[columnIndexInModel].label == this.columns[columnIndexInModel].name) this.columns[columnIndexInModel].label = cols[j].innerHTML;
- var colspan = parseInt(cols[j].getAttribute("colspan"));
- columnIndexInModel += colspan > 1 ? colspan : 1;
- }
- }
- // load content
- var rows = this.tBody.rows;
- for (var i = 0; i < rows.length; i++) {
- var rowData = [];
- var cols = rows[i].cells;
- for (var j = 0; j < cols.length && j < this.columns.length; j++) rowData.push(this.getTypedValue(j, cols[j].innerHTML));
- this.data.push({ visible: true, originalIndex: i, id: rows[i].id, columns: rowData });
- rows[i].rowId = rows[i].id;
- rows[i].id = this._getRowDOMId(rows[i].id);
- }
- };
- /**
- * Creates a suitable cell renderer for the column
- * @private
- */
- EditableGrid.prototype._createCellRenderer = function(column)
- {
- column.cellRenderer =
- column.enumProvider && column.datatype == "list" && typeof MultiselectCellRenderer != 'undefined' ? new MultiselectCellRenderer() :
- column.enumProvider ? new EnumCellRenderer() :
- column.datatype == "integer" || column.datatype == "double" ? new NumberCellRenderer() :
- column.datatype == "boolean" ? new CheckboxCellRenderer() :
- column.datatype == "email" ? new EmailCellRenderer() :
- column.datatype == "website" || column.datatype == "url" ? new WebsiteCellRenderer() :
- column.datatype == "date" ? new DateCellRenderer() :
- new CellRenderer();
- // give access to the column from the cell renderer
- if (column.cellRenderer) {
- column.cellRenderer.editablegrid = this;
- column.cellRenderer.column = column;
- }
- };
- /**
- * Creates a suitable header cell renderer for the column
- * @private
- */
- EditableGrid.prototype._createHeaderRenderer = function(column)
- {
- column.headerRenderer = (this.enableSort && column.datatype != "html") ? new SortHeaderRenderer(column.name) : new CellRenderer();
- // give access to the column from the header cell renderer
- if (column.headerRenderer) {
- column.headerRenderer.editablegrid = this;
- column.headerRenderer.column = column;
- }
- };
- /**
- * Creates a suitable cell editor for the column
- * @private
- */
- EditableGrid.prototype._createCellEditor = function(column)
- {
- column.cellEditor =
- column.enumProvider && column.datatype == "list" && typeof MultiselectCellEditor != 'undefined' ? new MultiselectCellEditor() :
- column.enumProvider ? new SelectCellEditor() :
- column.datatype == "integer" || column.datatype == "double" ? new NumberCellEditor(column.datatype) :
- column.datatype == "boolean" ? null :
- column.datatype == "email" ? new TextCellEditor(column.precision) :
- column.datatype == "website" || column.datatype == "url" ? new TextCellEditor(column.precision) :
- column.datatype == "date" ? (typeof jQuery == 'undefined' || typeof jQuery.datepicker == 'undefined' ? new TextCellEditor(column.precision, 10) : new DateCellEditor({ fieldSize: column.precision, maxLength: 10 })) :
- new TextCellEditor(column.precision);
- // give access to the column from the cell editor
- if (column.cellEditor) {
- column.cellEditor.editablegrid = this;
- column.cellEditor.column = column;
- }
- };
- /**
- * Creates a suitable header cell editor for the column
- * @private
- */
- EditableGrid.prototype._createHeaderEditor = function(column)
- {
- column.headerEditor = new TextCellEditor();
- // give access to the column from the cell editor
- if (column.headerEditor) {
- column.headerEditor.editablegrid = this;
- column.headerEditor.column = column;
- }
- };
- /**
- * Returns the number of rows
- */
- EditableGrid.prototype.getRowCount = function()
- {
- return this.data.length;
- };
- /**
- * Returns the number of rows, not taking the filter into account if any
- */
- EditableGrid.prototype.getUnfilteredRowCount = function()
- {
- // given if server-side filtering is involved
- if (this.unfilteredRowCount > 0) return this.unfilteredRowCount;
- var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
- return _data.length;
- };
- /**
- * Returns the number of rows in all pages
- */
- EditableGrid.prototype.getTotalRowCount = function()
- {
- // different from getRowCount only is server-side pagination is involved
- if (this.totalRowCount > 0) return this.totalRowCount;
- return this.getRowCount();
- };
- /**
- * Returns the number of columns
- */
- EditableGrid.prototype.getColumnCount = function()
- {
- return this.columns.length;
- };
- /**
- * Returns true if the column exists
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.hasColumn = function(columnIndexOrName)
- {
- return this.getColumnIndex(columnIndexOrName) >= 0;
- };
- /**
- * Returns the column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumn = function(columnIndexOrName)
- {
- var colIndex = this.getColumnIndex(columnIndexOrName);
- if (colIndex < 0) { console.error("[getColumn] Column not found with index or name " + columnIndexOrName); return null; }
- return this.columns[colIndex];
- };
- /**
- * Returns the name of a column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnName = function(columnIndexOrName)
- {
- return this.getColumn(columnIndexOrName).name;
- };
- /**
- * Returns the label of a column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnLabel = function(columnIndexOrName)
- {
- return this.getColumn(columnIndexOrName).label;
- };
- /**
- * Returns the type of a column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnType = function(columnIndexOrName)
- {
- return this.getColumn(columnIndexOrName).datatype;
- };
- /**
- * Returns the unit of a column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnUnit = function(columnIndexOrName)
- {
- return this.getColumn(columnIndexOrName).unit;
- };
- /**
- * Returns the precision of a column
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnPrecision = function(columnIndexOrName)
- {
- return this.getColumn(columnIndexOrName).precision;
- };
- /**
- * Returns true if the column is to be displayed in a bar chart
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.isColumnBar = function(columnIndexOrName)
- {
- var column = this.getColumn(columnIndexOrName);
- return (column.bar && column.isNumerical());
- };
- /**
- * Returns the stack of a column (for stacked bar charts)
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnStack = function(columnIndexOrName)
- {
- var column = this.getColumn(columnIndexOrName);
- return column.isNumerical() ? column.bar : '';
- };
- /**
- * Returns true if the column is numerical (double or integer)
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.isColumnNumerical = function(columnIndexOrName)
- {
- var column = this.getColumn(columnIndexOrName);
- return column.isNumerical();;
- };
- /**
- * Returns the value at the specified index
- * @param {Integer} rowIndex
- * @param {Integer} columnIndex
- */
- EditableGrid.prototype.getValueAt = function(rowIndex, columnIndex)
- {
- // check and get column
- if (columnIndex < 0 || columnIndex >= this.columns.length) { console.error("[getValueAt] Invalid column index " + columnIndex); return null; }
- var column = this.columns[columnIndex];
- // get value in model
- if (rowIndex < 0) return column.label;
- if (typeof this.data[rowIndex] == 'undefined') { console.error("[getValueAt] Invalid row index " + rowIndex); return null; }
- var rowData = this.data[rowIndex]['columns'];
- return rowData ? rowData[columnIndex] : null;
- };
- /**
- * Returns the display value (used for sorting and filtering) at the specified index
- * @param {Integer} rowIndex
- * @param {Integer} columnIndex
- */
- EditableGrid.prototype.getDisplayValueAt = function(rowIndex, columnIndex)
- {
- // use renderer to get the value that must be used for sorting and filtering
- var value = this.getValueAt(rowIndex, columnIndex);
- var renderer = rowIndex < 0 ? this.columns[columnIndex].headerRenderer : this.columns[columnIndex].cellRenderer;
- return renderer.getDisplayValue(rowIndex, value);
- };
- /**
- * Sets the value at the specified index
- * @param {Integer} rowIndex
- * @param {Integer} columnIndex
- * @param {Object} value
- * @param {Boolean} render
- */
- EditableGrid.prototype.setValueAt = function(rowIndex, columnIndex, value, render)
- {
- if (typeof render == "undefined") render = true;
- var previousValue = null;;
- // check and get column
- if (columnIndex < 0 || columnIndex >= this.columns.length) { console.error("[setValueAt] Invalid column index " + columnIndex); return null; }
- var column = this.columns[columnIndex];
- // set new value in model
- if (rowIndex < 0) {
- previousValue = column.label;
- column.label = value;
- }
- else {
- if (typeof this.data[rowIndex] == 'undefined') {
- console.error('Invalid rowindex ' + rowIndex);
- return null;
- }
- var rowData = this.data[rowIndex]['columns'];
- previousValue = rowData[columnIndex];
- if (rowData) rowData[columnIndex] = this.getTypedValue(columnIndex, value);
- }
- // render new value
- if (render) {
- var renderer = rowIndex < 0 ? column.headerRenderer : column.cellRenderer;
- var cell = this.getCell(rowIndex, columnIndex);
- if (cell) renderer._render(rowIndex, columnIndex, cell, value);
- }
- return previousValue;
- };
- /**
- * Find column index from its name
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.getColumnIndex = function(columnIndexOrName)
- {
- if (typeof columnIndexOrName == "undefined" || columnIndexOrName === "") return -1;
- // TODO: problem because the name of a column could be a valid index, and we cannot make the distinction here!
- // if columnIndexOrName is a number which is a valid index return it
- if (!isNaN(columnIndexOrName) && columnIndexOrName >= 0 && columnIndexOrName < this.columns.length) return columnIndexOrName;
- // otherwise search for the name
- for (var c = 0; c < this.columns.length; c++) if (this.columns[c].name == columnIndexOrName) return c;
- return -1;
- };
- /**
- * Get HTML row object at given index
- * @param {Integer} index of the row
- */
- EditableGrid.prototype.getRow = function(rowIndex)
- {
- if (rowIndex < 0) return this.tHead.rows[rowIndex + this.nbHeaderRows];
- if (typeof this.data[rowIndex] == 'undefined') { console.error("[getRow] Invalid row index " + rowIndex); return null; }
- return _$(this._getRowDOMId(this.data[rowIndex].id));
- };
- /**
- * Get row id for given row index
- * @param {Integer} index of the row
- */
- EditableGrid.prototype.getRowId = function(rowIndex)
- {
- return (rowIndex < 0 || rowIndex >= this.data.length) ? null : this.data[rowIndex]['id'];
- };
- /**
- * Get index of row (in filtered data) with given id
- * @param {Integer} rowId or HTML row object
- */
- EditableGrid.prototype.getRowIndex = function(rowId)
- {
- rowId = typeof rowId == 'object' ? rowId.rowId : rowId;
- for (var rowIndex = 0; rowIndex < this.data.length; rowIndex++) if (this.data[rowIndex].id == rowId) return rowIndex;
- return -1;
- };
- /**
- * Get custom row attribute specified in XML
- * @param {Integer} index of the row
- * @param {String} name of the attribute
- */
- EditableGrid.prototype.getRowAttribute = function(rowIndex, attributeName)
- {
- if (typeof this.data[rowIndex] == 'undefined') {
- console.error('Invalid rowindex ' + rowIndex);
- return null;
- }
- return this.data[rowIndex][attributeName];
- };
- /**
- * Set custom row attribute
- * @param {Integer} index of the row
- * @param {String} name of the attribute
- * @param value of the attribute
- */
- EditableGrid.prototype.setRowAttribute = function(rowIndex, attributeName, attributeValue)
- {
- this.data[rowIndex][attributeName] = attributeValue;
- };
- /**
- * Get Id of row in HTML DOM
- * @private
- */
- EditableGrid.prototype._getRowDOMId = function(rowId)
- {
- return this.currentContainerid != null ? this.name + "_" + rowId : rowId;
- };
- /**
- * Remove row with given id
- * Deprecated: use remove(rowIndex) instead
- * @param {Integer} rowId
- */
- EditableGrid.prototype.removeRow = function(rowId)
- {
- return this.remove(this.getRowIndex(rowId));
- };
- /**
- * Remove row at given index
- * @param {Integer} rowIndex
- */
- EditableGrid.prototype.remove = function(rowIndex)
- {
- var rowId = this.data[rowIndex].id;
- var originalIndex = this.data[rowIndex].originalIndex;
- var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
- // delete row from DOM (needed for attach mode)
- var tr = _$(this._getRowDOMId(rowId));
- if (tr != null) this.tBody.removeChild(tr);
- // update originalRowIndex
- for (var r = 0; r < _data.length; r++) if (_data[r].originalIndex >= originalIndex) _data[r].originalIndex--;
- // delete row from data
- this.data.splice(rowIndex, 1);
- if (this.dataUnfiltered != null) for (var r = 0; r < this.dataUnfiltered.length; r++) if (this.dataUnfiltered[r].id == rowId) { this.dataUnfiltered.splice(r, 1); break; }
- // callback
- this.rowRemoved(rowIndex,rowId);
- // refresh grid
- this.refreshGrid();
- };
- /**
- * Return an associative array (column name => value) of values in row with given index
- * @param {Integer} rowIndex
- */
- EditableGrid.prototype.getRowValues = function(rowIndex)
- {
- var rowValues = {};
- for (var columnIndex = 0; columnIndex < this.getColumnCount(); columnIndex++) {
- rowValues[this.getColumnName(columnIndex)] = this.getValueAt(rowIndex, columnIndex);
- }
- return rowValues;
- };
- /**
- * Append row with given id and data
- * @param {Integer} rowId id of new row
- * @param {Integer} columns
- * @param {Boolean} dontSort
- */
- EditableGrid.prototype.append = function(rowId, cellValues, rowAttributes, dontSort)
- {
- return this.insertAfter(this.data.length - 1, rowId, cellValues, rowAttributes, dontSort);
- };
- /**
- * Append row with given id and data
- * Deprecated: use append instead
- * @param {Integer} rowId id of new row
- * @param {Integer} columns
- * @param {Boolean} dontSort
- */
- EditableGrid.prototype.addRow = function(rowId, cellValues, rowAttributes, dontSort)
- {
- return this.append(rowId, cellValues, rowAttributes, dontSort);
- };
- /**
- * Insert row with given id and data at given location
- * We know rowIndex is valid, unless the table is empty
- * @private
- */
- EditableGrid.prototype._insert = function(rowIndex, offset, rowId, cellValues, rowAttributes, dontSort)
- {
- var originalRowId = null;
- var originalIndex = 0;
- var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
- if (typeof this.data[rowIndex] != "undefined") {
- originalRowId = this.data[rowIndex].id;
- originalIndex = this.data[rowIndex].originalIndex + offset;
- }
- // append row in DOM (needed for attach mode)
- if (this.currentContainerid == null) {
- var tr = this.tBody.insertRow(rowIndex + offset);
- tr.rowId = rowId;
- tr.id = this._getRowDOMId(rowId);
- for (var c = 0; c < this.columns.length; c++) tr.insertCell(c);
- }
- // build data for new row
- var rowData = { visible: true, originalIndex: originalIndex, id: rowId };
- if (rowAttributes) for (var attributeName in rowAttributes) rowData[attributeName] = rowAttributes[attributeName];
- rowData.columns = [];
- for (var c = 0; c < this.columns.length; c++) {
- var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
- rowData.columns.push(this.getTypedValue(c, cellValue));
- }
- // update originalRowIndex
- for (var r = 0; r < _data.length; r++) if (_data[r].originalIndex >= originalIndex) _data[r].originalIndex++;
- // append row in data
- this.data.splice(rowIndex + offset, 0, rowData);
- if (this.dataUnfiltered != null) {
- if (originalRowId === null) this.dataUnfiltered.splice(rowIndex + offset, 0, rowData);
- else for (var r = 0; r < this.dataUnfiltered.length; r++) if (this.dataUnfiltered[r].id == originalRowId) { this.dataUnfiltered.splice(r + offset, 0, rowData); break; }
- }
- // refresh grid
- this.refreshGrid();
- // sort and filter table
- if (!dontSort) this.sort();
- this.filter();
- };
- /**
- * Insert row with given id and data before given row index
- * @param {Integer} rowIndex index of row before which to insert new row
- * @param {Integer} rowId id of new row
- * @param {Integer} columns
- * @param {Boolean} dontSort
- */
- EditableGrid.prototype.insert = function(rowIndex, rowId, cellValues, rowAttributes, dontSort)
- {
- if (rowIndex < 0) rowIndex = 0;
- if (rowIndex >= this.data.length && this.data.length > 0) return this.insertAfter(this.data.length - 1, rowId, cellValues, rowAttributes, dontSort);
- return this._insert(rowIndex, 0, rowId, cellValues, rowAttributes, dontSort);
- };
- /**
- * Insert row with given id and data after given row index
- * @param {Integer} rowIndex index of row after which to insert new row
- * @param {Integer} rowId id of new row
- * @param {Integer} columns
- * @param {Boolean} dontSort
- */
- EditableGrid.prototype.insertAfter = function(rowIndex, rowId, cellValues, rowAttributes, dontSort)
- {
- if (rowIndex < 0) return this.insert(0, rowId, cellValues, rowAttributes, dontSort);
- if (rowIndex >= this.data.length) rowIndex = this.data.length - 1;
- return this._insert(rowIndex, 1, rowId, cellValues, rowAttributes, dontSort);
- };
- /**
- * Sets the column header cell renderer for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {CellRenderer} cellRenderer
- */
- EditableGrid.prototype.setHeaderRenderer = function(columnIndexOrName, cellRenderer)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[setHeaderRenderer] Invalid column: " + columnIndexOrName);
- else {
- var column = this.columns[columnIndex];
- column.headerRenderer = (this.enableSort && column.datatype != "html") ? new SortHeaderRenderer(column.name, cellRenderer) : cellRenderer;
- // give access to the column from the cell renderer
- if (cellRenderer) {
- if (this.enableSort && column.datatype != "html") {
- column.headerRenderer.editablegrid = this;
- column.headerRenderer.column = column;
- }
- cellRenderer.editablegrid = this;
- cellRenderer.column = column;
- }
- }
- };
- /**
- * Sets the cell renderer for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {CellRenderer} cellRenderer
- */
- EditableGrid.prototype.setCellRenderer = function(columnIndexOrName, cellRenderer)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[setCellRenderer] Invalid column: " + columnIndexOrName);
- else {
- var column = this.columns[columnIndex];
- column.cellRenderer = cellRenderer;
- // give access to the column from the cell renderer
- if (cellRenderer) {
- cellRenderer.editablegrid = this;
- cellRenderer.column = column;
- }
- }
- };
- /**
- * Sets the cell editor for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {CellEditor} cellEditor
- */
- EditableGrid.prototype.setCellEditor = function(columnIndexOrName, cellEditor)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[setCellEditor] Invalid column: " + columnIndexOrName);
- else {
- var column = this.columns[columnIndex];
- column.cellEditor = cellEditor;
- // give access to the column from the cell editor
- if (cellEditor) {
- cellEditor.editablegrid = this;
- cellEditor.column = column;
- }
- }
- };
- /**
- * Sets the header cell editor for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {CellEditor} cellEditor
- */
- EditableGrid.prototype.setHeaderEditor = function(columnIndexOrName, cellEditor)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[setHeaderEditor] Invalid column: " + columnIndexOrName);
- else {
- var column = this.columns[columnIndex];
- column.headerEditor = cellEditor;
- // give access to the column from the cell editor
- if (cellEditor) {
- cellEditor.editablegrid = this;
- cellEditor.column = column;
- }
- }
- };
- /**
- * Sets the enum provider for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {EnumProvider} enumProvider
- */
- EditableGrid.prototype.setEnumProvider = function(columnIndexOrName, enumProvider)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[setEnumProvider] Invalid column: " + columnIndexOrName);
- else {
- var hadProviderAlready = this.columns[columnIndex].enumProvider != null;
- this.columns[columnIndex].enumProvider = enumProvider;
- // if needed, we recreate the cell renderer and editor for this column
- // if the column had an enum provider already, the render/editor previously created by default is ok already
- // ... and we don't want to erase a custom renderer/editor that may have been set before calling setEnumProvider
- if (!hadProviderAlready) {
- this._createCellRenderer(this.columns[columnIndex]);
- this._createCellEditor(this.columns[columnIndex]);
- }
- }
- };
- /**
- * Clear all cell validators for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.clearCellValidators = function(columnIndexOrName)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[clearCellValidators] Invalid column: " + columnIndexOrName);
- else this.columns[columnIndex].cellValidators = [];
- };
- /**
- * Adds default cell validators for the specified column index (according to the column type)
- * @param {Object} columnIndexOrName index or name of the column
- */
- EditableGrid.prototype.addDefaultCellValidators = function(columnIndexOrName)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[addDefaultCellValidators] Invalid column: " + columnIndexOrName);
- return this._addDefaultCellValidators(this.columns[columnIndex]);
- };
- /**
- * Adds default cell validators for the specified column
- * @private
- */
- EditableGrid.prototype._addDefaultCellValidators = function(column)
- {
- if (column.datatype == "integer" || column.datatype == "double") column.cellValidators.push(new NumberCellValidator(column.datatype));
- else if (column.datatype == "email") column.cellValidators.push(new EmailCellValidator());
- else if (column.datatype == "website" || column.datatype == "url") column.cellValidators.push(new WebsiteCellValidator());
- else if (column.datatype == "date") column.cellValidators.push(new DateCellValidator(this));
- };
- /**
- * Adds a cell validator for the specified column index
- * @param {Object} columnIndexOrName index or name of the column
- * @param {CellValidator} cellValidator
- */
- EditableGrid.prototype.addCellValidator = function(columnIndexOrName, cellValidator)
- {
- var columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) console.error("[addCellValidator] Invalid column: " + columnIndexOrName);
- else this.columns[columnIndex].cellValidators.push(cellValidator);
- };
- /**
- * Sets the table caption: set as null to remove
- * @param columnIndexOrName
- * @param caption
- * @return
- */
- EditableGrid.prototype.setCaption = function(caption)
- {
- this.caption = caption;
- };
- /**
- * Get cell element at given row and column
- */
- EditableGrid.prototype.getCell = function(rowIndex, columnIndex)
- {
- var row = this.getRow(rowIndex);
- if (row == null) { console.error("[getCell] Invalid row index " + rowIndex); return null; }
- return row.cells[columnIndex];
- };
- /**
- * Get cell X position relative to the first non static offset parent
- * @private
- */
- EditableGrid.prototype.getCellX = function(oElement)
- {
- var iReturnValue = 0;
- while (oElement != null && this.isStatic(oElement)) try {
- iReturnValue += oElement.offsetLeft;
- oElement = oElement.offsetParent;
- } catch(err) { oElement = null; }
- return iReturnValue;
- };
- /**
- * Get cell Y position relative to the first non static offset parent
- * @private
- */
- EditableGrid.prototype.getCellY = function(oElement)
- {
- var iReturnValue = 0;
- while (oElement != null && this.isStatic(oElement)) try {
- iReturnValue += oElement.offsetTop;
- oElement = oElement.offsetParent;
- } catch(err) { oElement = null; }
- return iReturnValue;
- };
- /**
- * Get X scroll offset relative to the first non static offset parent
- * @private
- */
- EditableGrid.prototype.getScrollXOffset = function(oElement)
- {
- var iReturnValue = 0;
- while (oElement != null && typeof oElement.scrollLeft != 'undefined' && this.isStatic(oElement) && oElement != document.body) try {
- iReturnValue += parseInt(oElement.scrollLeft);
- oElement = oElement.parentNode;
- } catch(err) { oElement = null; }
- return iReturnValue;
- };
- /**
- * Get Y scroll offset relative to the first non static offset parent
- * @private
- */
- EditableGrid.prototype.getScrollYOffset = function(oElement)
- {
- var iReturnValue = 0;
- while (oElement != null && typeof oElement.scrollTop != 'undefined' && this.isStatic(oElement) && oElement != document.body) try {
- iReturnValue += parseInt(oElement.scrollTop);
- oElement = oElement.parentNode;
- } catch(err) { oElement = null; }
- return iReturnValue;
- };
- /**
- * Private
- * @param containerid
- * @param className
- * @param tableid
- * @return
- */
- EditableGrid.prototype._rendergrid = function(containerid, className, tableid)
- {
- with (this) {
- lastSelectedRowIndex = -1;
- _currentPageIndex = getCurrentPageIndex();
- // if we are already attached to an existing table, just update the cell contents
- if (typeof table != "undefined" && table != null) {
- var _data = dataUnfiltered == null ? data : dataUnfiltered;
- // render headers
- _renderHeaders();
- // render content
- var rows = tBody.rows;
- var skipped = 0;
- var displayed = 0;
- var rowIndex = 0;
- for (var i = 0; i < rows.length; i++) {
- // filtering and pagination in attach mode means hiding rows
- if (!_data[i].visible || (pageSize > 0 && displayed >= pageSize)) {
- if (rows[i].style.display != 'none') {
- rows[i].style.display = 'none';
- rows[i].hidden_by_editablegrid = true;
- }
- }
- else {
- if (skipped < pageSize * _currentPageIndex) {
- skipped++;
- if (rows[i].style.display != 'none') {
- rows[i].style.display = 'none';
- rows[i].hidden_by_editablegrid = true;
- }
- }
- else {
- displayed++;
- var cols = rows[i].cells;
- if (typeof rows[i].hidden_by_editablegrid != 'undefined' && rows[i].hidden_by_editablegrid) {
- rows[i].style.display = '';
- rows[i].hidden_by_editablegrid = false;
- }
- rows[i].rowId = getRowId(rowIndex);
- rows[i].id = _getRowDOMId(rows[i].rowId);
- for (var j = 0; j < cols.length && j < columns.length; j++)
- if (columns[j].renderable) columns[j].cellRenderer._render(rowIndex, j, cols[j], getValueAt(rowIndex,j));
- }
- rowIndex++;
- }
- }
- // attach handler on click or double click
- table.editablegrid = this;
- if (doubleclick) table.ondblclick = function(e) { this.editablegrid.mouseClicked(e); };
- else table.onclick = function(e) { this.editablegrid.mouseClicked(e); };
- }
- // we must render a whole new table
- else {
- if (!containerid) return console.warn("The container ID not specified (renderGrid not called yet ?)");
- if (!_$(containerid)) return console.error("Unable to get element [" + containerid + "]");
- currentContainerid = containerid;
- currentClassName = className;
- currentTableid = tableid;
- var startRowIndex = 0;
- var endRowIndex = getRowCount();
- // paginate if required
- if (pageSize > 0) {
- startRowIndex = _currentPageIndex * pageSize;
- endRowIndex = Math.min(getRowCount(), startRowIndex + pageSize);
- }
- // create editablegrid table and add it to our container
- this.table = document.createElement("table");
- table.className = className || "editablegrid";
- if (typeof tableid != "undefined") table.id = tableid;
- while (_$(containerid).hasChildNodes()) _$(containerid).removeChild(_$(containerid).firstChild);
- _$(containerid).appendChild(table);
- // create header
- if (caption) {
- var captionElement = document.createElement("CAPTION");
- captionElement.innerHTML = this.caption;
- table.appendChild(captionElement);
- }
- this.tHead = document.createElement("THEAD");
- table.appendChild(tHead);
- var trHeader = tHead.insertRow(0);
- var columnCount = getColumnCount();
- for (var c = 0; c < columnCount; c++) {
- var headerCell = document.createElement("TH");
- var td = trHeader.appendChild(headerCell);
- columns[c].headerRenderer._render(-1, c, td, columns[c].label);
- }
- // create body and rows
- this.tBody = document.createElement("TBODY");
- table.appendChild(tBody);
- var insertRowIndex = 0;
- for (var i = startRowIndex; i < endRowIndex; i++) {
- var tr = tBody.insertRow(insertRowIndex++);
- tr.rowId = data[i]['id'];
- tr.id = this._getRowDOMId(data[i]['id']);
- for (j = 0; j < columnCount; j++) {
- // create cell and render its content
- var td = tr.insertCell(j);
- columns[j].cellRenderer._render(i, j, td, getValueAt(i,j));
- }
- }
- // attach handler on click or double click
- _$(containerid).editablegrid = this;
- if (doubleclick) _$(containerid).ondblclick = function(e) { this.editablegrid.mouseClicked(e); };
- else _$(containerid).onclick = function(e) { this.editablegrid.mouseClicked(e); };
- }
- // callback
- tableRendered(containerid, className, tableid);
- }
- };
- /**
- * Renders the grid as an HTML table in the document
- * @param {String} containerid
- * id of the div in which you wish to render the HTML table (this parameter is ignored if you used attachToHTMLTable)
- * @param {String} className
- * CSS class name to be applied to the table (this parameter is ignored if you used attachToHTMLTable)
- * @param {String} tableid
- * ID to give to the table (this parameter is ignored if you used attachToHTMLTable)
- * @see EditableGrid#attachToHTMLTable
- * @see EditableGrid#loadXML
- */
- EditableGrid.prototype.renderGrid = function(containerid, className, tableid)
- {
- // actually render grid
- this._rendergrid(containerid, className, tableid);
- // if client side: sort and filter
- if (!this.serverSide) {
- this.sort() ;
- this.filter();
- }
- };
- /**
- * Refreshes the grid
- * @return
- */
- EditableGrid.prototype.refreshGrid = function()
- {
- if (this.currentContainerid != null) this.table = null; // if we are not in "attach mode", clear table to force a full re-render
- this._rendergrid(this.currentContainerid, this.currentClassName, this.currentTableid);
- };
- /**
- * Render all column headers
- * @private
- */
- EditableGrid.prototype._renderHeaders = function()
- {
- with (this) {
- var rows = tHead.rows;
- for (var i = 0; i < 1 /*rows.length*/; i++) {
- var rowData = [];
- var cols = rows[i].cells;
- var columnIndexInModel = 0;
- for (var j = 0; j < cols.length && columnIndexInModel < columns.length; j++) {
- columns[columnIndexInModel].headerRenderer._render(-1, columnIndexInModel, cols[j], columns[columnIndexInModel].label);
- var colspan = parseInt(cols[j].getAttribute("colspan"));
- columnIndexInModel += colspan > 1 ? colspan : 1;
- }
- }
- }
- };
- /**
- * Mouse click handler
- * @param {Object} e
- * @private
- */
- EditableGrid.prototype.mouseClicked = function(e)
- {
- e = e || window.event;
- with (this) {
- // get row and column index from the clicked cell
- var target = e.target || e.srcElement;
- // go up parents to find a cell or a link under the clicked position
- while (target) if (target.tagName == "A" || target.tagName == "TD" || target.tagName == "TH") break; else target = target.parentNode;
- if (!target || !target.parentNode || !target.parentNode.parentNode || (target.parentNode.parentNode.tagName != "TBODY" && target.parentNode.parentNode.tagName != "THEAD") || target.isEditing) return;
- // don't handle clicks on links
- if (target.tagName == "A") return;
- // get cell position in table
- var rowIndex = getRowIndex(target.parentNode);
- var columnIndex = target.cellIndex;
- var column = columns[columnIndex];
- if (column) {
- // if another row has been selected: callback
- if (rowIndex > -1 && rowIndex != lastSelectedRowIndex) {
- rowSelected(lastSelectedRowIndex, rowIndex);
- lastSelectedRowIndex = rowIndex;
- }
- // edit current cell value
- if (!column.editable) { readonlyWarning(column); }
- else {
- if (rowIndex < 0) {
- if (column.headerEditor && isHeaderEditable(rowIndex, columnIndex))
- column.headerEditor.edit(rowIndex, columnIndex, target, column.label);
- }
- else if (column.cellEditor && isEditable(rowIndex, columnIndex))
- column.cellEditor.edit(rowIndex, columnIndex, target, getValueAt(rowIndex, columnIndex));
- }
- }
- }
- };
- /**
- * Moves columns around (added by JRE)
- * @param {array[strings]} an array of class names of the headers
- * returns boolean based on success
- */
- EditableGrid.prototype.sortColumns = function(headerArray){
- with (this){
- newColumns = [];
- newColumnIndeces = [];
- for (var i = 0; i < headerArray.length; i++) {
- columnIndex = this.getColumnIndex(headerArray[i]);
- if(columnIndex == -1){//a column could not be found. can't reorder anything or data may be lost
- console.error("[sortColumns] Invalid column: " + columnIndex);
- return false;
- }
- newColumns[i] = this.columns[columnIndex];
- newColumnIndeces[i] = columnIndex;
- }
- //rearrance headers
- this.columns = newColumns;
- //need to rearrange all of the data elements as well
- for (var i = 0; i < this.data.length; i++) {
- var myData = this.data[i];
- var myDataColumns = myData.columns;
- var newDataColumns = [];
- for (var j = 0; j < myDataColumns.length; j++) {
- newIndex = newColumnIndeces[j];
- newDataColumns[j] = myDataColumns[newIndex];
- }
- this.data[i].columns = newDataColumns;
- }
- return true;
- }
- }
- /**
- * Sort on a column
- * @param {Object} columnIndexOrName index or name of the column
- * @param {Boolean} descending
- */
- EditableGrid.prototype.sort = function(columnIndexOrName, descending, backOnFirstPage)
- {
- with (this) {
- if (typeof columnIndexOrName == 'undefined' && sortedColumnName === -1) {
- // avoid a double render, but still send the expected callback
- tableSorted(-1, sortDescending);
- return true;
- }
- if (typeof columnIndexOrName == 'undefined') columnIndexOrName = sortedColumnName;
- if (typeof descending == 'undefined') descending = sortDescending;
- localset('sortColumnIndexOrName', columnIndexOrName);
- localset('sortDescending', descending);
- // if sorting is done on server-side, we are done here
- if (serverSide) return backOnFirstPage ? setPageIndex(0) : refreshGrid();
- var columnIndex = columnIndexOrName;
- if (parseInt(columnIndex, 10) !== -1) {
- columnIndex = this.getColumnIndex(columnIndexOrName);
- if (columnIndex < 0) {
- console.error("[sort] Invalid column: " + columnIndexOrName);
- return false;
- }
- }
- if (!enableSort) {
- tableSorted(columnIndex, descending);
- return;
- }
- // work on unfiltered data
- var filterActive = dataUnfiltered != null;
- if (filterActive) data = dataUnfiltered;
- var type = columnIndex < 0 ? "" : getColumnType(columnIndex);
- var row_array = [];
- var rowCount = getRowCount();
- for (var i = 0; i < rowCount - (ignoreLastRow ? 1 : 0); i++) row_array.push([columnIndex < 0 ? null : getDisplayValueAt(i, columnIndex), i, data[i].originalIndex]);
- var sort_function = type == "integer" || type == "double" ? sort_numeric : type == "boolean" ? sort_boolean : type == "date" ? sort_date : sort_alpha;
- row_array.sort(columnIndex < 0 ? unsort : sort_stable(sort_function, descending));
- if (ignoreLastRow) row_array.push([columnIndex < 0 ? null : getDisplayValueAt(rowCount - 1, columnIndex), rowCount - 1, data[rowCount - 1].originalIndex]);
- // rebuild data using the new order
- var _data = data;
- data = [];
- for (var i = 0; i < row_array.length; i++) data.push(_data[row_array[i][1]]);
- delete row_array;
- if (filterActive) {
- // keep only visible rows in data
- dataUnfiltered = data;
- data = [];
- for (var r = 0; r < rowCount; r++) if (dataUnfiltered[r].visible) data.push(dataUnfiltered[r]);
- }
- // refresh grid (back on first page if sort column has changed) and callback
- if (backOnFirstPage) setPageIndex(0); else refreshGrid();
- tableSorted(columnIndex, descending);
- return true;
- }
- };
- /**
- * Filter the content of the table
- * @param {String} filterString String string used to filter: all words must be found in the row
- * @param {Array} cols Columns to sort. If cols is not specified, the filter will be done on all columns
- */
- EditableGrid.prototype.filter = function(filterString, cols)
- {
- with (this) {
- if (typeof filterString != 'undefined') {
- this.currentFilter = filterString;
- this.localset('filter', filterString);
- }
- // if filtering is done on server-side, we are done here
- if (serverSide) return setPageIndex(0);
- // un-filter if no or empty filter set
- if (currentFilter == null || currentFilter == "") {
- if (dataUnfiltered != null) {
- data = dataUnfiltered;
- dataUnfiltered = null;
- for (var r = 0; r < getRowCount(); r++) data[r].visible = true;
- setPageIndex(0);
- tableFiltered();
- }
- return;
- }
- var words = currentFilter.toLowerCase().split(" ");
- // work on unfiltered data
- if (dataUnfiltered != null) data = dataUnfiltered;
- var rowCount = getRowCount();
- var columnCount = typeof cols != 'undefined' ? cols.length : getColumnCount();
- for (var r = 0; r < rowCount; r++) {
- var row = data[r];
- row.visible = true;
- var rowContent = "";
- // add column values
- for (var c = 0; c < columnCount; c++) {
- if (getColumnType(c) == 'boolean') continue;
- var displayValue = getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
- var value = getValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
- rowContent += displayValue + " " + (displayValue == value ? "" : value + " ");
- }
- // add attribute values
- for (var attributeName in row) {
- if (attributeName != "visible" && attributeName != "originalIndex" && attributeName != "columns") rowContent += row[attributeName];
- }
- // if row contents do not match one word in the filter, hide the row
- for (var i = 0; i < words.length; i++) {
- var word = words[i];
- var match = false;
- // a word starting with "!" means that we want a NON match
- var invertMatch = word.startsWith("!");
- if (invertMatch) word = word.substr(1);
- // if word is of the form "colname/attributename=value" or "colname/attributename!=value", only this column/attribute is used
- var colindex = -1;
- var attributeName = null;
- if (word.contains("!=")) {
- var parts = word.split("!=");
- colindex = getColumnIndex(parts[0]);
- if (colindex >= 0) {
- word = parts[1];
- invertMatch = !invertMatch;
- }
- else if (typeof row[parts[0]] != 'undefined') {
- attributeName = parts[0];
- word = parts[1];
- invertMatch = !invertMatch;
- }
- }
- else if (word.contains("=")) {
- var parts = word.split("=");
- colindex = getColumnIndex(parts[0]);
- if (colindex >= 0) word = parts[1];
- else if (typeof row[parts[0]] != 'undefined') {
- attributeName = parts[0];
- word = parts[1];
- }
- }
- // a word ending with "!" means that a column must match this word exactly
- if (!word.endsWith("!")) {
- if (colindex >= 0) match = (getValueAt(r, colindex) + ' ' + getDisplayValueAt(r, colindex)).trim().toLowerCase().indexOf(word) >= 0;
- else if (attributeName !== null) match = (''+getRowAttribute(r, attributeName)).trim().toLowerCase().indexOf(word) >= 0;
- else match = rowContent.toLowerCase().indexOf(word) >= 0;
- }
- else {
- word = word.substr(0, word.length - 1);
- if (colindex >= 0) match = (''+getDisplayValueAt(r, colindex)).trim().toLowerCase() == word || (''+getValueAt(r, colindex)).trim().toLowerCase() == word;
- else if (attributeName !== null) match = (''+getRowAttribute(r, attributeName)).trim().toLowerCase() == word;
- else for (var c = 0; c < columnCount; c++) {
- if (getColumnType(typeof cols != 'undefined' ? cols[c] : c) == 'boolean') continue;
- if ((''+getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word || (''+getValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word) match = true;
- }
- }
- if (invertMatch ? match : !match) {
- data[r].visible = false;
- break;
- }
- }
- }
- // keep only visible rows in data
- dataUnfiltered = data;
- data = [];
- for (var r = 0; r < rowCount; r++) if (dataUnfiltered[r].visible) data.push(dataUnfiltered[r]);
- // refresh grid (back on first page) and callback
- setPageIndex(0);
- tableFiltered();
- }
- };
- /**
- * Sets the page size(pageSize of 0 means no pagination)
- * @param {Integer} pageSize Integer page size
- */
- EditableGrid.prototype.setPageSize = function(pageSize)
- {
- this.pageSize = parseInt(pageSize);
- if (isNaN(this.pageSize)) this.pageSize = 0;
- this.currentPageIndex = 0;
- this.refreshGrid();
- };
- /**
- * Returns the number of pages according to the current page size
- */
- EditableGrid.prototype.getPageCount = function()
- {
- if (this.getRowCount() == 0) return 0;
- if (this.pageCount > 0) return this.pageCount; // server side pagination
- else if (this.pageSize <= 0) { console.error("getPageCount: no or invalid page size defined (" + this.pageSize + ")"); return -1; }
- return Math.ceil(this.getRowCount() / this.pageSize);
- };
- /**
- * Returns the number of pages according to the current page size
- */
- EditableGrid.prototype.getCurrentPageIndex = function()
- {
- // if pagination is handled on the server side, pageSize will (must) be 0
- if (this.pageSize <= 0 && !this.serverSide) return 0;
- // if page index does not exist anymore, go to last page (without losing the information of the current page)
- return Math.max(0, this.currentPageIndex >= this.getPageCount() ? this.getPageCount() - 1 : this.currentPageIndex);
- };
- /**
- * Sets the current page (no effect if pageSize is 0)
- * @param {Integer} pageIndex Integer page index
- */
- EditableGrid.prototype.setPageIndex = function(pageIndex)
- {
- this.currentPageIndex = pageIndex;
- this.localset('pageIndex', pageIndex);
- this.refreshGrid();
- };
- /**
- * Go the previous page if we are not already on the first page
- * @return
- */
- EditableGrid.prototype.prevPage = function()
- {
- if (this.canGoBack()) this.setPageIndex(this.getCurrentPageIndex() - 1);
- };
- /**
- * Go the first page if we are not already on the first page
- * @return
- */
- EditableGrid.prototype.firstPage = function()
- {
- if (this.canGoBack()) this.setPageIndex(0);
- };
- /**
- * Go the next page if we are not already on the last page
- * @return
- */
- EditableGrid.prototype.nextPage = function()
- {
- if (this.canGoForward()) this.setPageIndex(this.getCurrentPageIndex() + 1);
- };
- /**
- * Go the last page if we are not already on the last page
- * @return
- */
- EditableGrid.prototype.lastPage = function()
- {
- if (this.canGoForward()) this.setPageIndex(this.getPageCount() - 1);
- };
- /**
- * Returns true if we are not already on the first page
- * @return
- */
- EditableGrid.prototype.canGoBack = function()
- {
- return this.getCurrentPageIndex() > 0;
- };
- /**
- * Returns true if we are not already on the last page
- * @return
- */
- EditableGrid.prototype.canGoForward = function()
- {
- return this.getCurrentPageIndex() < this.getPageCount() - 1;
- };
- /**
- * Returns an interval { startPageIndex: ..., endPageIndex: ... } so that a window of the given size is visible around the current page (hence the 'sliding').
- * If pagination is not enabled this method displays an error and returns null.
- * If pagination is enabled but there is only one page this function returns null (wihtout error).
- * @param slidingWindowSize size of the visible window
- * @return
- */
- EditableGrid.prototype.getSlidingPageInterval = function(slidingWindowSize)
- {
- var nbPages = this.getPageCount();
- if (nbPages <= 1) return null;
- var curPageIndex = this.getCurrentPageIndex();
- var startPageIndex = Math.max(0, curPageIndex - Math.floor(slidingWindowSize/2));
- var endPageIndex = Math.min(nbPages - 1, curPageIndex + Math.floor(slidingWindowSize/2));
- if (endPageIndex - startPageIndex < slidingWindowSize) {
- var diff = slidingWindowSize - (endPageIndex - startPageIndex + 1);
- startPageIndex = Math.max(0, startPageIndex - diff);
- endPageIndex = Math.min(nbPages - 1, endPageIndex + diff);
- }
- return { startPageIndex: startPageIndex, endPageIndex: endPageIndex };
- };
- /**
- * Returns an array of page indices in the given interval.
- *
- * @param interval
- * The given interval must be an object with properties 'startPageIndex' and 'endPageIndex'.
- * This interval may for example have been obtained with getCurrentPageInterval.
- *
- * @param callback
- * The given callback is applied to each page index before adding it to the result array.
- * This callback is optional: if none given, the page index will be added as is to the array.
- * If given , the callback will be called with two parameters: pageIndex (integer) and isCurrent (boolean).
- *
- * @return
- */
- EditableGrid.prototype.getPagesInInterval = function(interval, callback)
- {
- var pages = [];
- for (var p = interval.startPageIndex; p <= interval.endPageIndex; p++) {
- pages.push(typeof callback == 'function' ? callback(p, p == this.getCurrentPageIndex()) : p);
- }
- return pages;
- };
|