📜 ⬆️ ⬇️

Chosen drop-down list widget: implement dynamic position adding

Based on the topic Chosen: make drop-down lists more friendly .

Pretty nice widget, sometimes it can even be useful (let's say when there are certain requirements for the design). But at the moment the widget will not allow to add positions dynamically, which angered comrade alexsrdk , and me too :) Now let's try to fix this.

Sources are presented in raw form, they were written quickly, I think there is something to refactor, I did not waste time on it, since the article was written for educational purposes, to show an approach to solving the problem (which, incidentally, does not exclude the possibility of practical use).

Option with jQuery version


Patch widget code

In the jQuery version I did not find a way to get to the main class of the widget “Chosen”, so I had to patch the main source of the widget for a bit (I understand that this is a bad approach).
')
You can come up with a lot of options to get to the Chosen class, I have at least 3 thoughts:
  1. Change the custom body of jQuery.fn.chosen function in a similar way
    $.fn.extend({
    chosen: function (data, options) {
    var createdInstances = [];
    $( this ).each( function (input_field) {
    if (!($( this )).hasClass( "chzn-done" )) {
    createdInstances.push( new Chosen( this , data, options));
    }
    });
    return createdInstances;
    }
    });


    * This source code was highlighted with Source Code Highlighter .

  2. You can save the created instance in the item repository.
    $.fn.extend({
    chosen: function (data, options) {
    return $( this ).each( function (input_field) {
    if (!($( this )).hasClass( "chzn-done" )) {
    return $( this ).data( 'chosenInstance' , new Chosen( this , data, options));
    }
    });
    }
    });


    * This source code was highlighted with Source Code Highlighter .

    You can get the Chosen object in a similar way.
    var createdChosenInstance = $ ( '#bears_multiple' ) .chosen (). data ( 'chosenInstance' );

    * This source code was highlighted with Source Code Highlighter .

  3. You can make a separate function to get the class
    $.fn.extend({
    chosenClass: function () {
    return Chosen;
    }
    });


    * This source code was highlighted with Source Code Highlighter .


I use the jQuery library very rarely (I mostly work with YUI), with the API at hand, so these options are probably not optimal.

In the following code, the third option is used to access the widget class. Modification of the Chosen class will occur at the prototype level (the methods will be common, that is, changes will affect all newly created class instances). In principle, it is possible to expand already created objects (obtaining created objects by options 1 or 2), but if the changes should affect all widgets, it is better to work with the prototype.

The main code for expanding the functional jQuery version of the widget

( function ($, ChosenClass) {
var dynamicItemInstance;

function DynamicItem(chosenInstance) {
$(( this .chosen = chosenInstance).search_results).parent().prepend(
this .elContainer = $( document .createElement( 'div' )));
this .elContainer.addClass( 'chzn-results-additemcontainer' );
this .elContainer.append( this .elButton = $( document .createElement( 'button' )));
this .elButton.click($.proxy( this .addNewItem, this ));
}
DynamicItem.prototype = {
constructor: DynamicItem,
show: function () {
var data = this .chosen.results_data,
text = this .text,
isNotSelected = true ;

if ( this .chosen.choices) {
( this .chosen.search_choices.find( "li.search-choice" ).each( function (el) {
var itemIdx = this .id.substr( this .id.lastIndexOf( "_" ) + 1),
item = data[itemIdx];

if (item.value === text) {
isNotSelected = !isNotSelected;
return false ;
}
}));
}

this .elContainer[isNotSelected ? 'show' : 'hide' ]();
},
update: function (terms) {
if (( this .text = terms).length) {
this .elButton.text( 'Add new item "' + this .text + '"' );
this .show();
} else {
this .elContainer.hide();
}
},
addNewItem: function (terms) {
this .chosen.form_field.options.add( new Option( this .text, this .text));
this .chosen.form_field_jq.trigger( 'liszt:updated' );
this .chosen.result_highlight = this .chosen.search_results.children().last();
return this .chosen.result_select();
}
};

$.extend(ChosenClass.prototype, {
no_results: ( function (fnSuper) {
return function (terms) {
(dynamicItemInstance || (dynamicItemInstance = new DynamicItem( this ))).update(terms);
return fnSuper.call( this , terms);
};
})(ChosenClass.prototype.no_results),
results_hide: ( function (fnSuper) {
return function () {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.call( this );
};
})(ChosenClass.prototype.results_hide),
winnow_results_set_highlight: ( function (fnSuper) {
return function () {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.apply( this , arguments);
};
})(ChosenClass.prototype.winnow_results_set_highlight)
});
})(jQuery, jQuery.fn.chosenClass());

* This source code was highlighted with Source Code Highlighter .

Having a little analyzed the source code of the widget, I figured out how to implement dynamic position adding. Chosen methods are redefined using closures, the functionality of adding a position in a separate class. Of course, if necessary, you can replace the button with a link, add CSS styles (the marking class to the button block is installed) and so on.

Demo

Option with Prototype version


In the Prototype version of the widget, the Chosen class is available globally (window.Chosen) so there was nothing to patch.
( function (Chosen) {
var dynamicItemInstance;

function DynamicItem(chosenInstance) {
( this .chosen = chosenInstance).search_results.up().insert({
top: this .elContainer = $( document .createElement( 'div' ))
});
this .elContainer.addClassName( 'chzn-results-additemcontainer' );
this .elContainer.insert( this .elButton = $( document .createElement( 'button' )));
Event.observe( this .elButton, 'click' , this .addNewItem.bind( this ));
}
DynamicItem.prototype = {
constructor: DynamicItem,
show: function () {
var data = this .chosen.results_data,
text = this .text,
isNotSelected = true ;

if ( this .chosen.choices) {
( this .chosen.search_choices.select( "li.search-choice" ).each( function (el) {
var itemIdx = el.id.substr(el.id.lastIndexOf( "_" ) + 1),
item = data[itemIdx];

if (item.value === text) {
isNotSelected = !isNotSelected;
return false ;
}
}));
}

this .elContainer[isNotSelected ? 'show' : 'hide' ]();
},
update: function (terms) {
if (( this .text = terms).length) {
this .elButton.update( 'Add new item "' + this .text + '"' );
this .show();
} else {
this .elContainer.hide();
}
},
addNewItem: function (terms) {
this .chosen.form_field.options.add( new Option( this .text, this .text));
Event.fire( this .chosen.form_field, "liszt:updated" );
this .chosen.result_highlight = this .chosen.search_results.childElements().pop();
return this .chosen.result_select();
}
};

Chosen.prototype.no_results = ( function (fnSuper) {
return function (terms) {
(dynamicItemInstance || (dynamicItemInstance = new DynamicItem( this ))).update(terms);
return fnSuper.call( this , terms);
};
})(Chosen.prototype.no_results);
Chosen.prototype.results_hide = ( function (fnSuper) {
return function () {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.call( this );
};
})(Chosen.prototype.results_hide);
Chosen.prototype.winnow_results_set_highlight = ( function (fnSuper) {
return function () {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.apply( this , arguments);
};
})(Chosen.prototype.winnow_results_set_highlight);
})(window.Chosen);

new Chosen($( 'bears_multiple' ));

* This source code was highlighted with Source Code Highlighter .

Demo

Remarks


In the place of the widget's author, I would implement a common kernel (instead of separate versions for Prototype / jQuery / Mootools). From libraries I would use only the most necessary methods, through a wrapper (interface). The purpose of this is to have one (common) version of the main widget code. Now, with the current approach of forks under different frameworks, I do not see much point in committing something on github.

Thanks to all those who have put in karma, this allowed us to publish the article.

Source: https://habr.com/ru/post/124912/


All Articles