123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- /*! jquery-cascading-dropdown 1.2.9 | (c) 2019 Dzulqarnain Nasir <dzul1983@gmail.com> | MIT */
- (function ($, undefined) {
- 'use strict';
- var defaultOptions = {
- usePost: false,
- useJson: false
- };
- // Constructor
- function Dropdown(options, parent) {
- this.el = $(options.selector, parent.el);
- this.parent = parent;
- this.options = $.extend({}, defaultOptions, options);
- this.name = this.options.paramName || this.el.attr('name');
- this.requiredDropdowns = options.requires && options.requires.length ? $(options.requires.join(','), parent.el) : null;
- this.isLoadingClassName = this.options.isLoadingClassName || parent.options.isLoadingClassName || 'cascading-dropdown-loading';
- }
- // Methods
- Dropdown.prototype = {
- _create: function() {
- var self = this;
- self.pending = 0;
- self.initialised = false;
- self.initialState = self.el.clone(true);
- self.el.data('plugin_cascadingDropdown', this);
- self.originalDropdownItems = self.el.children('option');
- // Init event handlers
- if(typeof self.options.onChange === 'function') {
- self.el.change(function(event) {
- var requirementsMet = self._requirementsMet() && self.el[0].value;
- self.options.onChange.call(self, event, self.el.val(), self.getRequiredValues(), requirementsMet);
- });
- }
- if(typeof self.options.onEnable === 'function') {
- self.el.on('enabled', function(event) {
- self.options.onEnable.call(self, event, self.el.val());
- });
- }
- if(typeof self.options.onDisable === 'function') {
- self.el.on('disabled', function(event) {
- self.options.onDisable.call(self, event, self.el.val());
- });
- }
- if(self.requiredDropdowns) {
- self.requiredDropdowns.change(function(event) {
- self.update();
- });
- }
- // Init source
- self._initSource();
- // Call update
- self.update();
- },
-
- // Destroys the instance and reverts everything back to initial state
- _destroy: function() {
- this.el.replaceWith(this.initialState).removeData('plugin_cascadingDropdown');
- },
- // Enables the dropdown
- enable: function() {
- if(this.el.attr('disabled') === undefined) return;
- this.el.removeAttr('disabled').triggerHandler('enabled');
- },
- // Disables the dropdown
- disable: function() {
- if(this.el.attr('disabled') !== undefined) return;
- this.el.attr('disabled', 'disabled').triggerHandler('disabled');
- },
- // Checks if required dropdowns have value
- _requirementsMet: function() {
- var self = this;
- if(!self.requiredDropdowns) {
- return true;
- }
- if(self.options.requireAll) { // If requireAll is true, return true if all dropdowns have values
- return (self.requiredDropdowns.filter(function() {
- return !!$(this).val();
- }).length == self.options.requires.length);
- } else { // Otherwise, return true if any one of the required dropdowns has value
- return (self.requiredDropdowns.filter(function() {
- return !!$(this).val();
- }).length > 0);
- }
- },
- // Defines dropdown item list source - inspired by jQuery UI Autocomplete
- _initSource: function() {
- var self = this;
- if($.isArray(self.options.source)) {
- this.source = function(request, response) {
- response($.map(self.options.source, function(item) {
- return {
- label: item.label || item.value || item,
- value: item.value || item.label || item,
- selected: item.selected
- };
- }));
- };
- } else if ( typeof self.options.source === 'string' ) {
- var url = self.options.source;
- this.source = function(request, response) {
- if ( self.xhr ) {
- self.xhr.abort();
- }
- self.xhr = $.ajax({
- url: url,
- data: self.options.useJson ? JSON.stringify(request) : request,
- dataType: self.options.useJson ? 'json' : undefined,
- type: self.options.usePost ? 'post' : 'get',
- contentType: 'application/json; charset=utf-8',
- success: function(data) {
- response(data);
- },
- error: function() {
- response([]);
- }
- });
- };
- } else {
- this.source = self.options.source;
- }
- },
- getRequiredValues: function() {
- var data = {};
- if(this.requiredDropdowns) {
- $.each(this.requiredDropdowns, function() {
- var instance = $(this).data('plugin_cascadingDropdown');
- if(instance.name) {
- data[instance.name] = instance.el.val();
- }
- });
- }
- return data;
- },
- // Update the dropdown
- update: function() {
- var self = this;
- // Disable it first
- self.disable();
- // If required dropdowns have no value, return
- if(!self._requirementsMet()) {
- self.setSelected(0);
- self._triggerReady();
- return self.el;
- }
- // If source isn't defined, it's most likely a static dropdown, so just enable it
- if(!self.source) {
- self.enable();
- self._triggerReady();
- return self.el;
- }
- // Reset the dropdown value so we don't trigger a false call
- self.el.val('').change();
- // Fetch data from required dropdowns
- var data = self.getRequiredValues();
- // Pass it to defined source for processing
- self.pending++;
- self.el.addClass(self.isLoadingClassName);
- self.source(data, self._response());
- return self.el;
- },
- _response: function(items) {
- var self = this;
- return function(items) {
- self._renderItems(items);
- self.pending--;
- if(!self.pending) {
- self.el.removeClass(self.isLoadingClassName);
- }
- }
- },
- // Render the dropdown items
- _renderItems: function(items) {
- var self = this;
- // Remove all dropdown items and restore to initial state
- self.el.find('option, optgroup').remove();
- self.el.append(self.originalDropdownItems);
- if(!items) {
- self._triggerReady();
- return;
- }
- var selected = [];
- if ($.isArray(items)) {
- $.each(items, function(index, item) {
- self.el.append(self._renderItem(item));
- if (item.selected) selected.push(item.value.toString());
- });
- } else {
- $.each(items, function(key, value) {
- var itemData = [];
- itemData.push('<optgroup label="' + key + '">');
- for (var i = 0; i < value.length; i++) {
- var item = value[i];
- itemData.push(self._renderItem(item));
- if (item.selected) selected.push(item.value.toString());
- }
- itemData.push('</optgroup>');
- self.el.append(itemData.join(''));
- });
- }
- // Enable the dropdown
- self.enable();
- // If a selected item exists, set it as default
- selected.length && self.setSelected(selected);
- self._triggerReady();
- },
- _renderItem: function(item) {
- return '<option value="' + item.value + '"' + (item.selected ? ' selected' : '') + '>' + item.label + '</option>';
- },
- // Trigger the ready event when instance is initialised for the first time
- _triggerReady: function() {
- if(this.initialised) return;
- // Set selected dropdown item if defined
- this.options.selected && this.setSelected(this.options.selected);
- this.initialised = true;
- this.el.triggerHandler('ready');
- },
- // Sets the selected dropdown item
- setSelected: function(indexOrValue, triggerChange) {
- var self = this,
- dropdownItems = self.el.find('option');
- // Trigger change event by default
- if(typeof triggerChange === 'undefined') {
- triggerChange = true;
- }
-
- var selectedItems = [];
-
- // check if indexOrValue is an array
- if($.isArray(indexOrValue)) {
- selectedItems = indexOrValue;
- } else {
- selectedItems = selectedItems.concat(indexOrValue);
- }
- var selectedValue;
- if(self.el.is('[multiple]')) {
- selectedValue = selectedItems.map(function(item) {
- if(typeof item === 'number'
- && (item !== undefined
- && item > -1
- && item < dropdownItems.length)) {
- return dropdownItems[item].value
- }
-
- return item;
- });
- } else {
- selectedValue = selectedItems[0];
- // if selected item is a number, get the value for the item at that index
- if(typeof selectedItems[0] === 'number'
- && (selectedItems[0] !== undefined
- && selectedItems[0] > -1
- && selectedItems[0] < dropdownItems.length)) {
- selectedValue = dropdownItems[selectedItems[0]].value;
- }
- }
-
- // Set the dropdown item
- self.el.val(selectedValue);
- // Trigger change event
- if(triggerChange) {
- self.el.change();
- }
- return self.el;
- }
- };
- function CascadingDropdown(element, options) {
- this.el = $(element);
- this.options = $.extend({ selectBoxes: [] }, options);
- this._init();
- }
- CascadingDropdown.prototype = {
- _init: function() {
- var self = this;
- self.pending = 0;
- // Instance array
- self.dropdowns = [];
-
- var dropdowns = $($.map(self.options.selectBoxes, function(item) {
- return item.selector;
- }).join(','), self.el);
- // Init event handlers
- var counter = 0;
- function readyEventHandler(event) {
- if(++counter == dropdowns.length) { // Once all dropdowns are ready, unbind the event handler, and execute onReady
- dropdowns.unbind('ready', readyEventHandler);
- self.options.onReady.call(self, event, self.getValues());
- }
- }
- function changeEventHandler(event) {
- self.options.onChange.call(self, event, self.getValues());
- }
-
- if(typeof self.options.onReady === 'function') {
- dropdowns.bind('ready', readyEventHandler);
- }
- if(typeof self.options.onChange === 'function') {
- dropdowns.bind('change', changeEventHandler);
- }
- // Init dropdowns
- $.each(self.options.selectBoxes, function(index, item) {
- // Create the instance
- var instance = new Dropdown(this, self);
- // Insert it into the dropdown instance array
- self.dropdowns.push(instance);
- // Call the create method
- instance._create();
- });
- },
-
- // Destroys the instance and reverts everything back to its initial state
- destroy: function() {
- $.each(this.dropdowns, function(index, item){
- item._destroy();
- });
- this.el.removeData('plugin_cascadingDropdown');
-
- return this.el;
- },
- // Fetches the values from all dropdowns in this group
- getValues: function() {
- var values = {};
- // Build the object and insert values from instances with name
- $.each(this.dropdowns, function(index, instance) {
- if(instance.name) {
- values[instance.name] = instance.el.val();
- }
- });
- return values;
- }
- }
- // jQuery plugin declaration
- $.fn.cascadingDropdown = function(methodOrOptions) {
- var $this = $(this),
- args = arguments,
- instance = $this.data('plugin_cascadingDropdown');
- if(typeof methodOrOptions === 'object' || !methodOrOptions) {
- return !instance && $this.data('plugin_cascadingDropdown', new CascadingDropdown(this, methodOrOptions));
- } else if(typeof methodOrOptions === 'string') {
- if(!instance) {
- $.error('Cannot call method ' + methodOrOptions + ' before init.');
- } else if(instance[methodOrOptions]) {
- return instance[methodOrOptions].apply(instance, Array.prototype.slice.call(args, 1))
- }
- } else {
- $.error('Method ' + methodOrOptions + ' does not exist in jQuery.cascadingDropdown');
- }
- };
- })(jQuery);
|