/* * File: jquery.dataTables.columnFilter.js * Version: 1.5.6. * Author: Jovan Popovic * * Copyright 2011-2014 Jovan Popovic, all rights reserved. * * This source file is free software, under either the GPL v2 license or a * BSD style license, as supplied with this software. * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. * * Parameters:" * @sPlaceHolder String Place where inline filtering function should be placed ("tfoot", "thead:before", "thead:after"). Default is "tfoot" * @sRangeSeparator String Separator that will be used when range values are sent to the server-side. Default value is "~". * @sRangeFormat string Default format of the From ... to ... range inputs. Default is From {from} to {to} * @aoColumns Array Array of the filter settings that will be applied on the columns */ (function ($) { $.fn.columnFilter = function (options) { var asInitVals, i, label, th; //var sTableId = "table"; var sRangeFormat = "From {from} to {to}"; //Array of the functions that will override sSearch_ parameters var afnSearch_ = new Array(); var aiCustomSearch_Indexes = new Array(); var oFunctionTimeout = null; var fnOnFiltered = function () { }; function _fnGetColumnValues(oSettings, iColumn, bUnique, bFiltered, bIgnoreEmpty) { /// ///Return values in the column /// ///DataTables settings ///Id of the column ///Return only distinct values ///Return values only from the filtered rows ///Ignore empty cells // check that we have a column id if (typeof iColumn == "undefined") return new Array(); // by default we only wany unique data if (typeof bUnique == "undefined") bUnique = true; // by default we do want to only look at filtered data if (typeof bFiltered == "undefined") bFiltered = true; // by default we do not wany to include empty values if (typeof bIgnoreEmpty == "undefined") bIgnoreEmpty = true; // list of rows which we're going to loop through var aiRows; // use only filtered rows if (bFiltered == true) aiRows = oSettings.aiDisplay; // use all rows else aiRows = oSettings.aiDisplayMaster; // all row numbers // set up data array var asResultData = new Array(); for (var i = 0, c = aiRows.length; i < c; i++) { var iRow = aiRows[i]; var aData = oTable.fnGetData(iRow); var sValue = aData[iColumn]; // ignore empty values? if (bIgnoreEmpty == true && sValue.length == 0) continue; // ignore unique values? else if (bUnique == true && jQuery.inArray(sValue, asResultData) > -1) continue; // else push the value onto the result data array else asResultData.push(sValue); } return asResultData.sort(); } function _fnColumnIndex(iColumnIndex) { if (properties.bUseColVis) return iColumnIndex; else return oTable.fnSettings().oApi._fnVisibleToColumnIndex(oTable.fnSettings(), iColumnIndex); //return iColumnIndex; //return oTable.fnSettings().oApi._fnColumnIndexToVisible(oTable.fnSettings(), iColumnIndex); } function fnCreateInput(oTable, regex, smart, bIsNumber, iFilterLength, iMaxLenght) { var sCSSClass = "text_filter form-control"; if (bIsNumber) sCSSClass = "number_filter form-control"; label = label.replace(/(^\s*)|(\s*$)/g, ""); var currentFilter = oTable.fnSettings().aoPreSearchCols[i].sSearch; var search_init = 'search_init '; var inputvalue = label; if (currentFilter != '' && currentFilter != '^') { if (bIsNumber && currentFilter.charAt(0) == '^') inputvalue = currentFilter.substr(1); //ignore trailing ^ else inputvalue = currentFilter; search_init = ''; } var input = $(''); if (iMaxLenght != undefined && iMaxLenght != -1) { input.attr('maxlength', iMaxLenght); } th.html(input); if (bIsNumber) th.wrapInner(''); else th.wrapInner(''); asInitVals[i] = label; var index = i; if (bIsNumber && !oTable.fnSettings().oFeatures.bServerSide) { input.keyup(function () { /* Filter on the column all numbers that starts with the entered value */ oTable.fnFilter('^' + this.value, _fnColumnIndex(index), true, false); //Issue 37 fnOnFiltered(); }); } else { input.keyup(function () { if (oTable.fnSettings().oFeatures.bServerSide && iFilterLength != 0) { //If filter length is set in the server-side processing mode //Check has the user entered at least iFilterLength new characters var currentFilter = oTable.fnSettings().aoPreSearchCols[index].sSearch; var iLastFilterLength = $(this).data("dt-iLastFilterLength"); if (typeof iLastFilterLength == "undefined") iLastFilterLength = 0; var iCurrentFilterLength = this.value.length; if (Math.abs(iCurrentFilterLength - iLastFilterLength) < iFilterLength //&& currentFilter.length == 0 //Why this? ) { //Cancel the filtering return; } else { //Remember the current filter length $(this).data("dt-iLastFilterLength", iCurrentFilterLength); } } /* Filter on the column (the index) of this element */ oTable.fnFilter(this.value, _fnColumnIndex(index), regex, smart); //Issue 37 fnOnFiltered(); }); } input.focus(function () { if ($(this).hasClass("search_init")) { $(this).removeClass("search_init"); this.value = ""; } }); input.blur(function () { if (this.value == "") { $(this).addClass("search_init"); this.value = asInitVals[index]; } }); } function fnCreateRangeInput(oTable) { //var currentFilter = oTable.fnSettings().aoPreSearchCols[i].sSearch; th.html(_fnRangeLabelPart(0)); var sFromId = oTable.attr("id") + '_range_from_' + i; var from = $(''); th.append(from); th.append(_fnRangeLabelPart(1)); var sToId = oTable.attr("id") + '_range_to_' + i; var to = $(''); th.append(to); th.append(_fnRangeLabelPart(2)); th.wrapInner(''); var index = i; aiCustomSearch_Indexes.push(i); //------------start range filtering function /* Custom filtering function which will filter data in column four between two values * Author: Allan Jardine, Modified by Jovan Popovic */ //$.fn.dataTableExt.afnFiltering.push( oTable.dataTableExt.afnFiltering.push( function (oSettings, aData, iDataIndex) { if (oTable.attr("id") != oSettings.sTableId) return true; // Try to handle missing nodes more gracefully if (document.getElementById(sFromId) == null) return true; var iMin = document.getElementById(sFromId).value * 1; var iMax = document.getElementById(sToId).value * 1; var iValue = aData[_fnColumnIndex(index)] == "-" ? 0 : aData[_fnColumnIndex(index)] * 1; if (iMin == "" && iMax == "") { return true; } else if (iMin == "" && iValue <= iMax) { return true; } else if (iMin <= iValue && "" == iMax) { return true; } else if (iMin <= iValue && iValue <= iMax) { return true; } return false; } ); //------------end range filtering function $('#' + sFromId + ',#' + sToId, th).keyup(function () { var iMin = document.getElementById(sFromId).value * 1; var iMax = document.getElementById(sToId).value * 1; if (iMin != 0 && iMax != 0 && iMin > iMax) return; oTable.fnDraw(); fnOnFiltered(); }); } function fnCreateDateRangeInput(oTable) { var aoFragments = sRangeFormat.split(/[}{]/); th.html(""); //th.html(_fnRangeLabelPart(0)); var sFromId = oTable.attr("id") + '_range_from_' + i; var from = $(''); from.datepicker(); //th.append(from); //th.append(_fnRangeLabelPart(1)); var sToId = oTable.attr("id") + '_range_to_' + i; var to = $(''); //th.append(to); //th.append(_fnRangeLabelPart(2)); for (ti = 0; ti < aoFragments.length; ti++) { if (aoFragments[ti] == properties.sDateFromToken) { th.append(from); } else { if (aoFragments[ti] == properties.sDateToToken) { th.append(to); } else { th.append(aoFragments[ti]); } } } th.wrapInner(''); to.datepicker(); var index = i; aiCustomSearch_Indexes.push(i); //------------start date range filtering function //$.fn.dataTableExt.afnFiltering.push( oTable.dataTableExt.afnFiltering.push( function (oSettings, aData, iDataIndex) { if (oTable.attr("id") != oSettings.sTableId) return true; var dStartDate = from.datepicker("getDate"); var dEndDate = to.datepicker("getDate"); if (dStartDate == null && dEndDate == null) { return true; } var dCellDate = null; try { if (aData[_fnColumnIndex(index)] == null || aData[_fnColumnIndex(index)] == "") return false; dCellDate = $.datepicker.parseDate($.datepicker.regional[""].dateFormat, aData[_fnColumnIndex(index)]); } catch (ex) { return false; } if (dCellDate == null) return false; if (dStartDate == null && dCellDate <= dEndDate) { return true; } else if (dStartDate <= dCellDate && dEndDate == null) { return true; } else if (dStartDate <= dCellDate && dCellDate <= dEndDate) { return true; } return false; } ); //------------end date range filtering function $('#' + sFromId + ',#' + sToId, th).change(function () { oTable.fnDraw(); fnOnFiltered(); }); } function fnCreateColumnSelect(oTable, aData, iColumn, nTh, sLabel, bRegex, oSelected, bMultiselect) { if (aData == null) aData = _fnGetColumnValues(oTable.fnSettings(), iColumn, true, false, true); var index = iColumn; var currentFilter = oTable.fnSettings().aoPreSearchCols[i].sSearch; if (currentFilter == null || currentFilter == "")//Issue 81 currentFilter = oSelected; var r = ''; } var j = 0; var iLen = aData.length; for (j = 0; j < iLen; j++) { if (typeof (aData[j]) != 'object') { var selected = ''; if (escape(aData[j]) == currentFilter || escape(aData[j]) == escape(currentFilter) ) selected = 'selected ' r += ''; } else { var selected = ''; if (bRegex) { //Do not escape values if they are explicitely set to avoid escaping special characters in the regexp if (aData[j].value == currentFilter) selected = 'selected '; r += ''; } else { if (escape(aData[j].value) == currentFilter) selected = 'selected '; r += ''; } } } var select = $(r + ''); nTh.html(select); nTh.wrapInner(''); if(bMultiselect) { select.change(function () { if ($(this).val() != "") { $(this).removeClass("search_init"); } else { $(this).addClass("search_init"); } var selectedOptions = $(this).val(); var asEscapedFilters = []; if(selectedOptions==null || selectedOptions==[]){ var re = '^(.*)$'; }else{ $.each( selectedOptions, function( i, sFilter ) { asEscapedFilters.push( fnRegExpEscape( sFilter ) ); } ); var re = '^(' + asEscapedFilters.join('|') + ')$'; } oTable.fnFilter( re, index, true, false ); }); } else { select.change(function () { //var val = $(this).val(); if ($(this).val() != "") { $(this).removeClass("search_init"); } else { $(this).addClass("search_init"); } if (bRegex) oTable.fnFilter($(this).val(), iColumn, bRegex); //Issue 41 else oTable.fnFilter(unescape($(this).val()), iColumn); //Issue 25 fnOnFiltered(); }); if (currentFilter != null && currentFilter != "")//Issue 81 oTable.fnFilter(unescape(currentFilter), iColumn); } } function fnCreateSelect(oTable, aData, bRegex, oSelected, bMultiselect) { var oSettings = oTable.fnSettings(); if ( (aData == null || typeof(aData) == 'function' ) && oSettings.sAjaxSource != "" && !oSettings.oFeatures.bServerSide) { // Add a function to the draw callback, which will check for the Ajax data having // been loaded. Use a closure for the individual column elements that are used to // built the column filter, since 'i' and 'th' (etc) are locally "global". oSettings.aoDrawCallback.push({ "fn": (function (iColumn, nTh, sLabel) { return function (oSettings) { // Only rebuild the select on the second draw - i.e. when the Ajax // data has been loaded. if (oSettings.iDraw == 2 && oSettings.sAjaxSource != null && oSettings.sAjaxSource != "" && !oSettings.oFeatures.bServerSide) { return fnCreateColumnSelect(oTable, aData && aData(oSettings.aoData, oSettings), _fnColumnIndex(iColumn), nTh, sLabel, bRegex, oSelected, bMultiselect); //Issue 37 } }; })(i, th, label), "sName": "column_filter_" + i }); } // Regardless of the Ajax state, build the select on first pass fnCreateColumnSelect(oTable, typeof(aData) == 'function' ? null: aData, _fnColumnIndex(i), th, label, bRegex, oSelected, bMultiselect); //Issue 37 } function fnRegExpEscape( sText ) { return sText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); }; function fnCreateDropdown(aData) { var index = i; var r = ''); th.html(select); th.wrapInner(''); select.find('li').click(function () { oTable.fnFilter($(this).data('value'), index); }); } function fnCreateCheckbox(oTable, aData) { if (aData == null) aData = _fnGetColumnValues(oTable.fnSettings(), i, true, true, true); var index = i; var r = '', j, iLen = aData.length; //clean the string var localLabel = label.replace('%', 'Perc').replace("&", "AND").replace("$", "DOL").replace("£", "STERL").replace("@", "AT").replace(/\s/g, "_"); localLabel = localLabel.replace(/[^a-zA-Z 0-9]+/g, ''); //clean the string //button label override var labelBtn = label; if (properties.sFilterButtonText != null || properties.sFilterButtonText != undefined) { labelBtn = properties.sFilterButtonText; } var relativeDivWidthToggleSize = 10; var numRow = 12; //numero di checkbox per colonna var numCol = Math.floor(iLen / numRow); if (iLen % numRow > 0) { numCol = numCol + 1; }; //count how many column should be generated and split the div size var divWidth = 100 / numCol - 2; var divWidthToggle = relativeDivWidthToggleSize * numCol; if (numCol == 1) { divWidth = 20; } var divRowDef = '
'; var divClose = '
'; var uniqueId = oTable.attr("id") + localLabel; var buttonId = "chkBtnOpen" + uniqueId; var checkToggleDiv = uniqueId + "-flt-toggle"; r += ''; //filter button witch open dialog r += '
'; //dialog div //r+= '
'; //reset button and its div r += divRowDef; for (j = 0; j < iLen; j++) { //if last check close div if (j % numRow == 0 && j != 0) { r += divClose + divRowDef; } var sLabel = aData[j]; var sValue = aData[j]; if (typeof (aData[j]) == 'object') { sLabel = aData[j].label; sValue = aData[j].value; } //check button r += '' + sLabel + '
'; var checkbox = $(r); th.html(checkbox); th.wrapInner(''); //on every checkbox selection checkbox.change(function () { var search = ''; var or = '|'; //var for select checks in 'or' into the regex var resSize = $('input:checkbox[name="' + localLabel + '"]:checked').size(); $('input:checkbox[name="' + localLabel + '"]:checked').each(function (index) { //search = search + ' ' + $(this).val(); //concatenation for selected checks in or if ((index == 0 && resSize == 1) || (index != 0 && index == resSize - 1)) { or = ''; } //trim search = search.replace(/^\s+|\s+$/g, ""); search = search + $(this).val() + or; or = '|'; }); if (search != "") { $('input:checkbox[name="' + localLabel + '"]').removeClass("search_init"); } else { $('input:checkbox[name="' + localLabel + '"]').addClass("search_init"); } /* Old code for setting search_init CSS class on checkboxes if any of them is checked for (var jj = 0; jj < iLen; jj++) { if (search != "") { $('#' + aData[jj]).removeClass("search_init"); } else { $('#' + aData[jj]).addClass("search_init"); } } */ //execute search oTable.fnFilter(search, index, true, false); fnOnFiltered(); }); } //filter button $('#' + buttonId).button(); //dialog $('#' + checkToggleDiv).dialog({ //height: 140, autoOpen: false, //show: "blind", hide: "blind", buttons: [{ text: "Reset", click: function () { //$('#'+buttonId).removeClass("filter_selected"); //LM remove border if filter selected $('input:checkbox[name="' + localLabel + '"]:checked').each(function (index3) { $(this).attr('checked', false); $(this).addClass("search_init"); }); oTable.fnFilter('', index, true, false); fnOnFiltered(); return false; } }, { text: "Close", click: function () { $(this).dialog("close"); } } ] }); $('#' + buttonId).click(function () { $('#' + checkToggleDiv).dialog('open'); var target = $(this); $('#' + checkToggleDiv).dialog("widget").position({ my: 'top', at: 'bottom', of: target }); return false; }); var fnOnFilteredCurrent = fnOnFiltered; fnOnFiltered = function () { var target = $('#' + buttonId); $('#' + checkToggleDiv).dialog("widget").position({ my: 'top', at: 'bottom', of: target }); fnOnFilteredCurrent(); }; //reset /* $('#'+buttonId+"Reset").button(); $('#'+buttonId+"Reset").click(function(){ $('#'+buttonId).removeClass("filter_selected"); //LM remove border if filter selected $('input:checkbox[name="'+localLabel+'"]:checked').each(function(index3) { $(this).attr('checked', false); $(this).addClass("search_init"); }); oTable.fnFilter('', index, true, false); return false; }); */ } function _fnRangeLabelPart(iPlace) { switch (iPlace) { case 0: return sRangeFormat.substring(0, sRangeFormat.indexOf("{from}")); case 1: return sRangeFormat.substring(sRangeFormat.indexOf("{from}") + 6, sRangeFormat.indexOf("{to}")); default: return sRangeFormat.substring(sRangeFormat.indexOf("{to}") + 4); } } var oTable = this; var defaults = { sPlaceHolder: "foot", sRangeSeparator: "~", iFilteringDelay: 500, aoColumns: null, sRangeFormat: "From {from} to {to}", sDateFromToken: "from", sDateToToken: "to" }; var properties = $.extend(defaults, options); return this.each(function () { if (!oTable.fnSettings().oFeatures.bFilter) return; asInitVals = new Array(); var aoFilterCells = oTable.fnSettings().aoFooter[0]; var oHost = oTable.fnSettings().nTFoot; //Before fix for ColVis var sFilterRow = "tr"; //Before fix for ColVis if (properties.sPlaceHolder == "head:after") { var tr = $("tr:first", oTable.fnSettings().nTHead).detach(); //tr.appendTo($(oTable.fnSettings().nTHead)); if (oTable.fnSettings().bSortCellsTop) { tr.prependTo($(oTable.fnSettings().nTHead)); //tr.appendTo($("thead", oTable)); aoFilterCells = oTable.fnSettings().aoHeader[1]; } else { tr.appendTo($(oTable.fnSettings().nTHead)); //tr.prependTo($("thead", oTable)); aoFilterCells = oTable.fnSettings().aoHeader[0]; } sFilterRow = "tr:last"; oHost = oTable.fnSettings().nTHead; } else if (properties.sPlaceHolder == "head:before") { if (oTable.fnSettings().bSortCellsTop) { var tr = $("tr:first", oTable.fnSettings().nTHead).detach(); tr.appendTo($(oTable.fnSettings().nTHead)); aoFilterCells = oTable.fnSettings().aoHeader[1]; } else { aoFilterCells = oTable.fnSettings().aoHeader[0]; } /*else { //tr.prependTo($("thead", oTable)); sFilterRow = "tr:first"; }*/ sFilterRow = "tr:first"; oHost = oTable.fnSettings().nTHead; } //$(sFilterRow + " th", oHost).each(function (index) {//bug with ColVis $(aoFilterCells).each(function (index) {//fix for ColVis i = index; var aoColumn = { type: "text", bRegex: false, bSmart: true, iMaxLenght: -1, iFilterLength: 0 }; if (properties.aoColumns != null) { if (properties.aoColumns.length < i || properties.aoColumns[i] == null) return; aoColumn = properties.aoColumns[i]; } //label = $(this).text(); //Before fix for ColVis label = $($(this)[0].cell).text(); //Fix for ColVis if (aoColumn.sSelector == null) { //th = $($(this)[0]);//Before fix for ColVis th = $($(this)[0].cell); //Fix for ColVis } else { th = $(aoColumn.sSelector); if (th.length == 0) th = $($(this)[0].cell); } if (aoColumn != null) { if (aoColumn.sRangeFormat != null) sRangeFormat = aoColumn.sRangeFormat; else sRangeFormat = properties.sRangeFormat; switch (aoColumn.type) { case "null": break; case "number": fnCreateInput(oTable, true, false, true, aoColumn.iFilterLength, aoColumn.iMaxLenght); break; case "select": if (aoColumn.bRegex != true) aoColumn.bRegex = false; fnCreateSelect(oTable, aoColumn.values, aoColumn.bRegex, aoColumn.selected, aoColumn.multiple); break; case "number-range": fnCreateRangeInput(oTable); break; case "date-range": fnCreateDateRangeInput(oTable); break; case "checkbox": fnCreateCheckbox(oTable, aoColumn.values); break; case "twitter-dropdown": case "dropdown": fnCreateDropdown(aoColumn.values); break; case "text": default: bRegex = (aoColumn.bRegex == null ? false : aoColumn.bRegex); bSmart = (aoColumn.bSmart == null ? false : aoColumn.bSmart); fnCreateInput(oTable, bRegex, bSmart, false, aoColumn.iFilterLength, aoColumn.iMaxLenght); break; } } }); for (j = 0; j < aiCustomSearch_Indexes.length; j++) { //var index = aiCustomSearch_Indexes[j]; var fnSearch_ = function () { var id = oTable.attr("id"); return $("#" + id + "_range_from_" + aiCustomSearch_Indexes[j]).val() + properties.sRangeSeparator + $("#" + id + "_range_to_" + aiCustomSearch_Indexes[j]).val() } afnSearch_.push(fnSearch_); } if (oTable.fnSettings().oFeatures.bServerSide) { var fnServerDataOriginal = oTable.fnSettings().fnServerData; oTable.fnSettings().fnServerData = function (sSource, aoData, fnCallback) { for (j = 0; j < aiCustomSearch_Indexes.length; j++) { var index = aiCustomSearch_Indexes[j]; for (k = 0; k < aoData.length; k++) { if (aoData[k].name == "sSearch_" + index) aoData[k].value = afnSearch_[j](); } } aoData.push({ "name": "sRangeSeparator", "value": properties.sRangeSeparator }); if (fnServerDataOriginal != null) { try { fnServerDataOriginal(sSource, aoData, fnCallback, oTable.fnSettings()); //TODO: See Issue 18 } catch (ex) { fnServerDataOriginal(sSource, aoData, fnCallback); } } else { $.getJSON(sSource, aoData, function (json) { fnCallback(json) }); } }; } }); }; })(jQuery);