diff --git a/Gemfile b/Gemfile index ab0fe33..6b78d33 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,6 @@ gem "haml-rails", "~> 0.9" gem 'purecss-rails' # Use datatables gem 'jquery-datatables-rails' -gem 'ajax-datatables-rails' # Use Select2 for selecting users gem 'select2-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 4ffca3f..6ebdfcf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,8 +36,6 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - ajax-datatables-rails (0.3.1) - railties (>= 3.1) annotate (2.6.10) activerecord (>= 3.2, <= 4.3) rake (~> 10.4) @@ -251,7 +249,6 @@ PLATFORMS ruby DEPENDENCIES - ajax-datatables-rails annotate byebug cancancan diff --git a/app/assets/javascripts/jquery.dataTables.columnFilter.js b/app/assets/javascripts/jquery.dataTables.columnFilter.js new file mode 100644 index 0000000..ea4cec3 --- /dev/null +++ b/app/assets/javascripts/jquery.dataTables.columnFilter.js @@ -0,0 +1,829 @@ +/* +* 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); \ No newline at end of file diff --git a/app/controllers/concerns/data_table.rb b/app/controllers/concerns/data_table.rb new file mode 100644 index 0000000..a6268de --- /dev/null +++ b/app/controllers/concerns/data_table.rb @@ -0,0 +1,23 @@ + +module DataTable + extend ActiveSupport::Concern + + def apply_filter(user, params) + p selection_to_json(user, user.transactions) + selection_to_json(user, user.transactions) + end + + private + + def selection_to_json(user, selection) + { data: selection.map { |transaction| { + amount: transaction.signed_amount_for(user), + origin: transaction.origin, + message: transaction.message, + peer: transaction.peer_of(user).try(:name), + time: transaction.created_at + } } + } + end +end + diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f11be9d..6dcaeb3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,13 +1,13 @@ class UsersController < ApplicationController load_and_authorize_resource + include DataTable + def show @user = User.find(params[:id]) respond_to do |format| format.html - format.json { render json: TransactionDatatable.new( - view_context, {user: @user}) - } + format.json { render json: apply_filter(@user, params) } end end diff --git a/app/datatables/transaction_datatable.rb b/app/datatables/transaction_datatable.rb deleted file mode 100644 index 69ebfa3..0000000 --- a/app/datatables/transaction_datatable.rb +++ /dev/null @@ -1,27 +0,0 @@ -class TransactionDatatable < AjaxDatatablesRails::Base - include TransactionsHelper - - def sortable_columns - @sortable_columns ||= ['Transaction.amount'] - end - - def searchable_columns - @searchable_columns ||= [] - end - - private - def data - records.map do |record| - [ amount_in_perspective(record, options[:user]), - record.origin, - record.message, - get_transaction_peer(record, options[:user]).name, - record.created_at.strftime('%d/%m/%y %H:%M') - ] - end - end - - def get_raw_records - options[:user].transactions.eager_load(:debtor, :creditor) - end -end diff --git a/app/helpers/transactions_helper.rb b/app/helpers/transactions_helper.rb index 0300537..36098d1 100644 --- a/app/helpers/transactions_helper.rb +++ b/app/helpers/transactions_helper.rb @@ -1,11 +1,2 @@ module TransactionsHelper - def get_transaction_peer(transaction, user) - return transaction.creditor if user == transaction.debtor - return transaction.debtor if user == transaction.creditor - end - - def amount_in_perspective(transaction, user) - return -transaction.amount if user == transaction.debtor - return transaction.amount if user == transaction.creditor - end end diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 2e6f346..eb5cc51 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -26,6 +26,16 @@ class Transaction < ActiveRecord::Base Client.find_by name: origin end + def peer_of(user) + return creditor if user == debtor + return debtor if user == creditor + end + + def signed_amount_for(user) + return -amount if user == debtor + return amount if user == creditor + end + private def recalculate_balances diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 119b6ea..b6524c2 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,6 +1,12 @@ %h2= @user.name %table#transactions.display{data: { source: user_path(@user) }} %thead + %tr + %td Amount + %td Origin + %td Message + %td Peer + %td Time %tr %th Amount %th Origin @@ -11,10 +17,28 @@ :javascript $(document).ready(function() { - $('#transactions').DataTable({ - 'processing': true, - 'serverSide': true, - 'ajax': $('#transactions').data('source'), - 'pagingType': 'full_numbers' - }); + $('#transactions').dataTable({ + processing: true, + serverSide: true, + searching: true, + ordering: false, + ajax: $('#transactions').data('source'), + pagingType: 'full_numbers', + columns: [ + { data: 'amount', name: 'Amount' }, + { data: 'origin', name: 'Origin' }, + { data: 'message', name: 'Message' }, + { data: 'peer', name: 'Peer' }, + { data: 'time', name: 'Time' } + ] + }).columnFilter({ + sPlaceHolder: 'head:before', + aoColumns: [ + { type: 'number-range' }, + { type: 'text' }, + { type: 'text' }, + { type: 'text' }, + { type: 'date-range' } + ] + }); });