Dynamically adding data to select2 in meteor

1,750 views
Skip to first unread message

gregor

unread,
Jun 19, 2014, 5:48:36 AM6/19/14
to meteo...@googlegroups.com
I am searching for a solution how to dynamically add data to select2 in meteor. 
I want to refresh data in select2 after user types string in select2 input box (filtering). 
Similar like typeahead package is doing for input field with server method to query data.
I would like to use select2 to have a limitation to select from existing docs only and when user selects a document from collection I need also the _id of the document to unique identify the doc.

I also got this error when trying to add array of data to select2:
Error: Option 'data' is not allowed for Select2 when attached to a <select> element

Matthew Fieger

unread,
Jun 19, 2014, 7:38:09 AM6/19/14
to meteo...@googlegroups.com
I think I am doing something similar to what you are describing.  I am using select2 to autofill a 'textarea` based on a list of tags that are in a collection.  Below is the relevant code in my template:

<textarea id="post-tags" name="post-tags" placeholder="Add comma separated tags..." rows="1" required ></textarea>

Below is my template helper code.  The neat thing is that because I have it inside Deps.autorun, as soon as a new tag is added by one client, it is reactively available on all other clients as a new select2 option.  I am using the 'tag' setting of select2 below, where you just pass an array of strings to the 'tag' option.  You could also use the 'data' mode, in which case you have to pass an array of objects to the 'data' option, with each object having an 'id' property and a 'text' property.

Template.observationPostForm.rendered = function () {
    var self = this;
    self.myDeps = Deps.autorun(function () {
        var cursor = Tags.findOne({'projectId': Session.get("currentProject")})
        if (cursor && cursor.tags) {
            var options = {
              'multiple': true,
              'selectOnBlur': true,
              'tags': cursor.tags,
              'tokenSeparators': [","],
              'width': 'element'
            };
            self.$('#post-tags').select2(options);   
        }
    });
};

On the server, I'm publishing a pseudo collection of tags.  This pattern was inspired by the 'counts-by-room' example in the official Meteor Documentation.

Meteor.publish("tags", function (arguments) {
  var self = this;

  if (this.userId) {
var roles = Meteor.users.findOne({_id : this.userId}).roles;
if ( _.contains(roles, arguments.projectId) ) {

var observations, tags, initializing, projectId;
initializing = true;
projectId = arguments.projectId;
observations = Observations.find({'projectId' : projectId}, {fields: {tags: 1}}).fetch();
tags = _.pluck(observations, 'tags');
   tags = _.flatten(tags);
   tags = _.uniq(tags);

var handle = Observations.find({'projectId': projectId}, {fields : {'tags' : 1}}).observeChanges({
added: function (id, fields) {
 if (!initializing) {
  tags = _.union(tags, fields.tags);
   self.changed("tags", projectId, {'projectId': projectId, 'tags': tags});
 }
},
removed: function (id) {
 self.changed("tags", projectId, {'projectId': projectId, 'tags': tags});
}
});

initializing = false;
self.added("tags", projectId,  {'projectId': projectId, 'tags': tags});
self.ready();

self.onStop(function () {
handle.stop();
});
} //if _.contains
} // if userId

  return this.ready();

});

Hope this helps.  I tried typeahead at first, but found that select2 was a little better suited for what I wanted to do above.  P.S. I did some testing with different sizes of data and I noticed that the autofill starts to lag when you get to thousands of data items.  That's a little bit of a concern for me because that could be a possibility in the future.  But in the mean time I am only working with hundreds of data items and it seems very fast.

Luis Fernando Alvarez David

unread,
Jun 19, 2014, 8:23:47 AM6/19/14
to meteo...@googlegroups.com
I also had trouble with select2 when changes occur in the collection and select does not change my temporary way to fix it so it works very well is that when a change happens I replace that select with another select, and so on

gregor

unread,
Jun 19, 2014, 8:52:26 AM6/19/14
to meteo...@googlegroups.com
 Could you post your solution too?

gregor

unread,
Jun 19, 2014, 3:32:24 PM6/19/14
to meteo...@googlegroups.com
I managed to dynamically add values to select (select2) based on user typing, but there is a problem, that select2 queries the values before they are added to list.
So values are not found on first char entered.
Has someone any idea how to solve this? By triggering requery of select2 or to fill data in some other way.

code
++++++++

CLIENT:
client2 is select with select2

Template.frmOrgRelationAdd2.rendered = function () {
   $('body').on('keyup',".select2-input",function(e) {
      search = $("input.select2-input")
      query = search.val();
      console.log("query :" + query);
      //$("#client2").select2('data', null);
   $("#client2").empty();
   Meteor.call('search', query, {}, function(err, res) {
           if (err) {
               console.log(err);
               return;
           }
           $('#client2').append( $.map(res, function(v, i){ return $('<option>', { val: v.unid, text: v.name }); }) );
       });
   });
};

SERVER:

Meteor.methods({
    search: function(query, options) {
        options = options || {};

        // guard against client-side DOS: hard limit to 50
        if (options.limit) {
            options.limit = Math.min(15, Math.abs(options.limit));
        } else {
            options.limit = 15;
        }
        console.log("query: " + query);
        var regex = new RegExp("^" + query, "i");
        console.log(Organizations.find({name: {$regex: regex}}, options).count());
        return Organizations.find({name: {$regex: regex}}, options).fetch();
    }
});

Luis Fernando Alvarez David

unread,
Jun 20, 2014, 12:21:18 PM6/20/14
to meteo...@googlegroups.com
the idea is simple, every time there is a reactive change the select to be replaced with another select, which is the same

var readys = [];

// Ready invests its value every time there is a reactive change
Template.select.ready = function () {
    var ready = readys[this.name];
    if (typeof ready != 'boolean')
        ready = false;
    return readys[this.name] = !ready;
}

<template name="select">
    {{#if ready }}
{{> getSelect }}
    {{ else }}
{{> getSelect }}
    {{/if}}
</template>

<template name="getSelect">
<select class="form-control select2">
<option>I'm a option</option>
</select>
</template>

Brandon Bechtel

unread,
Jan 20, 2015, 5:12:16 PM1/20/15
to meteo...@googlegroups.com
Gregor,

Had this same problem today and came across your post as I was trying to figure it out. This seemed to work for me:

myTemplate.js

Template.myTemplate.rendered = function () {
    $
(".select_individual").select2();
};

Template.myTemplate.helpers({
  helpers
: function() {
   
return My_Collection.find();
 
}
});

myTemplate.html
<template name="myTemplate">
 
<form>
   
<select>
      {{#each helpers}}
       
<option value="{{_id}}">{{helper}} {{data}}</option>
      {{/each}}
   
</select>
 
</form>
</template>

-Brandon
Reply all
Reply to author
Forward
0 new messages