Selectize.js: Getting source JSON data for items instead of DOM elements

Created on 17 Jun 2016  路  13Comments  路  Source: selectize/selectize.js

I'm not sure if this is an enhancement request, or I'm just not properly understanding the API.

I use this library often, because it's great, but I rarely use remote sources with it. I used to use Typeahead/Bloodhound, but since this library does the same thing (and in many cases better) I figured I'd use selectize.js instead. Ok enough backstory here's the problem:

Using a selectize'd textbox I have a remote source to [pre]load / search for values that are sent [obviously] in JSON. Without an issue it's rendered properly and the selected value is available in the data and the selected label (both defined in initialization options) display properly.

Using the getItem(val) and/or getOption(val) functions I expected to get the original JSON object that created the option (or the selected item(s)) using either of these functions. However, both of these functions instead return the HTML Element that is the option/selected item in the selectize'd element.

No biggie, I figured that the JSON would be stored in either of these elements because of the dataAttr option that can be set when initializing (or the default of 'data-data'). However, calling .getOption(val).data('data') always returned undefined (same with the element returned from getItem(val)). Using my debugger I am not seeing any data-* element within the returned elements.

I feel like this probably isn't a bug, but instead I'm just not quite understanding how I'm supposed to get this data. My current workaround is to get the option array ($('#selectized)[0].selectize.options) and search for the correct object. Is there a better way?

Additionally, per the title of my issue, I do believe that there should be some sort of equivalent functions for getOption() or getItem() that return the JSON data that generated that option as opposed to the jQuery element that was created. Maybe this could be something like getOptionObject() or getItemObject(). I didn't see anything like this in the documentation or source code. I would be happy to fork & submit a pull request for this if others are interested (and I'm not missing if/where this already exists).

consensus on need enhancement needs-thought

Most helpful comment

var $select = $('#jsartnum').selectize(...);
var selectize = $select[0].selectize;
selectize.on('item_add', function(value, item) {
console.log(selectize.sifter.items[value]); // Here we go ... your JSON's object.
});

All 13 comments

+1

In my case I have an address input which fetches address suggestions as you write. Once you select an address the input's value is that address' text but I still need a clean way to have access to the original JSON. It's common for these type of APIs to have a find and a fetch action. The find endpoint offers suggestions while the fetch endpoint returns more throughout information about the address such as country and the like.

As you suggest there should be an easy way to store and get the data from which the option was built.

Now that I think of this more there really should be some sort of structure that holds the original objects that are currently selected. Then a function can be called to get the single object that's selected or an array if settings.maxItems > 1 (and multiple options are selected). This could be retrieved from a function such as getSelectedObjects() which utilizes that internal structure.

This type of solution would solve @pablo-co issue as well.

After further review it definitely isn't necessary to implement getOptionObject() from my original post, since the option's original object can be easily retrieved from the options property as I mentioned I do as a workaround in my original post. This allows retrieval easily using either the value that was set as the value field in the settings (ie: $selectize.options[val]) or another more complex search method.

I wholeheartedly agree with the need, so much I've kept this issue in a tab for a few days because I felt it needed the attention. I think this is a great example the direction in which Selectize needs to be refactored (#974). However the solution needs to be solid design-wise, I'm always wary of tacking yet another function in the method hodgepodge that is Selectize.

Let me think out loud for a minute. getOption and getItem do the same thing, one looks up the _options_ in the dropdown, and the other for the _items_ in the control. The selected data in the end鈥攚e'll call it like that鈥攎akes its way from your source first (remote or not), to the dropdown, selected into the items, reflecting the input value.

Now reading your original post, you don't make your use case exactly clear, can you explain what you're trying to do? More precisely, how are you using the original object and at what time? (on an event) A pseudocode example would be great.

Going backwards through your post, my use case: Selecting an item causes a bunch of other fields/elements to be auto-populated from the original object. Here's a made up Selectize options initialization with some psuedocode in the methods that I've been using to mimic the behavior of Typeahead/Bloodhound.

Let's pretend that our object is of a Person that stores 4 properties: Id, Name, Phone, Email.

