/** * jQuery grider * Versión 0.7 * @author: Boris Barroso Camberos * @email: boriscyber@gmail.com * @license MIT * http://www.opensource.org/licenses/mit-license.php */ (function($) { $.fn.extend({ grider: function(config) { return this.each(function() { new $.Grider(this, config); }); } }); /** * Transforms a grid that contains data, and allows to do calculations con form elements * in a simple way. Example: * * * * * * * * * * * * * *
PriceQuantityDiscountSubtotal
* * And then you execute $('#grid').grider(); * * @attr col: Defines the col name, it is a unique identifier for that col in the table * * @attr summary: Defines the type of calculation it will be done on the column, * operations that can be done are "sum", "avg", "max", "min" and "count" * * @attr formula: Calculates the formula with the columns you defined, right now it does simple calculations, * the formula is evaluated eval(formula), to calculate * * Configurations for the config variable * @param boolean config['initCalc'] Defines if formula should make calculations when it initializes, in case that you already did calculations server side * * @param boolean config['addRow'] Defines if the add row link will appear * @param string config['addRowText'] Text that will display to add rows * @param boolean config['delRow'] Defines if it will appear the delete link to delete a row * @param string config['delRowText'] Text that will be displayed for deleting a row * @param boolean config['countRow'] Indicates if it will do count the rows, necessary for adding and deleting rows * @param integer config['countRowCol'] Defines the column in which the count will be displayed, defauls = 0 * @param boolean config['countRowAdd'] Indicates if it will be able to add Row * @param boolean config['rails'] accepts the use of rails nested attributes */ $.Grider = function(table, config) { /** * Defaults */ var defaults = Grider.defaults; var config; if(config) { for(var k in defaults) { if(typeof(defaults[k]) == "boolean" && typeof(config[k]) == "boolean" ) { config[k] = config[k]; }else{ config[k] = config[k] || defaults[k]; } } }else{ config = defaults; } var cols = {}; // Identifies if there is a summary column var summaryRow = false; var formulaSet = false; // Indicates if the formula was added config = config || {}; setGrider(table); /** * This function prepares all to set the table * @param DOM t Table */ function setGrider(t) { $(table).find('tr:first').addClass('noedit'); // Allow to count rows if(config['countRow']) { if(config['countRowAdd']) { var className = $(table).find("th:first").attr("class"); className = className != "" ? ' class="' + className + '"': ""; $(table).find('tr.noedit:first').prepend(''+ config.countRowText +''); $(table).find('tr:not(.noedit)').each(function(index, elem){ var ind = index+1; $(elem).prepend(''+ind+''); }); } } var l = $(table).find("tr:not(.noedit):first td").length; for(var i = 0; i < l; i++) { setColumn(t.rows[0].cells[i], i); } // Types of columns setColType(); // Setting formulas and summaries for(var i = 0; i < l; i++) { setFormula(t.rows[0].cells[i]); setSummary(t.rows[0].cells[i]); } // Calculation of formulas for the first Time if(formulaSet && config.initCalc) { var rows = $(table).find('tr:not(.noedit)'); rows.each(function(index, elem) { var pos = index + 1; for(var k in cols) { if(cols[k].formula) { calculateFormula(cols[k].name, pos); } } }); } for(var k in cols){ if(cols[k].summary) { calculateSummary(k); } }; // Allow to add rows if(config['addRow']) { $(table).append(config['addRowText']); $(table).find("caption a").click(function() { addRow(); }); } // Allow to delete rows if(config['delRow']) { $(table).find('tr:not(.noedit)').each(function(index,elem){ $(elem).append(config['delRowText']); }); $(table).find('a.delete').live("click", function(){ delRow(this); return true; }); } // Add events to the elements in the table, elements that are related to a summary or formula setEvents(); // Allows to add rows using tab when cursor on a delete link if(config.addRowWithTab) addRowWithTab(); } /** * Allows to add a new row, the new row is added at the endof the editable rows */ function addRowWithTab() { $(table).find("tr:not(.noedit):last a.delete").live("keydown",function(e) { if(e.keyCode == 9) { addRow(); } }); } /** * Determines the type of element that is contained in the TD */ function setColType() { var row = $(table).find('tr:not(.noedit):first')[0]; // Finds the first row tha is editable for(var k in cols) { var cell = $(row).find('td:eq(' + cols[k].pos + ')')[0]; var node = $(cell).find('select')[0] || $(cell).find('input:not(:submit)')[0] || $(cell).find('select')[0]; try { type = node.nodeName.toLowerCase(); }catch(e){ type = false } if(type) { switch(type) { case 'input': cols[k]['type'] = 'input:'+ node.type; break; case 'select': cols[k]['type'] = 'select'; break; case 'textarea': cols[k]['type'] = 'textarea'; break; default: cols[k]['type'] = 'input:text'; break; } }else{ // Allows to use jQuery selectors cols[k]['type'] = ''; } } } /** * Allows to define columns with its names * @param DOM cell TD * @param integer pos Column number, starts on 0 */ function setColumn(cell, pos) { var col = $(cell).attr('col'); if(col) { cols[col] = { pos: pos, name: col }; } } /** * Alows columns to calculate summaries, like avg (average), sum, max, count * @param DOM cell */ function setSummary(cell) { var summary = $(cell).attr('summary'); var summaryCell = false; var col = $(cell).attr('col'); if(summary == 'sum' || summary == 'avg' || summary == 'max' || summary == 'min' || summary == 'count') { cols[col]['summary'] = summary; summaryCell = true; } // Add the summary row if(!summaryRow && summaryCell) { var html = ''; $(table).find("tr:not(.noedit):first td").each(function(index, elem) { var style = $(elem).attr("style") ? ' style="' + $(elem).attr("style") + '"' : ""; html += ""; }); html+=''; $(table).append(html); summaryRow = true; } } /** * Calculates the summary of a column * @param String col, name of the column */ function calculateSummary(col) { var summary = cols[col].summary; var pos = parseInt(cols[col].pos) + 1; var cells = $(table).find('tr:not(.noedit) td:nth-child(' + pos + ')'); var res = 0, sum = 0, max = null, min = null; if(summary != 'count') { var val = 0; cells.each(function(index, elem) { if(cols[col].type == "") { val = $(elem).html() * 1; }else{ val = $(elem).find(cols[col].type).val() * 1; } switch(summary) { case 'sum': sum+= val; break; case 'avg': sum+= val; break; case 'max': if(!max){ max = val; } else if(max < val) { max = val; } break; case 'min': if(!min){ min = val; } else if(min > val) { min = val; } break; } }); switch(summary) { case 'sum': res = sum; break; case 'avg': res = sum/cells.length; break; case 'max': res = max; break; case 'min': res = min; break; } }else{ res = cells.length; } res = res.toFixed(config.decimals); $(table).find('tr.summary td:nth-child(' + pos +')').html(res); } /** * Fires the event required * @param Event e */ function fireCellEvent(e) { var target = e.target || e.srcElement; if(target.nodeType == 1) { var rowNum = $(target).parents('tr')[0].rowIndex; var colNum = $(target).parents('td')[0].cellIndex; var col = findColBy(colNum, 'pos'); for(var k in cols) { if(cols[k].formula) { try{ var reg = '\\b'+ col.name +'\\b'; reg = new RegExp(reg); if(reg.test(cols[k].formula)) { calculateFormula(k, rowNum); } }catch(e){} } } } } /** * creates the formulas to be calculated * @param DOM cell */ function setFormula(cell) { formulaSet = true; var formula = $(cell).attr('formula'); var col = $(cell).attr('col'); if(formula) { cols[col]['formula'] = formula; // Register elements that trigger the calculation of a formula for(var k in cols) { reg = "\\b" + k + "\\b"; var reg = new RegExp(reg); // Definir que elementos tienen evento if( reg.test(formula)) { if(cols[k].type != '') cols[k]["event"] = true; } } } } /** * Prepares the events requied in the grid * @param string col, name of the column */ function setEvents() { for(k in cols) { if(cols[k].event) { var pos = parseInt(cols[k]['pos']) + 1; var exp = 'tr td:nth-child(' + pos + ') ' + cols[k].type; // Shitty Internet Explorer, not posible to use "live" if(cols[k].type == 'input:text' || cols[k].type == 'textarea' || cols[k].type == 'select' ) { $(table).find(exp).unbind("change"); $(table).find(exp).change( function(e) { fireCellEvent(e); }); }else if( cols[k].type == 'input:checkbox') { $(table).find(exp).unbind("click"); $(table).find(exp).click( function(e) { fireCellEvent(e); }); } } } } /** * Calculates the formula according to de columns * @param String col, column name * @param Integer pos, position of the row where the event was generated */ function calculateFormula(col, pos) { var pat = cols[col].formula.match(/\b[a-z_-]+[0-9]*\b/ig); var formu = cols[col].formula; var row = $(table).find('tr:eq('+ pos + ')'); // Again needed for IE for(var k in pat) { if(!/^\d+$/.test(k)) { delete(pat[k]); } } var columns = [] // Prepare formula to be calcultated for(var k in pat) { //console.log("%s: %o, %o, %o",table.id, k, pat[k], cols[pat[k]]); try{ var exp = 'td:nth-child(' + (cols[pat[k]].pos + 1) + ') ' + cols[pat[k]].type; }catch(e){ //console.log("%s: %o, %o, %o",table.id, k, pat[k], cols); } var val = 0; if(cols[pat[k]].type == 'input:checkbox') { val = $(row).find(exp).attr('checked') ? 1 : 0; }else if(cols[pat[k]].type == 'input:text'){ val = parseFloat( $(row).find(exp).val() ) || 0 } var reg = new RegExp('\\b' + pat[k] + '\\b') formu = formu.replace(reg, val); columns.push(pat[k]); } var res = eval(formu); res = res.toFixed(config.decimals); // Pocision the response var cell = $(row).find('td:nth-child(' + (cols[col].pos + 1) + ')'); if(cols[col].type == "") { $(cell).html(res); }else{ $(cell).find(type).html(res); } for(var i=0, l=columns.length ; i< l; i++) { if(cols[columns[i]].summary) calculateSummary(columns[i]); } calculateSummary(col); } /** * Finds a value in the cols Object * @param string bus, search parameter * @param string prop, Property in the cols Object * @return object, Returns the column */ function findColBy(bus, prop) { for(var k in cols) { if(bus == cols[k][prop]) { return cols[k]; } } } /** * Intializes and adds a number when needed to count the rows added or deleted */ function addFormPos() { if(!config.formPos || config.formPos == '') { var control = $(table).find('tr:not(.noedit):last').find('input, select, textarea')[0] || false; // Row number in the control if(control.name) { config["formPos"] = control.name.replace(/^.*\[([0-9]+)\].*$/ig, "$1") || ''; } config.addedRow = true; config.formPos++; } else { config.formPos++; } } /** * Function that allows o add new rows */ function addRow() { var tr = $(table).find('tr:not(.noedit):first').clone(); addFormPos(); if($(tr).find("input, select, textarea").length > 0) { $(tr).find("input, textarea, select").each(function(index, elem) { // Change the name of the fields var newName = ''; if(config.formPos !== '') { newName = elem.name.replace(/\[[0-9]+\]/i, '[' + config.formPos + ']'); }else { newName = elem.name; } if(elem.type == 'checkbox' || elem.type == 'radio') { $(elem).attr({'name': newName, 'checked': false}) }else { $(elem).attr({'name': newName, 'value': ''}); } $(elem) }); $(tr).find("input:radio, input:checkbox").attr('checked', false); } if(cols[k] && cols[k].type == "" && cols[k].formula) $(tr).find("td:eq(" + cols[k].pos + ")").html(''); if(config['countRow']) { var fila = parseInt($(table).find('tr:not(.noedit):last td:eq('+ config['countRowCol'] +')').html()) + 1; $(tr).find('td:eq('+ config['countRowCol'] +')').html(fila); } $(table).find('tr:not(.noedit):last').after(tr); // Register elements that fire events setEvents(); for(var kk in cols){ if(cols[kk].summary) calculateSummary(cols[kk].name); } } /** * Allows to delete a row */ function delRow(elem) { if($(table).find('tr:not(.noedit)').length > 1 ) { if(config.rails) { var el = $(elem).parents('tr').eq(0).prev("input:hidden[name$='[id]']"); if(el.length > 0) { addFormPos(); var name = $(el).attr("name"); var value = $(el).val(); n1 = name.replace(/^(.*)(\[[0-9]+\])(\[id\])$/, "$1["+ config.formPos + "]$3"); n2 = name.replace(/^(.*)(\[[0-9]+\])(\[id\])$/, "$1["+ config.formPos +"][_delete]"); $(el).remove(); $(table).prepend(''); } } $(elem).parents('tr').eq(0).remove(); if(config['countRow']) { rowNumber(); } } for(var k in cols) { if(cols[k].summary) calculateSummary(k); } } /** * Number the rows */ function rowNumber() { var pos = parseInt(config.countRowCol) + 1; $(table).find('tr:not(.noedit) td:nth-child('+ pos +')').each(function(index, elem) { var ind = index + 1; $(elem).html(ind); }); } return { cols: cols, config: config, summaryRow: summaryRow, table: table, formulaSet: formulaSet, calculateFormula: calculateFormula, setGrider: setGrider, setColumn: setColumn, fireCellEvent: fireCellEvent, setColType: setColType, findColBy: findColBy, addRow: addRow, addRowWithTab: addRowWithTab, delRow: delRow, rowNumber: rowNumber } } $.Grider.events = function() { return 'nuevo'; } })(jQuery); // Defauls English configurations Grider = { defaults : { initCalc: true, addRow: true, addRowWithTab: true, delRow: true, decimals: 2, addRowText: 'Add Row', delRowText: 'delete', countRow: false, countRowText: 'Nº', countRowCol: 0, countRowAdd: false, addedRow: false, rails: false } } //Defauls Spanish /* Grider = { defaults : { initCalc: true, addRow: true, addRowWithTab: true, delRow: true, decimals: 2, addRowText: 'Adicionar Fila', delRowText: 'borrar', countRow: false, countRowText: 'Nº', countRowCol: 0, countRowAdd: false, addedRow: false } } */