1'use strict';
2
3function tableInit(ctx){
4
5  if (ctx.url.length === 0) {
6    throw "No url supplied for retreiving data";
7  }
8
9  var tableChromeDone = false;
10  var tableTotal = 0;
11
12  var tableParams = {
13    limit : 25,
14    page : 1,
15    orderby : null,
16    filter : null,
17    search : null,
18    default_orderby: null,
19  };
20
21  var defaultHiddenCols = [];
22
23  var table =  $("#" + ctx.tableName);
24
25  /* if we're loading clean from a url use it's parameters as the default */
26  var urlParams = libtoaster.parseUrlParams();
27
28  /* Merge the tableParams and urlParams object properties  */
29  tableParams = $.extend(tableParams, urlParams);
30
31  /* Now fix the types that .extend changed for us */
32  tableParams.limit = Number(tableParams.limit);
33  tableParams.page = Number(tableParams.page);
34
35  loadData(tableParams);
36
37  // clicking on this set of elements removes the search
38  var clearSearchElements = $('.remove-search-btn-'+ctx.tableName +
39                              ', .show-all-'+ctx.tableName);
40
41  function loadData(tableParams){
42    table.trigger("table-loading");
43
44    $.ajax({
45        type: "GET",
46        url: ctx.url,
47        data: tableParams,
48        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
49        success: function(tableData) {
50          updateTable(tableData);
51          window.history.replaceState(null, null,
52            libtoaster.dumpsUrlParams(tableParams));
53        }
54    });
55  }
56
57  function updateTable(tableData) {
58    var tableBody = table.children("tbody");
59    var pagination = $('#pagination-'+ctx.tableName);
60    var paginationBtns = pagination.children('ul');
61    var tableContainer = $("#table-container-"+ctx.tableName);
62
63    tableContainer.css("visibility", "hidden");
64    /* To avoid page re-layout flicker when paging set fixed height */
65    table.css("padding-bottom", table.height());
66
67    /* Reset table components */
68    tableBody.html("");
69    paginationBtns.html("");
70
71    if (tableParams.search)
72      clearSearchElements.show();
73    else
74      clearSearchElements.hide();
75
76    $('.table-count-' + ctx.tableName).text(tableData.total);
77    tableTotal = tableData.total;
78
79    if (tableData.total === 0){
80      tableContainer.hide();
81      /* No results caused by a search returning nothing */
82      if (tableParams.search) {
83        if ($("#no-results-special-"+ctx.tableName).length > 0) {
84          /* use this page's special no-results form instead of the default */
85          $("#no-results-search-input-"+ctx.tableName).val(tableParams.search);
86          $("#no-results-special-"+ctx.tableName).show();
87          $("#results-found-"+ctx.tableName).hide();
88        } else {
89          $("#new-search-input-"+ctx.tableName).val(tableParams.search);
90          $("#no-results-"+ctx.tableName).show();
91        }
92      }
93      else {
94        /* No results caused by there being no data */
95        $("#empty-state-"+ctx.tableName).show();
96      }
97      table.trigger("table-done", [tableData.total, tableParams]);
98
99      return;
100    } else {
101      tableContainer.show();
102      $("#no-results-"+ctx.tableName).hide();
103      $("#empty-state-"+ctx.tableName).hide();
104    }
105
106    setupTableChrome(tableData);
107
108    /* Add table data rows */
109    var column_index;
110    for (var i in tableData.rows){
111      /* only display if the column is display-able */
112      var row = $("<tr></tr>");
113      column_index = -1;
114      for (var key_j in tableData.rows[i]){
115        var td = $("<td></td>");
116        td.prop("class", key_j);
117        if (tableData.rows[i][key_j]){
118          td.html(tableData.rows[i][key_j]);
119        }
120        row.append(td);
121      }
122      tableBody.append(row);
123
124      /* If we have layerbtns then initialise them */
125      layerBtnsInit();
126
127      /* If we have popovers initialise them now */
128      $('td > a.btn').popover({
129        html:true,
130        placement:'left',
131        container:'body',
132        trigger:'manual'
133      }).click(function(e){
134        $('td > a.btn').not(this).popover('hide');
135        /* ideally we would use 'toggle' here
136         * but it seems buggy in our Bootstrap version
137         */
138        $(this).popover('show');
139        e.stopPropagation();
140      });
141
142      /* enable help information tooltip */
143      $(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
144    }
145
146    /* Setup the pagination controls */
147
148    var start = tableParams.page - 2;
149    var end = tableParams.page + 2;
150    var numPages = Math.ceil(tableData.total/tableParams.limit);
151
152    if (numPages >  1){
153      if (tableParams.page < 3)
154        end = 5;
155
156      for (var page_i=1; page_i <= numPages;  page_i++){
157        if (page_i >= start && page_i <= end){
158          var btn = $('<li><a href="#" class="page">'+page_i+'</a></li>');
159
160          if (page_i === tableParams.page){
161            btn.addClass("active");
162          }
163
164          /* Add the click handler */
165          btn.click(pageButtonClicked);
166          paginationBtns.append(btn);
167        }
168      }
169    }
170
171    loadColumnsPreference();
172
173    table.css("padding-bottom", 0);
174    tableContainer.css("visibility", "visible");
175
176    /* If we have a hash in the url try and highlight that item in the table */
177    if (window.location.hash){
178      var highlight = $("table a[name="+window.location.hash.replace('#',''));
179      if (highlight.length > 0){
180        highlight.parents("tr").addClass('highlight');
181        window.scroll(0, highlight.position().top - 50);
182      }
183    }
184
185    table.trigger("table-done", [tableData.total, tableParams]);
186  }
187
188  function setupTableChrome(tableData){
189    if (tableChromeDone === true)
190      return;
191
192    var tableHeadRow = table.find("thead > tr");
193    var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol");
194
195    tableHeadRow.html("");
196    editColMenu.html("");
197
198    tableParams.default_orderby = tableData.default_orderby;
199
200    if (!tableParams.orderby && tableData.default_orderby){
201      tableParams.orderby = tableData.default_orderby;
202    }
203
204    /* Add table header and column toggle menu */
205    var column_edit_entries = [];
206    for (var i in tableData.columns){
207      var col = tableData.columns[i];
208      if (col.displayable === false) {
209        continue;
210      }
211      var header = $("<th></th>");
212      header.prop("class", col.field_name);
213
214      /* Setup the help text */
215      if (col.help_text.length > 0) {
216        var help_text = $('<span class="glyphicon glyphicon-question-sign get-help"> </span>');
217        help_text.tooltip({title: col.help_text});
218        header.append(help_text);
219      }
220
221      /* Setup the orderable title */
222      if (col.orderable) {
223        var title = $('<a href=\"#\" ></a>');
224
225        title.data('field-name', col.field_name);
226        title.attr('data-sort-field', col.field_name);
227        title.text(col.title);
228        title.click(sortColumnClicked);
229
230        header.append(title);
231
232        header.append(' <i class="icon-caret-down" style="display:none"></i>');
233        header.append(' <i class="icon-caret-up" style="display:none"></i>');
234
235        /* If we're currently ordered setup the visual indicator */
236        if (col.field_name === tableParams.orderby ||
237          '-' + col.field_name === tableParams.orderby){
238          header.children("a").addClass("sorted");
239
240          if (tableParams.orderby.indexOf("-") === -1){
241            header.find('.icon-caret-down').show();
242          } else {
243            header.find('.icon-caret-up').show();
244          }
245        }
246
247       if (col.field_name === tableData.default_orderby){
248         title.addClass("default-orderby");
249       }
250
251      } else {
252        /* Not orderable */
253        header.css("font-weight", "normal");
254        header.append('<span class="text-muted">' + col.title + '</span> ');
255      }
256
257      /* Setup the filter button */
258      if (col.filter_name){
259        var filterBtn = $('<a href="#" role="button" data-filter-on="' + col.filter_name + '" class="pull-right btn btn-link btn-xs" data-toggle="modal"><i class="glyphicon glyphicon-filter filtered"></i></a>');
260
261        filterBtn.data('filter-name', col.filter_name);
262        filterBtn.prop('id', col.filter_name);
263        filterBtn.click(filterOpenClicked);
264
265        /* If we're currently being filtered setup the visial indicator */
266        if (tableParams.filter &&
267            tableParams.filter.match('^'+col.filter_name)) {
268
269            filterBtnActive(filterBtn, true);
270        }
271        header.append(filterBtn);
272      }
273
274      /* Done making the header now add it */
275      tableHeadRow.append(header);
276
277      /* Now setup the checkbox state and click handler */
278      var toggler = $('<li><div class="checkbox"><label><input type="checkbox" id="checkbox-'+ col.field_name +'" class="col-toggle" value="'+col.field_name+'" />'+col.title+'</label></div></li>');
279
280      var togglerInput = toggler.find("input");
281
282      togglerInput.attr("checked","checked");
283
284      /* If we can hide the column enable the checkbox action */
285      if (col.hideable){
286        togglerInput.click(colToggleClicked);
287      } else {
288        toggler.find("label").addClass("text-muted");
289        toggler.find("label").parent().addClass("disabled");
290        togglerInput.attr("disabled", "disabled");
291      }
292
293      if (col.hidden) {
294        defaultHiddenCols.push(col.field_name);
295      }
296
297      /* Gather the Edit Column entries */
298      column_edit_entries.push({'title':col.title,'html':toggler});
299
300    } /* End for each column */
301
302    /* Append the sorted Edit Column toggler entries */
303    column_edit_entries.sort(function(a,b) {return (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0);} );
304    for (var col in column_edit_entries){
305      editColMenu.append(column_edit_entries[col].html);
306    }
307
308    tableChromeDone = true;
309  }
310
311  /* Toggles the active state of the filter button */
312  function filterBtnActive(filterBtn, active){
313    if (active) {
314      filterBtn.removeClass("btn-link");
315      filterBtn.addClass("btn-primary");
316
317      filterBtn.tooltip({
318          html: true,
319          title: '<button class="btn btn-sm btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>',
320          placement: 'bottom',
321          delay: {
322            hide: 1500,
323            show: 400,
324          },
325      });
326    } else {
327      filterBtn.removeClass("btn-primary");
328      filterBtn.addClass("btn-link");
329      filterBtn.tooltip('destroy');
330    }
331  }
332
333  /* Display or hide table columns based on the cookie preference or defaults */
334  function loadColumnsPreference(){
335    var cookie_data = $.cookie("cols");
336
337    if (cookie_data) {
338      var cols_hidden = JSON.parse($.cookie("cols"));
339
340      /* For each of the columns check if we should hide them
341       * also update the checked status in the Edit columns menu
342       */
343      $("#"+ctx.tableName+" th").each(function(){
344        for (var i in cols_hidden){
345          if ($(this).hasClass(cols_hidden[i])){
346            table.find("."+cols_hidden[i]).hide();
347            $("#checkbox-"+cols_hidden[i]).removeAttr("checked");
348          }
349        }
350      });
351      } else {
352        /* Disable these columns by default when we have no columns
353         * user setting.
354         */
355        for (var i in defaultHiddenCols) {
356          table.find("."+defaultHiddenCols[i]).hide();
357          $("#checkbox-"+defaultHiddenCols[i]).removeAttr("checked");
358        }
359    }
360  }
361
362  /* Apply an ordering to the current table.
363   *
364   * 1. Find the column heading matching the sortSpecifier
365   * 2. Set its up/down arrow and add .sorted
366   *
367   * orderby: e.g. "-started_on", "completed_on"
368   * colHeading: column heading element to activate (by showing the caret
369   * up/down, depending on sort order); if not set, the correct column
370   * heading is selected from the DOM using orderby as a key
371   */
372  function applyOrderby(orderby, colHeading) {
373    if (!orderby) {
374      return;
375    }
376
377    // We only have one sort at a time so remove existing sort indicators
378    $("#" + ctx.tableName + " th .icon-caret-down").hide();
379    $("#" + ctx.tableName + " th .icon-caret-up").hide();
380    $("#" + ctx.tableName + " th a").removeClass("sorted");
381
382    // normalise the orderby so we can use it to find the link we want
383    // to style
384    var fieldName = orderby;
385    if (fieldName.indexOf('-') === 0) {
386      fieldName = fieldName.slice(1);
387    }
388
389    // find the table header element which corresponds to the sort field
390    // (if we don't already have it)
391    if (!colHeading) {
392      colHeading = $('[data-sort-field="' + fieldName + '"]');
393    }
394
395    colHeading.addClass("sorted");
396
397    var parent = colHeading.parent();
398
399    if (orderby.indexOf('-') === 0) {
400      parent.children('.icon-caret-up').show();
401    }
402    else {
403      parent.children('.icon-caret-down').show();
404    }
405
406    tableParams.orderby = orderby;
407    loadData(tableParams);
408  }
409
410  function sortColumnClicked(e){
411    e.preventDefault();
412
413    /* if we're already sorted sort the other way */
414    var orderby = $(this).data('field-name');
415    if (tableParams.orderby === orderby &&
416        tableParams.orderby.indexOf('-') === -1) {
417      orderby = '-' + orderby;
418    }
419
420    applyOrderby(orderby, $(this));
421  }
422
423  function pageButtonClicked(e) {
424    tableParams.page = Number($(this).text());
425    loadData(tableParams);
426    /* Stop page jumps when clicking on # links */
427    e.preventDefault();
428  }
429
430  /* Toggle a table column */
431  function colToggleClicked (){
432    var col = $(this).val();
433    var disabled_cols = [];
434
435    if ($(this).prop("checked")) {
436      table.find("."+col).show();
437    }  else {
438      table.find("."+col).hide();
439      // If we're ordered by the column we're hiding remove the order by
440      // and apply the default one instead
441      if (col === tableParams.orderby ||
442          '-' + col === tableParams.orderby){
443        tableParams.orderby = null;
444
445        applyOrderby(tableParams.default_orderby);
446      }
447    }
448
449    /* Update the cookie with the unchecked columns */
450    $(".col-toggle").not(":checked").map(function(){
451      disabled_cols.push($(this).val());
452    });
453
454    $.cookie("cols", JSON.stringify(disabled_cols));
455  }
456
457  /**
458   * Create the DOM/JS for the client side of a TableFilterActionToggle
459   * or TableFilterActionDay
460   *
461   * filterName: (string) internal name for the filter action
462   * filterActionData: (object)
463   * filterActionData.count: (number) The number of items this filter will
464   * show when selected
465   *
466   * NB this triggers a filtervalue event each time its radio button is checked
467   */
468  function createActionRadio(filterName, filterActionData) {
469    var hasNoRecords = (Number(filterActionData.count) == 0);
470
471    var actionStr = '<div class="radio">' +
472      '<label class="filter-title' +
473      (hasNoRecords ? ' text-muted' : '') + '"' +
474      '       for="' + filterName + '">' +
475      '<input type="radio" name="filter"' +
476      '       value="' + filterName + '"';
477
478    if (hasNoRecords) {
479      actionStr += ' disabled="disabled"';
480    }
481
482    actionStr += ' id="' + filterName + '">' +
483      '<input type="hidden" name="filter_value" value="on"' +
484      '       data-value-for="' + filterName + '">' +
485      filterActionData.title +
486      ' (' + filterActionData.count + ')' +
487      '</label>' +
488      '</div>';
489
490    var action = $(actionStr);
491
492    // fire the filtervalue event from this action when the radio button
493    // is active so that the apply button can be enabled
494    action.find('[type="radio"]').change(function () {
495      if ($(this).is(':checked')) {
496        action.trigger('filtervalue', 'on');
497      }
498    });
499
500    return action;
501  }
502
503  /**
504   * Create the DOM/JS for the client side of a TableFilterActionDateRange
505   *
506   * filterName: (string) internal name for the filter action
507   * filterValue: (string) from,to date range in format yyyy-mm-dd,yyyy-mm-dd;
508   * used to select the current values for the from/to datepickers;
509   * if this is partial (e.g. "yyyy-mm-dd,") only the applicable datepicker
510   * will have a date pre-selected; if empty, neither will
511   * filterActionData: (object) data for generating the action's HTML
512   * filterActionData.title: label for the radio button
513   * filterActionData.max: (string) maximum date for the pickers, in ISO 8601
514   * datetime format
515   * filterActionData.min: (string) minimum date for the pickers, ISO 8601
516   * datetime
517   *
518   * NB this triggers a filtervalue event each time its radio button is checked
519   */
520  function createActionDateRange(filterName, filterValue, filterActionData) {
521    var action = $('<div class="radio">' +
522                   '<label class="filter-title"' +
523                   '       for="' + filterName + '">' +
524                   '<input type="radio" name="filter"' +
525                   '       value="' + filterName + '" ' +
526                   '       id="' + filterName + '">' +
527                   '<input type="hidden" name="filter_value" value=""' +
528                   '       data-value-for="' + filterName + '">' +
529                   filterActionData.title +
530                   '</label>' +
531									 '<div class="form-inline form-group date-filter-controls">' +
532                   '<input type="text" maxlength="10" class="form-control"' +
533                   '       data-date-from-for="' + filterName + '">' +
534                   '<span>to</span>' +
535                   '<input type="text" maxlength="10" class="form-control"' +
536                   '       data-date-to-for="' + filterName + '">' +
537                   '<span class="help-inline get-help">(yyyy-mm-dd)</span>' +
538                   '</div></div>');
539
540    var radio = action.find('[type="radio"]');
541    var value = action.find('[data-value-for]');
542
543    // make the datepickers for the range
544    var options = {
545      dateFormat: 'yy-mm-dd',
546      maxDate: new Date(filterActionData.max),
547      minDate: new Date(filterActionData.min)
548    };
549
550    // create date pickers, setting currently-selected from and to dates
551    var selectedFrom = null;
552    var selectedTo = null;
553
554    var selectedFromAndTo = [];
555    if (filterValue) {
556      selectedFromAndTo = filterValue.split(',');
557    }
558
559    if (selectedFromAndTo.length == 2) {
560      selectedFrom = selectedFromAndTo[0];
561      selectedTo = selectedFromAndTo[1];
562    }
563
564    options.defaultDate = selectedFrom;
565    var inputFrom =
566      action.find('[data-date-from-for]').datepicker(options);
567    inputFrom.val(selectedFrom);
568
569    options.defaultDate = selectedTo;
570    var inputTo =
571      action.find('[data-date-to-for]').datepicker(options);
572    inputTo.val(selectedTo);
573
574    // set filter_value based on date pickers when
575    // one of their values changes; if either from or to are unset,
576    // the new value is null;
577    // this triggers a 'filter_value-change' event on the action's element,
578    // which is used to determine the disabled/enabled state of the "Apply"
579    // button
580    var changeHandler = function () {
581      var fromValue = inputFrom.val();
582      var toValue = inputTo.val();
583
584      var newValue = undefined;
585      if (fromValue !== '' && toValue !== '') {
586        newValue = fromValue + ',' + toValue;
587      }
588
589      value.val(newValue);
590
591      // if this action is selected, fire an event for the new range
592      if (radio.is(':checked')) {
593        action.trigger('filtervalue', newValue);
594      }
595    };
596
597    inputFrom.change(changeHandler);
598    inputTo.change(changeHandler);
599
600    // check the associated radio button on clicking a date picker
601    var checkRadio = function () {
602      radio.prop('checked', 'checked');
603
604      // checking the radio button this way doesn't cause the "change"
605      // event to fire, so we manually call the changeHandler
606      changeHandler();
607    };
608
609    inputFrom.focus(checkRadio);
610    inputTo.focus(checkRadio);
611
612    // selecting a date in a picker constrains the date you can
613    // set in the other picker
614    inputFrom.change(function () {
615      inputTo.datepicker('option', 'minDate', inputFrom.val());
616    });
617
618    inputTo.change(function () {
619      inputFrom.datepicker('option', 'maxDate', inputTo.val());
620    });
621
622    // checking the radio input causes the "Apply" button disabled state to
623    // change, depending on which from/to dates are supplied
624    radio.change(changeHandler);
625
626    return action;
627  }
628
629  function filterOpenClicked(){
630    var filterName = $(this).data('filter-name');
631
632    /* We need to pass in the current search so that the filter counts take
633     * into account the current search term
634     */
635    var params = {
636      'name' : filterName,
637      'search': tableParams.search,
638      'cmd': 'filterinfo',
639    };
640
641    $.ajax({
642        type: "GET",
643        url: ctx.url,
644        data: params,
645        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
646        success: function (filterData) {
647          /*
648            filterData structure:
649
650            {
651              title: '<title for the filter popup>',
652              filter_actions: [
653                {
654                  title: '<label for radio button inside the popup>',
655                  name: '<name of the filter action>',
656                  count: <number of items this filter will show>,
657                  ... additional data for the action ...
658                }
659              ]
660            }
661
662            each filter_action gets a radio button; the value of this is
663            set to filterName + ':' + filter_action.name; e.g.
664
665              in_current_project:in_project
666
667            specifies the "in_project" action of the "in_current_project"
668            filter
669
670            the filterName is set on the column filter icon, and corresponds
671            to a value in the table's filter map
672
673            when the filter popup's "Apply" button is clicked, the
674            value for the radio button which is checked is passed in the
675            querystring, along with a filter_value, and applied to the
676            queryset on the table
677          */
678          var filterActionRadios = $('#filter-actions-' + ctx.tableName);
679          var filterApplyBtn = $('[data-cat="filter-apply"]');
680
681          var setApplyButtonState = function (e, filterActionValue) {
682            if (filterActionValue !== undefined) {
683              filterApplyBtn.removeAttr('disabled');
684            }
685            else {
686              filterApplyBtn.attr('disabled', 'disabled');
687            }
688          };
689
690          $('#filter-modal-title-' + ctx.tableName).text(filterData.title);
691
692          filterActionRadios.empty();
693
694          // create a radio button + form elements for each action associated
695          // with the filter on this column of the table
696          for (var i in filterData.filter_actions) {
697            var action = null;
698            var filterActionData = filterData.filter_actions[i];
699            var filterName = filterData.name + ':' +
700                             filterActionData.action_name;
701
702            if (filterActionData.type === 'toggle' ||
703                filterActionData.type === 'day') {
704              action = createActionRadio(filterName, filterActionData);
705            }
706            else if (filterActionData.type === 'daterange') {
707              // current values for the from/to dates
708              var filterValue = tableParams.filter_value;
709
710              action = createActionDateRange(
711                filterName,
712                filterValue,
713                filterActionData
714              );
715            }
716
717            if (action) {
718              // Setup the current selected filter; default to 'all' if
719              // no current filter selected
720              var radioInput = action.find('input[name="filter"]');
721              if ((tableParams.filter &&
722                  tableParams.filter === radioInput.val()) ||
723                  filterActionData.action_name == 'all') {
724                  radioInput.prop("checked", "checked");
725              }
726
727              filterActionRadios.append(action);
728
729              // if the action's filter_value changes but is falsy, disable
730              // the "Apply" button
731              action.on('filtervalue', setApplyButtonState);
732            }
733          }
734
735          $('#filter-modal-'+ctx.tableName).modal('show');
736        }
737    });
738  }
739
740  /* Allow pages to trigger reload event */
741  table.on('reload', function(e, newTableParams){
742    if (newTableParams)
743      loadData(newTableParams);
744    else
745      loadData(tableParams)
746  });
747
748  $(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
749
750  /* Keep the Edit columns menu open after click by eating the event */
751  $('.dropdown-menu').click(function(e) {
752    e.stopPropagation();
753  });
754
755  $(".pagesize-"+ctx.tableName).val(tableParams.limit);
756
757  /* page size selector  */
758  $(".pagesize-"+ctx.tableName).change(function(e){
759    tableParams.limit = Number(this.value);
760    if ((tableParams.page * tableParams.limit) > tableTotal)
761      tableParams.page = 1;
762
763    loadData(tableParams);
764    /* sync the other selectors on the page */
765    $(".pagesize-"+ctx.tableName).val(this.value);
766    e.preventDefault();
767  });
768
769  $("#search-submit-"+ctx.tableName).click(function(e){
770    e.preventDefault();
771    var searchTerm = $("#search-input-"+ctx.tableName).val();
772
773    tableParams.page = 1;
774    tableParams.search = searchTerm;
775
776    /* If a filter was active we remove it */
777    if (tableParams.filter) {
778      var filterBtn = $("#" + tableParams.filter.split(":")[0]);
779      filterBtnActive(filterBtn, false);
780      tableParams.filter = null;
781    }
782
783    loadData(tableParams);
784  });
785
786  clearSearchElements.click(function(e){
787    e.preventDefault();
788
789    tableParams.page = 1;
790    tableParams.search = null;
791    loadData(tableParams);
792
793    $("#search-input-"+ctx.tableName).val("");
794    $(this).hide();
795  });
796
797  $("#search-input-"+ctx.tableName).keyup(function(e){
798    if (e.which === 13)
799      $('#search-submit-'+ctx.tableName).click();
800  });
801
802  /* Stop page jumps when clicking on # links */
803  $('a[href="#"]').click(function(e){
804    e.preventDefault();
805  });
806
807  $("#clear-filter-btn-"+ctx.tableName).click(function(e){
808    e.preventDefault();
809
810    var filterBtn = $("#" + tableParams.filter.split(":")[0]);
811    filterBtnActive(filterBtn, false);
812
813    tableParams.filter = null;
814    loadData(tableParams);
815  });
816
817  $("#filter-modal-form-"+ctx.tableName).submit(function(e){
818    e.preventDefault();
819
820    /* remove active status from all filter buttons so that only one filter
821       can be active at a time */
822    $('[data-filter-on]').each(function (index, filterBtn) {
823      filterBtnActive($(filterBtn), false);
824    });
825
826    // checked radio button
827    var checkedFilter = $(this).find("input[name='filter']:checked");
828    tableParams.filter = checkedFilter.val();
829
830    // hidden field holding the value for the checked filter
831    var checkedFilterValue = $(this).find("input[data-value-for='" +
832                                          tableParams.filter + "']");
833    tableParams.filter_value = checkedFilterValue.val();
834
835    /* All === remove filter */
836    if (tableParams.filter.match(":all$")) {
837      tableParams.filter = null;
838      tableParams.filter_value = null;
839    } else {
840      var filterBtn = $("#" + tableParams.filter.split(":")[0]);
841      filterBtnActive(filterBtn, true);
842    }
843
844    loadData(tableParams);
845
846
847    $('#filter-modal-'+ctx.tableName).modal('hide');
848  });
849
850  table.on("table-loading", function(){
851    table.css("opacity", 0.5);
852  });
853
854  table.on("table-done", function(){
855    table.css("opacity", 1);
856  })
857}
858