$('#mySelectizedElement').selectize({
    create: true,
    labelField: 'name',
    valueField: 'id',
    searchField: ['name'],
    maxItems: 1,
    preload: true,
    load:  function(query, callback) {
        $.getJSON('/api/contacts/' + query, function(data) {
            callback(data);
        }
    },
    onOptionAdd: function(value, data) {
       if (value is not an integer) {  //Looks like this has an invalid Id, it must have been a user-defined item
            data.id = value,
            data.phone = '',
            data.email = ''
        },
    onChange: function(val) {  //this is where the original object is needed
           if (val is not null, empty, or whitespace) {
                var contact = this.options[val];  //assume (val in options) is true
                $('#phone').val(contact.phone);
                $('#email').val(contact.email);
            }
        }
    });

The above example is pretty simple and takes some naive shortcuts, but definitely shows you how this is being used. There are a few problems that come up though. The first is that when you're using objects like this and only one field is set as the "value" field, but that field does not contain unique values. In my example above this most likely won't happen, but it certainly could if the field selected for the value isn't unique. This causes values to override each other so whatever the last loaded option with that value is. I believe this is most likely what issue #1055 was attempting to describe. Additionally, adding new options can be problematic since we have a single text field. Some creativity is needed in setting up default values for objects while making sure that the 'value' field is still always unique.

You're absolutely correct that getOption and getItem do the same thing in regards to getting the elements that are either in the dropdown or that are selected. Therefore, contrary to my original post (and updated in my second) it's definitely not necessary to attempt to create an object version of those. Especially since .options can be called from the API.

This really should be implemented into the API similar to getValue(), with a name such as getSelected(). My idea for this would be for this function to return either a string, object, or array. A string will be returned only in the case that the underlying data is of single strings where the option objects only consist of a single field (valueField == labelField obviously in this case). In this situation if nothing is selected the empty string is returned to mimic the same behavior as an empty text field. If the maxItems option is set to 1, then the function would return either the selected object or null if nothing is currently selected. In all other cases an array is returned with the selected objects or an empty array in the case of nothing being selected.

If this is implemented I think that if valueField is not set it defaults to the "value" being the entire object. Additionally, we'd want to note that the value field should always be unique, otherwise you'd have the overwriting issue I mentioned earlier. If defaulted, each option can be placed into a wrapper object where each object has a property that a unique identifier assigned and the original object is the other. (Additional properties can potentially be added for sorting purposes). The only caveat with this is then I'm not completely sure what would be set to the submitted value in the underlying input/select field when this is within an HTML form so it can be submitted without additional manipulation by the end user.

I do believe that this can be implemented in the project's current state without creating much of a mess into as you put the "hodgepodge that is Selectize." However, I do like your refactor into components idea. In that case if this is implemented during the refactoring then the behavior of the function as I mentioned in the previous paragraph can be determined by what components are currently being utilized. It would certainly help with the cleanup process and make this library even more awesome than it currently is.

Wrapping up this beast of a post, I'd love to help out in implementing this and/or the refactoring into components. I will admit that I suck at CSS though so I probably can't contribute too much to refactoring that stuff (if it's even necessary), but I'm game for anything else. Let me know your thoughts and if/how you'd like to proceed.

+1

I also hope that here is an easy way to get option and selected item data. My case is that I have a city select, when user select the city, I want to read the city data of latitudes and longitudes to update a related map. But currently I have to manually render data attributes to options or items. However, this approach does not work well.

+1

It'd be awesome if there was an method to retrieve full data of selected items. Not just only value.

Right now I need to loop the selectize.sifter.items to access that data, and it feels hacky.

I'm actually working on implementing this in the little bit of free time I have. Unfortunately, it's very limited right now so it might take me a bit. I've been getting familiar with the codebase and it doesn't look too difficult to add, but I want to make sure there aren't any 'gotchas' and I write tests before I submit a pull request.

Just a quick note that there is an alternative way to accomplish this (thanks to @rmm5t) using a jQuery micro-plugin and closures. I really like his pattern which is more fully explained here. Here's an example for the use case mentioned by @jblacker :

$(document).ready(function() {
  $('#mySelectizedElement').contactsSelectize();
});

$.fn.contactsSelectize = function(options) {
  return this.each(function() {
    var select = $(this); // <------ get the jQuery object so we can add something to it
    select.dataMap = {}; // <------ We attach this hash map to the jQuery select object to store our objects returned

    return select.selectize({
      create: true,
      labelField: 'name',
      valueField: 'id',
      searchField: ['name'],
      maxItems: 1,
      preload: true,
      load:  function(query, callback) {
          $.getJSON('/api/contacts/' + query, function(data) {
              select.dataMap = {}; // <------- We reset the hash map every time we load so we don't have memory issues
              callback(data);
          }
      },
      onOptionAdd: function(value, data) {
        select.dataMap[value] = data; // <------ We assign the data object to the hashmap
      },
      onChange: function(value) {
         if (value is not null, empty, or whitespace) {
           var contact = select.dataMap[value]; // <------ Grab the object from the hashmap based on the value
           console.log(contact); // <----- Look! we have the entire json object...
           $('#phone').val(contact.phone);
           $('#email').val(contact.email);
          }
      }
    });

The other advantage to this is we could have multiple selectized contact drop-downs and they would keep their own set of objects, e.g.

$(document).ready(function() {
  $('#primary-contact').contactsSelectize();
  $('#secondary-contact').contactsSelectize();
});

Note that my original code was using coffeescript so apologies in advance if the javascript needs tweaking. I can update if there's questions.

any update on this? :)
I have search input where I search for item and then once I get the item object, I can add it to the cart (name, price, discount).
Right now i have id using selectize and then using that selected item id , I need to fetch the object again from server ,this makes me to call the server twice for single item object. I am using vuejs.

how to handle json with selectize please help me , I posted the question in the stackoverflow.
here is my link from stackoverflow

Sorry for not posting any updates in forever. I implemented this in my local fork a while back, but I haven't written any unit tests because I haven't had any time due to craziness at work. It seems to work, but I cannot confirm that I haven't broken anything else. I'm going to try to get them written this week, but I can't promise anything & I'm not super familiar with Karma.

var $select = $('#jsartnum').selectize(...);
var selectize = $select[0].selectize;
selectize.on('item_add', function(value, item) {
console.log(selectize.sifter.items[value]); // Here we go ... your JSON's object.
});

closing stale issues older than one year.
If this issue was closed in error please message the maintainers.
All issues must include a proper title, description, and examples.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vilimco picture vilimco  路  5Comments

fadhilanugrah picture fadhilanugrah  路  4Comments

jaideepYes picture jaideepYes  路  5Comments

Davidslv picture Davidslv  路  5Comments

adrianmihalko picture adrianmihalko  路  4Comments