blob: f18034df5248de6c70fd6620a5c1c9e145f6c3cb [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001'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 };
19
20 var defaultHiddenCols = [];
21
22 var table = $("#" + ctx.tableName);
23
24 /* if we're loading clean from a url use it's parameters as the default */
25 var urlParams = libtoaster.parseUrlParams();
26
27 /* Merge the tableParams and urlParams object properties */
28 tableParams = $.extend(tableParams, urlParams);
29
30 /* Now fix the types that .extend changed for us */
31 tableParams.limit = Number(tableParams.limit);
32 tableParams.page = Number(tableParams.page);
33
34 loadData(tableParams);
35
36 window.onpopstate = function(event){
37 if (event.state){
38 tableParams = event.state.tableParams;
39 /* We skip loadData and just update the table */
40 updateTable(event.state.tableData);
41 }
42 };
43
44 function loadData(tableParams){
45 $.ajax({
46 type: "GET",
47 url: ctx.url,
48 data: tableParams,
49 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
50 success: function(tableData) {
51 updateTable(tableData);
52 window.history.pushState({
53 tableData: tableData,
54 tableParams: tableParams
55 }, null, libtoaster.dumpsUrlParams(tableParams));
56 }
57 });
58 }
59
60 function updateTable(tableData) {
61 var tableBody = table.children("tbody");
62 var pagination = $('#pagination-'+ctx.tableName);
63 var paginationBtns = pagination.children('ul');
64 var tableContainer = $("#table-container-"+ctx.tableName);
65
66 tableContainer.css("visibility", "hidden");
67 /* To avoid page re-layout flicker when paging set fixed height */
68 table.css("padding-bottom", table.height());
69
70 /* Reset table components */
71 tableBody.html("");
72 paginationBtns.html("");
73
74 if (tableParams.search)
75 $('.remove-search-btn-'+ctx.tableName).show();
76 else
77 $('.remove-search-btn-'+ctx.tableName).hide();
78
79 $('.table-count-' + ctx.tableName).text(tableData.total);
80 tableTotal = tableData.total;
81
82 if (tableData.total === 0){
83 tableContainer.hide();
84 /* If we were searching show the new search bar and return */
85 if (tableParams.search){
86 $("#new-search-input-"+ctx.tableName).val(tableParams.search);
87 $("#no-results-"+ctx.tableName).show();
88 }
89 table.trigger("table-done", [tableData.total, tableParams]);
90
91 return;
92
93 /* We don't want to clutter the place with the table chrome if there
94 * are only a few results */
95 } else if (tableData.total <= 10 &&
96 !tableParams.filter &&
97 !tableParams.search){
98 $("#table-chrome-"+ctx.tableName).hide();
99 pagination.hide();
100 } else {
101 tableContainer.show();
102 $("#no-results-"+ctx.tableName).hide();
103 }
104
105 setupTableChrome(tableData);
106
107 /* Add table data rows */
108 var column_index;
109 for (var i in tableData.rows){
110 /* only display if the column is display-able */
111 var row = $("<tr></tr>");
112 column_index = -1;
113 for (var key_j in tableData.rows[i]){
114
115 /* if we have a static: version of a key, prefer the static: version for rendering */
116 var orig_key_j = key_j;
117
118 if (key_j.indexOf("static:") === 0) {
119 if (key_j.substr("static:".length) in tableData.rows[i]) {
120 continue;
121 }
122 orig_key_j = key_j.substr("static:".length)
123 } else if (("static:" + key_j) in tableData.rows[i]) {
124 key_j = "static:" + key_j;
125 }
126
127 /* we skip over un-displayable column entries */
128 column_index += 1;
129 if (! tableData.columns[column_index].displayable) {
130 continue;
131 }
132
133 var td = $("<td></td>");
134 td.prop("class", orig_key_j);
135 if (tableData.rows[i][key_j]){
136 td.html(tableData.rows[i][key_j]);
137 }
138 row.append(td);
139 }
140 tableBody.append(row);
141
142 /* If we have layerbtns then initialise them */
143 layerBtnsInit(ctx);
144
145 /* If we have popovers initialise them now */
146 $('td > a.btn').popover({
147 html:true,
148 placement:'left',
149 container:'body',
150 trigger:'manual'
151 }).click(function(e){
152 $('td > a.btn').not(this).popover('hide');
153 /* ideally we would use 'toggle' here
154 * but it seems buggy in our Bootstrap version
155 */
156 $(this).popover('show');
157 e.stopPropagation();
158 });
159
160 /* enable help information tooltip */
161 $(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
162 }
163
164 /* Setup the pagination controls */
165
166 var start = tableParams.page - 2;
167 var end = tableParams.page + 2;
168 var numPages = Math.ceil(tableData.total/tableParams.limit);
169
170 if (numPages > 1){
171 if (tableParams.page < 3)
172 end = 5;
173
174 for (var page_i=1; page_i <= numPages; page_i++){
175 if (page_i >= start && page_i <= end){
176 var btn = $('<li><a href="#" class="page">'+page_i+'</a></li>');
177
178 if (page_i === tableParams.page){
179 btn.addClass("active");
180 }
181
182 /* Add the click handler */
183 btn.click(pageButtonClicked);
184 paginationBtns.append(btn);
185 }
186 }
187 }
188
189 loadColumnsPreference();
190
191 table.css("padding-bottom", 0);
192 tableContainer.css("visibility", "visible");
193
194 table.trigger("table-done", [tableData.total, tableParams]);
195 }
196
197 function setupTableChrome(tableData){
198 if (tableChromeDone === true)
199 return;
200
201 var tableHeadRow = table.find("thead");
202 var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol");
203
204 tableHeadRow.html("");
205 editColMenu.html("");
206
207 if (!tableParams.orderby && tableData.default_orderby){
208 tableParams.orderby = tableData.default_orderby;
209 }
210
211 /* Add table header and column toggle menu */
212 for (var i in tableData.columns){
213 var col = tableData.columns[i];
214 if (col.displayable === false) {
215 continue;
216 }
217 var header = $("<th></th>");
218 header.prop("class", col.field_name);
219
220 /* Setup the help text */
221 if (col.help_text.length > 0) {
222 var help_text = $('<i class="icon-question-sign get-help"> </i>');
223 help_text.tooltip({title: col.help_text});
224 header.append(help_text);
225 }
226
227 /* Setup the orderable title */
228 if (col.orderable) {
229 var title = $('<a href=\"#\" ></a>');
230
231 title.data('field-name', col.field_name);
232 title.text(col.title);
233 title.click(sortColumnClicked);
234
235 header.append(title);
236
237 header.append(' <i class="icon-caret-down" style="display:none"></i>');
238 header.append(' <i class="icon-caret-up" style="display:none"></i>');
239
240 /* If we're currently ordered setup the visual indicator */
241 if (col.field_name === tableParams.orderby ||
242 '-' + col.field_name === tableParams.orderby){
243 header.children("a").addClass("sorted");
244
245 if (tableParams.orderby.indexOf("-") === -1){
246 header.find('.icon-caret-down').show();
247 } else {
248 header.find('.icon-caret-up').show();
249 }
250 }
251
252 } else {
253 /* Not orderable */
254 header.addClass("muted");
255 header.css("font-weight", "normal");
256 header.append(col.title+' ');
257 }
258
259 /* Setup the filter button */
260 if (col.filter_name){
261 var filterBtn = $('<a href="#" role="button" class="pull-right btn btn-mini" data-toggle="modal"><i class="icon-filter filtered"></i></a>');
262
263 filterBtn.data('filter-name', col.filter_name);
264 filterBtn.prop('id', col.filter_name);
265 filterBtn.click(filterOpenClicked);
266
267 /* If we're currently being filtered setup the visial indicator */
268 if (tableParams.filter &&
269 tableParams.filter.match('^'+col.filter_name)) {
270
271 filterBtnActive(filterBtn, true);
272 }
273 header.append(filterBtn);
274 }
275
276 /* Done making the header now add it */
277 tableHeadRow.append(header);
278
279 /* Now setup the checkbox state and click handler */
280 var toggler = $('<li><label class="checkbox">'+col.title+'<input type="checkbox" id="checkbox-'+ col.field_name +'" class="col-toggle" value="'+col.field_name+'" /></label></li>');
281
282 var togglerInput = toggler.find("input");
283
284 togglerInput.attr("checked","checked");
285
286 /* If we can hide the column enable the checkbox action */
287 if (col.hideable){
288 togglerInput.click(colToggleClicked);
289 } else {
290 toggler.find("label").addClass("muted");
291 togglerInput.attr("disabled", "disabled");
292 }
293
294 if (col.hidden) {
295 defaultHiddenCols.push(col.field_name);
296 }
297
298 editColMenu.append(toggler);
299 } /* End for each column */
300
301 tableChromeDone = true;
302 }
303
304 /* Toggles the active state of the filter button */
305 function filterBtnActive(filterBtn, active){
306 if (active) {
307 filterBtn.addClass("btn-primary");
308
309 filterBtn.tooltip({
310 html: true,
311 title: '<button class="btn btn-small btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>',
312 placement: 'bottom',
313 delay: {
314 hide: 1500,
315 show: 400,
316 },
317 });
318 } else {
319 filterBtn.removeClass("btn-primary");
320 filterBtn.tooltip('destroy');
321 }
322 }
323
324 /* Display or hide table columns based on the cookie preference or defaults */
325 function loadColumnsPreference(){
326 var cookie_data = $.cookie("cols");
327
328 if (cookie_data) {
329 var cols_hidden = JSON.parse($.cookie("cols"));
330
331 /* For each of the columns check if we should hide them
332 * also update the checked status in the Edit columns menu
333 */
334 $("#"+ctx.tableName+" th").each(function(){
335 for (var i in cols_hidden){
336 if ($(this).hasClass(cols_hidden[i])){
337 $("."+cols_hidden[i]).hide();
338 $("#checkbox-"+cols_hidden[i]).removeAttr("checked");
339 }
340 }
341 });
342 } else {
343 /* Disable these columns by default when we have no columns
344 * user setting.
345 */
346 for (var i in defaultHiddenCols) {
347 $("."+defaultHiddenCols[i]).hide();
348 $("#checkbox-"+defaultHiddenCols[i]).removeAttr("checked");
349 }
350 }
351 }
352
353 function sortColumnClicked(){
354
355 /* We only have one sort at a time so remove any existing sort indicators */
356 $("#"+ctx.tableName+" th .icon-caret-down").hide();
357 $("#"+ctx.tableName+" th .icon-caret-up").hide();
358 $("#"+ctx.tableName+" th a").removeClass("sorted");
359
360 var fieldName = $(this).data('field-name');
361
362 /* if we're already sorted sort the other way */
363 if (tableParams.orderby === fieldName &&
364 tableParams.orderby.indexOf('-') === -1) {
365 tableParams.orderby = '-' + $(this).data('field-name');
366 $(this).parent().children('.icon-caret-up').show();
367 } else {
368 tableParams.orderby = $(this).data('field-name');
369 $(this).parent().children('.icon-caret-down').show();
370 }
371
372 $(this).addClass("sorted");
373
374 loadData(tableParams);
375 }
376
377 function pageButtonClicked(e) {
378 tableParams.page = Number($(this).text());
379 loadData(tableParams);
380 /* Stop page jumps when clicking on # links */
381 e.preventDefault();
382 }
383
384 /* Toggle a table column */
385 function colToggleClicked (){
386 var col = $(this).val();
387 var disabled_cols = [];
388
389 if ($(this).prop("checked")) {
390 $("."+col).show();
391 } else {
392 $("."+col).hide();
393 /* If we're ordered by the column we're hiding remove the order by */
394 if (col === tableParams.orderby ||
395 '-' + col === tableParams.orderby){
396 tableParams.orderby = null;
397 loadData(tableParams);
398 }
399 }
400
401 /* Update the cookie with the unchecked columns */
402 $(".col-toggle").not(":checked").map(function(){
403 disabled_cols.push($(this).val());
404 });
405
406 $.cookie("cols", JSON.stringify(disabled_cols));
407 }
408
409 function filterOpenClicked(){
410 var filterName = $(this).data('filter-name');
411
412 /* We need to pass in the curren search so that the filter counts take
413 * into account the current search filter
414 */
415 var params = {
416 'name' : filterName,
417 'search': tableParams.search,
418 'cmd': 'filterinfo',
419 };
420
421 $.ajax({
422 type: "GET",
423 url: ctx.url,
424 data: params,
425 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
426 success: function (filterData) {
427 var filterActionRadios = $('#filter-actions-'+ctx.tableName);
428
429 $('#filter-modal-title-'+ctx.tableName).text(filterData.title);
430
431 filterActionRadios.text("");
432
433 for (var i in filterData.filter_actions){
434 var filterAction = filterData.filter_actions[i];
435
436 var action = $('<label class="radio"><input type="radio" name="filter" value=""><span class="filter-title"></span></label>');
437 var actionTitle = filterAction.title + ' (' + filterAction.count + ')';
438
439 var radioInput = action.children("input");
440
441 if (Number(filterAction.count) == 0){
442 radioInput.attr("disabled", "disabled");
443 }
444
445 action.children(".filter-title").text(actionTitle);
446
447 radioInput.val(filterName + ':' + filterAction.name);
448
449 /* Setup the current selected filter, default to 'all' if
450 * no current filter selected.
451 */
452 if ((tableParams.filter &&
453 tableParams.filter === radioInput.val()) ||
454 filterAction.name == 'all') {
455 radioInput.attr("checked", "checked");
456 }
457
458 filterActionRadios.append(action);
459 }
460
461 $('#filter-modal-'+ctx.tableName).modal('show');
462 }
463 });
464 }
465
466
467 $(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
468
469 /* Keep the Edit columns menu open after click by eating the event */
470 $('.dropdown-menu').click(function(e) {
471 e.stopPropagation();
472 });
473
474 $(".pagesize-"+ctx.tableName).val(tableParams.limit);
475
476 /* page size selector */
477 $(".pagesize-"+ctx.tableName).change(function(e){
478 tableParams.limit = Number(this.value);
479 if ((tableParams.page * tableParams.limit) > tableTotal)
480 tableParams.page = 1;
481
482 loadData(tableParams);
483 /* sync the other selectors on the page */
484 $(".pagesize-"+ctx.tableName).val(this.value);
485 e.preventDefault();
486 });
487
488 $("#search-submit-"+ctx.tableName).click(function(e){
489 var searchTerm = $("#search-input-"+ctx.tableName).val();
490
491 tableParams.page = 1;
492 tableParams.search = searchTerm;
493
494 /* If a filter was active we remove it */
495 if (tableParams.filter) {
496 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
497 filterBtnActive(filterBtn, false);
498 tableParams.filter = null;
499 }
500
501 loadData(tableParams);
502
503 e.preventDefault();
504 });
505
506 $('.remove-search-btn-'+ctx.tableName).click(function(e){
507 e.preventDefault();
508
509 tableParams.page = 1;
510 tableParams.search = null;
511 loadData(tableParams);
512
513 $("#search-input-"+ctx.tableName).val("");
514 $(this).hide();
515 });
516
517 $("#search-input-"+ctx.tableName).keyup(function(e){
518 if (e.which === 13)
519 $('#search-submit-'+ctx.tableName).click();
520 });
521
522 /* Stop page jumps when clicking on # links */
523 $('a[href="#"]').click(function(e){
524 e.preventDefault();
525 });
526
527 $("#clear-filter-btn-"+ctx.tableName).click(function(){
528 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
529 filterBtnActive(filterBtn, false);
530
531 tableParams.filter = null;
532 loadData(tableParams);
533 });
534
535 $("#filter-modal-form-"+ctx.tableName).submit(function(e){
536 e.preventDefault();
537
538 tableParams.filter = $(this).find("input[type='radio']:checked").val();
539
540 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
541
542 /* All === remove filter */
543 if (tableParams.filter.match(":all$")) {
544 tableParams.filter = null;
545 filterBtnActive(filterBtn, false);
546 } else {
547 filterBtnActive(filterBtn, true);
548 }
549
550 loadData(tableParams);
551
552 $(this).parent().modal('hide');
553 });
554}