Issues (789)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

www/js/bootstrap-ui.js (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
/*!
2
 * Bootstrap UI
3
 * Built on the shoulders of a giant: Bootstrap 3
4
 * http://www.bootstrap-ui.com
5
 *
6
 * Created by VisionApps (www.visionapps.cz)
7
 *
8
 * v2.3.2 (8 September 2016)
9
 */
10
;(function ($, window) {
11
  'use strict';
12
13
  // CKEDITOR-LOADER DATA-API
14
  // ========================
15
16
  (function ($, window) {
17
    // We have to use $(winodow).load() as $(document).ready() can not be triggered manually
18
    // and thus it would make it impossible to test this part of the code.
19
    $(window).on('load', function () {
20
      $('[data-onload-ckeditor]').each(function () {
21
        var language = $('html').attr('lang');
22
        var confObj = {};
23
        var $this = $(this);
24
        var confValue = $this.data('onload-ckeditor');
25
26
        if (confValue) {
27
          if (typeof confValue === 'object') {
28
            confObj = confValue;
29
          } else {
30
            confObj = { customConfig: confValue };
31
          }
32
        }
33
34
        if (language && !confObj.hasOwnProperty('language')) {
35
          confObj.language = language;
36
        }
37
38
        $this.ckeditor(confObj);
39
      });
40
    });
41
  }($, window));
42
43
}(jQuery, window));
44
45
;(function ($, window, document) {
46
  'use strict';
47
48
  // CONFIRMATION CLASS DEFINITION
49
  // =============================
50
51
  var Confirmation = function ($triggerEl, options) {
52
    options = $.extend({}, this.options, options);
53
    this.modal = this.getModal(
54
        options['confirm-message'],
55
        options['confirm-yes'],
56
        options['confirm-no']
57
    );
58
    this.$triggerEl = $triggerEl;
59
    this.callback = options.callback;
60
  };
61
62
  Confirmation.prototype.options = {
63
    'confirm-message': 'Are you sure?',
64
    'confirm-yes': 'Yes',
65
    'confirm-no': 'No',
66
    callback: function () {}, // Having empty callback is useless, it is here as a sane fallback for
67
    // tests
68
  };
69
70
  Confirmation.prototype.showConfirmation = function () {
71
    var $triggerEl = this.$triggerEl;
72
    var callback = this.callback;
73
    var $modal = this.modal.modal({
74
      keboard: false,
75
      backdrop: 'static',
76
    });
77
78
    $triggerEl.trigger('show.bui.confirmation');
79
    $triggerEl.on('rejected.bui.confirmation', function () {
80
      callback(false);
81
    });
82
83
    $triggerEl.on('confirmed.bui.confirmation', function () {
84
      callback(true);
85
    });
86
87
    $triggerEl.on('rejected.bui.confirmation confirmed.bui.confirmation', function () {
88
      $modal.on('hidden.bs.modal', function () {
89
        $(this).remove();
90
      });
91
92
      // The fade class is removed before hiding the modal to prevent the backdrop from staying
93
      // behond
94
      // Thats why there is no animation :(
95
      // http://stackoverflow.com/questions/22056147/bootstrap-modal-backdrop-remaining
96
      $modal.removeClass('fade').modal('hide');
97
      $triggerEl.off('rejected.bui.confirmation confirmed.bui.confirmation');
98
    });
99
100
    $modal.on('keydown.bui.confirmation', function (e) {
101
      if (e.keyCode === 27) { //escape
102
        $triggerEl.trigger('rejected.bui.confirmation');
103
      } else if (e.keyCode === 13) { //enter
104
        $triggerEl.trigger('confirmed.bui.confirmation');
105
      }
106
    });
107
108
    $modal
109
      .find('[data-confirmation=reject]')
110
      .on('click.bui.confirmation', function () {
111
        $triggerEl.trigger('rejected.bui.confirmation');
112
      });
113
114
    $modal
115
      .find('[data-confirmation=confirm]')
116
      .on('click.bui.confirmation', function () {
117
        $triggerEl.trigger('confirmed.bui.confirmation');
118
      });
119
  };
120
121
  Confirmation.prototype.getModal = function (message, yes, no) {
122
    return $('<div class="modal fade" tabindex="-1">' +
123
      '<div class="modal-dialog modal-sm">' +
124
      '<div class="modal-content">' +
125
      '<div class="modal-body">' + message + '</div>' +
126
      '<div class="modal-footer">' +
127
      '<button type="button" class="btn btn-default" data-confirmation="reject">' + no +
128
      '</button>' + '<button type="button" class="btn btn-primary" data-confirmation="confirm">' +
129
      yes + '</button></div></div></div></div>');
130
  };
131
132
  // CONFIRMATION PLUGIN DEFINITION
133
  // ==============================
134
135
  function Plugin(options) {
136
    return this.each(function () {
137
      var $this = $(this);
138
      var data = $this.data('bui.confirmation');
139
140
      if (!data) {
141
        data = new Confirmation($this, options);
142
        $this.data('bui.confirmation', data);
143
      }
144
145
      data.showConfirmation();
146
    });
147
  }
148
149
  var old = $.fn.confirmation;
150
151
  $.fn.confirmation = Plugin;
152
  $.fn.confirmation.Constructor = Confirmation;
153
154
  // CONFIRMATION NO CONFLICT
155
  // ========================
156
  $.fn.confirmation.noConflict = function () {
157
    $.fn.confirmation = old;
158
    return this;
159
  };
160
161
  // CONFIRMATION DATA-API
162
  // =====================
163
164
  $(document).on('click.bui.confirmation.data-api', '[data-toggle=confirm]',
165
    function (e, noConfirm) {
166
      var $this = $(this);
167
168
      if (!noConfirm) {
169
        e.preventDefault();
170
171
        Plugin.call($this, {
172
          'confirm-message': $this.data('confirm-message'),
173
          'confirm-yes': $this.data('confirm-yes'),
174
          'confirm-no': $this.data('confirm-no'),
175
          callback: function (result) {
176
            if (result) {
177
              $this.trigger('click.bui.confirmation.data-api', true);
178
            }
179
          },
180
        });
181
      }
182
    }
183
  );
184
185
}(jQuery, window, document));
186
187
;(function ($, moment, window) {
188
  'use strict';
189
190
  var DatetimePickerLoader = function ($element) {
191
    this.$element = $element;
192
  };
193
194
  DatetimePickerLoader.prototype.filterLocale = function (locale) {
195
    return moment.locale(locale);
196
  };
197
198
  DatetimePickerLoader.prototype.init = function (confObj) {
199
    confObj.locale = this.filterLocale(confObj.locale);
200
    this.$element.datetimepicker(confObj);
201
  };
202
203
  // We have to use $(winodow).load() as $(document).ready() can not be triggered manually
204
  // and thus it would make it impossible to test this part of the code.
205
  $(window).on('load', function () {
206
    var initComponentFn = function (inlineConf) {
207
      var datetimePickerLoader = new DatetimePickerLoader($(this));
208
      var conf = {
209
        allowInputToggle: true,
210
        sideBySide: true,
211
        locale: $('html').attr('lang'),
212
      };
213
214
      if (inlineConf) {
215
        $.extend(conf, inlineConf);
216
      }
217
218
      datetimePickerLoader.init(conf);
219
    };
220
221
    // CKEDITOR-LOADER DATA-API
222
    // ========================
223
224
    $('[data-onload-datetimepicker]').each(function () {
225
      initComponentFn.call(this, $(this).data('onload-datetimepicker'));
226
    });
227
  });
228
229
}(jQuery, moment, window));
0 ignored issues
show
The variable moment seems to be never declared. If this is a global, consider adding a /** global: moment */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
230
231
;(function ($, window, document) {
232
  'use strict';
233
234
  // DISABLE CLASS DEFINITION
235
  // ========================
236
  var Disable = function ($element) {
237
    this.$element = $element;
238
  };
239
240
  Disable.prototype.toggle = function () {
241
    $(document).trigger('toggle.bui.disable');
242
    this.$element.prop('disabled', !this.$element.prop('disabled'));
243
    $(document).trigger('toggled.bui.disable');
244
  };
245
246
  // DISABLE PLUGIN DEFINITION
247
  // =========================
248
249
  function Plugin() {
250
    this.each(function () {
251
      var $this = $(this);
252
      var data = $this.data('bui.disable');
253
254
      if (!data) {
255
        data = new Disable($this);
256
        $this.data('bui.disable', data);
257
      }
258
259
      data.toggle();
260
    });
261
262
    return this;
263
  }
264
265
  var old = $.fn.disable;
266
267
  $.fn.disable = Plugin;
268
  $.fn.disable.Constructor = Disable;
269
270
  // DISABLE NO CONFLICT
271
  // ===================
272
273
  $.fn.disable.noConflict = function () {
274
    $.fn.disable = old;
275
    return this;
276
  };
277
278
  // DISABLE DATA-API
279
  // ================
280
281
  (function (Plugin, $, window) {
282
    // We have to use $(window).load() as $(document).ready() can not be triggered manually
283
    // and thus it would make it impossible to test this part of the code.
284
    $(window).on('load', function () {
285
      var $controls = $('[data-toggle=disable]');
286
287
      $controls.each(function () {
288
        var $this = $(this);
289
        var eventType = $this.data('disable-event');
290
        if (!eventType) {
291
          eventType = 'change';
292
        }
293
294
        $this.on(eventType + '.bui.disable.data-api', function () {
295
          Plugin.call($($this.data('disable-target')));
296
        });
297
      });
298
    });
299
  }(Plugin, $, window));
300
301
}(jQuery, window, document));
302
303
;(function ($, window, document) {
304
  'use strict';
305
306
  // FILTERABLE CLASS DEFINITION
307
  // ===========================
308
309
  var Filterable = function ($filterable) {
310
    this.$filterable = $filterable;
311
  };
312
313
  Filterable.prototype.filter = function (fObjects) {
314
    var dataVal;
315
    var filterValCounter;
316
    var filterValLength;
317
    var filterVal;
318
    var filterOper;
319
    var dataValCounter;
320
    var dataValLength;
321
    var fObjCounter;
322
    var hideEl;
323
    var fObjectsLength;
324
325
    this.$filterable.show();
326
    if (fObjects && fObjects.length) {
327
      fObjectsLength = fObjects.length;
328
      for (fObjCounter = 0; fObjCounter < fObjectsLength; fObjCounter++) {
329
        filterVal = fObjects[fObjCounter]['filter-value'];
330
        filterOper = fObjects[fObjCounter]['filter-operator'];
331
        dataVal = this.$filterable.data(fObjects[fObjCounter]['filter-attrib']);
332
333
        if (dataVal !== null) {
334
          hideEl = false;
335
336
          filterValLength = filterVal.length;
337
          if (filterOper === 'subset') {
338
            for (filterValCounter = 0; filterValCounter < filterValLength; filterValCounter++) {
339
              if (dataVal.indexOf(filterVal[filterValCounter]) === -1) {
340
                hideEl = true;
341
                break;
342
              }
343
            }
344
          } else if (filterOper === 'intersect') {
345
            hideEl = true;
346
            if (typeof filterVal === 'string') {
347
              filterVal = [filterVal];
348
            }
349
350
            if (typeof dataVal === 'string') {
351
              dataVal = [dataVal];
352
            }
353
354
            filterValLength = filterVal.length;
355
            dataValLength = dataVal.length;
356
            for (filterValCounter = 0; filterValCounter < filterValLength; filterValCounter++) {
357
              for (dataValCounter = 0; dataValCounter < dataValLength; dataValCounter++) {
358
                if (dataVal[dataValCounter].toLowerCase().indexOf(filterVal[filterValCounter]
359
                        .toLowerCase()) !== -1) {
360
                  hideEl = false;
361
                  break;
362
                }
363
              }
364
            }
365
          } else if (
366
            filterOper === '=' && +dataVal !== +filterVal ||
367
            filterOper === '>=' && +dataVal < +filterVal ||
368
            filterOper === '<=' && +dataVal > +filterVal ||
369
            filterOper === '<' && +dataVal >= +filterVal ||
370
            filterOper === '>' && +dataVal <= +filterVal
371
          ) {
372
            hideEl = true;
373
          }
374
375
          if (hideEl === true) {
376
            this.$filterable.hide();
377
          }
378
        }
379
      }
380
    }
381
  };
382
383
  Filterable.prototype.resetFilter = function () {
384
    this.$filterable.show();
385
  };
386
387
  // FILTERABLE PLUGIN DEFINITION
388
  // ============================
389
390
  function Plugin(options) {
391
    if (this.length) {
392
      if (options === 'reset') {
393
        $(document).trigger('resetStart.bui.filterable');
394
      } else {
395
        $(document).trigger('filter.bui.filterable');
396
      }
397
398
      this.each(function () {
399
        var data;
400
        var $this = $(this);
401
402
        data = $this.data('bui.filterable');
403
        if (!data) {
404
          data = new Filterable($this);
405
          $this.data('bui.filterable', data);
406
        }
407
408
        if (options === 'reset') {
409
          data.resetFilter();
410
        } else {
411
          data.filter(options);
412
        }
413
      });
414
415
      if (options === 'reset') {
416
        $(document).trigger('resetEnd.bui.filterable');
417
      } else {
418
        $(document).trigger('filtered.bui.filterable');
419
      }
420
    }
421
422
    return this;
423
  }
424
425
  var old = $.fn.filterable;
426
427
  $.fn.filterable = Plugin;
428
  $.fn.filterable.Constructor = Filterable;
429
430
  // FILTERABLE NO CONFLICT
431
  // ======================
432
433
  $.fn.filterable.noConflict = function () {
434
    $.fn.filterable = old;
435
    return this;
436
  };
437
438
  // FILTERABLE DATA-API
439
  // ===================
440
441
  var lastEventTarget;
442
  var lastEventValue;
443
444
  $(window).on('load', function () {
445
    $.each($('form'), function () {
446
      var $this = $(this);
447
      var filterData;
448
449
      if ($this.data('filter-storage-id')) {
450
        var storageId = window.location.pathname + '|' + $this.data('filter-storage-id');
451
        if (window.sessionStorage.getItem(storageId)) {
452
          filterData = JSON.parse(window.sessionStorage.getItem(storageId));
453
          $.each(filterData, function () {
454
            $this
455
              .find('[data-filter-attrib=' + this['filter-attrib'] + ']')
456
              .val(this['filter-value']);
457
          });
458
459
          Plugin.call($($this.data('filter-target')), filterData);
460
        }
461
      }
462
    });
463
  });
464
465
  $(document).on(
466
    'keyup.bui.filterable.data-api change.bui.filterable.data-api',
467
    '[data-toggle=filter]',
468
    function (e) {
469
      var $filter = $(this).closest('form');
470
      var filterData = [];
471
472
      if (lastEventTarget !== e.target || lastEventTarget === e.target &&
473
        lastEventValue !== e.target.value) {
474
        $filter.find(':input').each(function () {
475
          var $this = $(this);
476
          if ($this.val() !== '' && $this.val() !== null) {
477
            filterData.push({
478
              'filter-attrib': $this.data('filter-attrib'),
479
              'filter-operator': $this.data('filter-operator'),
480
              'filter-value': $this.val(),
481
            });
482
          }
483
484
          if ($filter.data('filter-storage-id')) {
485
            window.sessionStorage.setItem(
486
              window.location.pathname + '|' + $filter.data('filter-storage-id'),
487
              JSON.stringify(filterData)
488
            );
489
          }
490
491
          Plugin.call($($filter.data('filter-target')), filterData);
492
        });
493
      }
494
495
      lastEventTarget = e.target;
496
      lastEventValue = e.target.value;
497
    }
498
  );
499
500
  $(document).on('click.bui.filterable.data-api', '[data-toggle="filter-reset"]', function () {
501
    var $filter = $(this).closest('form');
502
    var storageId = $filter.data('filter-storage-id');
503
504
    $filter[0].reset();
505
    if (storageId) {
506
      window.sessionStorage.removeItem(window.location.pathname + '|' + storageId);
507
    }
508
509
    Plugin.call($($filter.data('filter-target')), 'reset');
510
  });
511
512
}(jQuery, window, document));
513
514
;(function ($, window) {
515
  'use strict';
516
517
  // SELECT2-LOADER DATA-API
518
  // ========================
519
520
  (function ($, window) {
521
    // We have to use $(winodow).load() as $(document).ready() can not be triggered manually
522
    // and thus it would make it impossible to test this part of the code.
523
    $(window).on('load', function () {
524
      $('[data-onload-select2]').each(function () {
525
        var confObj = {};
526
        var $this = $(this);
527
        var confValue = $this.data('onload-select2');
528
        var formatFunc = function (option) {
529
          var $optionEl = $(option.element);
530
          var imageSrc = $optionEl.data('image-src');
531
          var imageSrcset = $optionEl.data('image-srcset');
532
          var imageHeight = $optionEl.data('image-height');
533
          var imageWidth = $optionEl.data('image-width');
534
          var attribs = '';
535
536
          if (imageSrc) {
537
            attribs = 'src="' + imageSrc + '" alt="' + option.text + '"';
538
            if (imageSrcset) {
539
              attribs = attribs + ' srcset="' + imageSrcset + '"';
540
            }
541
542
            if (imageHeight) {
543
              attribs = attribs + ' height="' + imageHeight + '"';
544
            }
545
546
            if (imageWidth) {
547
              attribs = attribs + ' width="' + imageWidth + '"';
548
            }
549
550
            return '<img ' + attribs + '>&nbsp;' + option.text;
551
          }
552
553
          return option.text;
554
        };
555
556
        if (confValue) {
557
          confObj = confValue;
558
        }
559
560
        confObj.formatSelection = formatFunc;
561
        confObj.formatResult = formatFunc;
562
        confObj.escapeMarkup = function (m) { return m; };
563
564
        $this.select2(confObj);
565
      });
566
    });
567
  }($, window));
568
569
}(jQuery, window));
570
571
;(function ($, window, document) {
572
  'use strict';
573
574
  // SLUGGER CLASS DEFINITION
575
  // ========================
576
  var Slugger = function ($source, options) {
577
    this.$source = $source;
578
    this.$target = options.target;
579
  };
580
581
  Slugger.prototype.updateSlug = function () {
582
    var generateSlug = function (str) {
583
      var from = 'ãàáäâåčçďẽèéëêìíïîñõòóöôřšťùúüûýž·/_,:;';
584
      var to   = 'aaaaaaccdeeeeeiiiinooooorstuuuuyz------';
585
586
      str = str
587
        .replace(/^\s+|\s+$/g, '') //trim
588
        .toLowerCase();
589
590
      for (var i = 0; i < from.length; i++) {
591
        str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
592
      }
593
594
      str = str
595
        .replace(/[^a-z0-9 -]/g, '') // remove invalid chars
596
        .replace(/\s+/g, '-') // collapse whitespace and replace by -
597
        .replace(/-+/g, '-'); // collapse dashes
598
599
      return str;
600
    };
601
602
    this.$target.val(generateSlug(this.$source.val()));
603
    this.$source.trigger('updated.bui.slugger');
604
  };
605
606
  // SLUGGER PLUGIN DEFINITION
607
  // =========================
608
609
  function Plugin(options) {
610
    this.each(function () {
611
      var $this = $(this);
612
      var data = $this.data('bui.slugger');
613
614
      if (!data) {
615
        data = new Slugger($this, options);
616
        $this.data('bui.slugger', data);
617
      }
618
619
      data.updateSlug();
620
    });
621
622
    return this;
623
  }
624
625
  var old = $.fn.slugger;
626
627
  $.fn.slugger = Plugin;
628
  $.fn.slugger.Constructor = Slugger;
629
630
  // SLUGGER NO CONFLICT
631
  // ===================
632
633
  $.fn.slugger.noConflict = function () {
634
    $.fn.slugger = old;
635
    return this;
636
  };
637
638
  // SLUGGER DATA-API
639
  // ================
640
641
  $(document)
642
    .on('keyup.bui.slugger.data-api', '[data-toggle=slugger]', function () {
643
      $('[data-toggle=slugger]').each(function () {
644
        var $this = $(this);
645
        Plugin.call($this, { target: $($this.data('slugger-target')) });
646
      });
647
    })
648
    .on('change.bui.slugger.data-api', '[data-toggle=slugger]', function () {
649
      $(this).trigger('changed.bui.slugger');
650
    });
651
652
}(jQuery, window, document));
653
654
;(function ($, window, document) {
655
  'use strict';
656
657
  // SORTABLE TABLE CLASS DEFINITION
658
  // ===============================
659
660
  var SortableTable = function ($sortedTable, $navigation) {
661
    this.$sortedTable = $sortedTable;
662
    this.$navigation = $navigation;
663
    if ($navigation) {
664
      this.colCount = $sortedTable.find('tr')[0].childElementCount;
665
    }
666
  };
667
668
  SortableTable.prototype.sort = function ($sortedTh, sortDir) {
669
    var sortGroup;
670
    var rowCounter;
671
    var rowsLength;
672
    var tableHtml;
673
    var row;
674
    var isNavigationCol;
675
    var rows;
676
    var newSortGroup = null;
677
    var navigationHtml = '';
678
    var isSortedAsc = $sortedTh.hasClass('sorting-asc');
679
680
    this.$sortedTable
681
      .trigger('sort.bui.sortableTable')
682
      .find('th')
683
      .removeClass('sorting-asc')
684
      .removeClass('sorting-desc');
685
686
    if (isSortedAsc || sortDir === 'desc') {
687
      sortDir = 'desc';
688
      $sortedTh.addClass('sorting-desc');
689
    } else {
690
      $sortedTh.addClass('sorting-asc');
691
    }
692
693
    rows = this.$sortedTable
694
      .find('tbody tr')
695
      .toArray()
696
      .sort(this.comparer($sortedTh.index(), sortDir));
697
698
    isNavigationCol = this.$navigation && typeof $(rows[0]).children('td').eq($sortedTh.index())
699
            .data('sort-group') !== 'undefined';
700
    tableHtml = '<thead>' + this.$sortedTable.find('thead:eq(0)').html() + '</thead>';
701
702
    rowsLength = rows.length;
703
    for (rowCounter = 0; rowCounter < rowsLength; rowCounter++) {
704
      row = rows[rowCounter];
705
      if (isNavigationCol) {
706
        sortGroup = $(row)
707
          .children('td')
708
          .eq($sortedTh.index())
709
          .data('sort-group');
710
711
        if (newSortGroup !== sortGroup) {
712
          newSortGroup = sortGroup;
713
          navigationHtml += '<li><a href="#letter-' + sortGroup + '">' + sortGroup + '</a></li>';
714
          tableHtml += '<thead><tr class="active"><th colspan="' + this.colCount + '">' +
715
            '<h2 class="h3" id="letter-' + newSortGroup + '">' + newSortGroup + '</h2>' +
716
            '</th></tr></thead><tbody>';
717
        }
718
      }
719
720
      tableHtml += row.outerHTML;
721
    }
722
723
    if (this.$navigation) {
724
      if (isNavigationCol) {
725
        navigationHtml = '<ul>' + navigationHtml + '</ul>';
726
      }
727
728
      this.$navigation.html(navigationHtml);
729
    }
730
731
    this.$sortedTable.html(tableHtml + '</tbody>');
732
    this.$sortedTable.trigger('sorted.bui.sortableTable');
733
  };
734
735
  SortableTable.prototype.comparer = function (index, sortDir) {
736
    return function (a, b) {
737
      var result;
738
      var valA;
739
      var valB;
740
      var getCellValue = function (row, index) {
741
        var cell = $(row).children('td').eq(index);
742
        if (cell.attr('data-sort-value')) {
743
          return cell.attr('data-sort-value');
744
        } else {
745
          return cell.text();
746
        }
747
      };
748
749
      valA = getCellValue(a, index);
750
      valB = getCellValue(b, index);
751
      if ($.isNumeric(valA) && $.isNumeric(valB)) {
752
        result = valA - valB;
753
      } else {
754
        try {
755
          result = valA.localeCompare(valB, $('html').attr('lang'));
756
        } catch (err) {
757
          if (err instanceof RangeError) {
758
            result = valA.localeCompare(valB);
759
          }
760
        }
761
      }
762
763
      return sortDir === 'desc' ? result * -1 : result;
764
    };
765
  };
766
767
  // SORTABLE TABLE PLUGIN DEFINITION
768
  // ================================
769
770
  function Plugin(options) {
771
    return this.each(function () {
772
      var $navigation;
773
      var $this = $(this);
774
      var data = $this.data('bui.sortableTable');
775
776
      if (!data) {
777
        $navigation = options && 'navigation' in options && options.navigation ?
778
            $(options.navigation) : false;
779
        data = new SortableTable($this, $navigation);
780
        $this.data('bui.sortableTable', data);
781
      }
782
783
      data.sort(options['sorted-th'], options['sort-direction']);
784
    });
785
  }
786
787
  var old = $.fn.sortableTable;
788
789
  $.fn.sortableTable = Plugin;
790
  $.fn.sortableTable.Constructor = SortableTable;
791
792
  // SORTABLE TABLE NO CONFLICT
793
  // ==========================
794
795
  $.fn.sortableTable.noConflict = function () {
796
    $.fn.sortableTable = old;
797
    return this;
798
  };
799
800
  // SORTABLE TABLE DATA-API
801
  // =======================
802
803
  (function (Plugin, $, window, document) {
804
    var callPlugin = function ($this) {
805
      var $sortedTable = $this.closest('table');
806
      Plugin.call($sortedTable, {
807
        'sorted-th': $this,
808
        navigation: $($sortedTable.data('sort-navigation')),
809
      });
810
    };
811
812
    $(document).on('click.bui.sortableTable.data-api', 'th[data-toggle=sort]', function () {
813
      callPlugin($(this));
814
    });
815
816
    $(document).on('keydown.bui.sortableTable.data-api', 'th[data-toggle=sort]', function (e) {
817
      if (e.keyCode === 13 || e.keyCode === 32) { //enter or space
818
        callPlugin($(this));
819
      }
820
    });
821
822
    // We have to use $(winodow).load() as $(document).ready() can not be triggered manually
823
    // and thus it would make it impossible to test this part of the code.
824
    $(window).on('load', function () {
825
      var $sortedTh = $('th[data-sort-onload]');
826
      $sortedTh.each(function (i) {
827
        var $sortedTable = $($sortedTh[i]).closest('table');
828
        Plugin.call($sortedTable, {
829
          'sorted-th': $($sortedTh[i]),
830
          navigation: $sortedTable.data('sort-navigation'),
831
          'sort-direction': $($sortedTh[i]).data('sort-onload'),
832
        });
833
      });
834
    });
835
  }(Plugin, $, window, document));
836
837
}(jQuery, window, document));
838
839
//# sourceMappingURL=bootstrap-ui.js.map