Using bootstrap-tagsinput plugin in meteor

1.3k views Asked by At

In my meteor project I am using the bootstrap-tagsinput plugin:

http://timschlechter.github.io/bootstrap-tagsinput/examples/

I use it in the 'typeahead' mode, and so it requires initialization as in:

<input type="text" value="Amsterdam,Washington" data-role="tagsinput" />
<script>
$('input').tagsinput({
  typeahead: {
    source: function(query) {
      return $.getJSON('citynames.json');
    }
  }
});
</script>

I can't figure out what is the best way to integrate it with meteor -- so I'm asking for advice.

I've tried several approaches:

(1) Put the initialization code in the .created of the template containing the input element:

<template name="hello">
<input type="text" value="Amsterdam,Washington" data-role="tagsinput" />
</template>

template.hello.created = function () {
    $('input').tagsinput({...});
}

This seems natural. But, when the template gets re-rendered, the initialization data is lost and the input element does not behave as a tagsinput.

(2) Same as (1) but add the {{#constant}} directive. The {{#constant}} directive prevents re-rendering according to meteor docs. The plugin should just work if it is init once and never re-rendered:

(btw, there's a reason for the added div, see further on:)

{{#constant}} 
<div>
  <input type="text" value="Amsterdam,Washington" data-role="tagsinput" />
</div>
{{/constant}} 

This fails with:

"Exception from Deps recompute: Error: 
An attempt was made to reference a Node in a context where it does not exist" 

The exception stack was useless (mostly 'spark' code), so I ended up abandoning this path (but I suspect this is still the best way, if only I can get it to work).

(3) Initializing the tagsinput in the .rendered function:

template.hello.rendered = function () {
    $('input').tagsinput({...});
}

This also fails, because the plugin accepts initialization exactly once. A second initialization will not work: it expects the tagsinput() arg to be a function property name and tries to execute it (or something along these lines).

(4) I thought I'd take (3) further and outsmart it by removing the initialized data:

template.hello.rendered = function () {
    $('input').removeData('tagsinput');
    $('input').tagsinput({...});
}

This clears data['tagsinput'] at the input element and allows for repeated tagsinput initializations. Once data['tagsinput'] is non-existent, the initialization goes through and recreates it. This trick almost solved it, except for a small side-effect: an auto-generated div element lingers in the DOM. The way tagsinput plugin works is by adding a sibling div after the input element:

<input data-role="tagsinput" ... />
<div class="bootstrap-tagsinput">...</div>   <-- auto-generated by tagsinput

Once solution attempt (4) runs, an occasional div as such will remain in the dom, along with the newly generated div. At this point I started feeling that this solution is not according to the meteor spirit, but I decided to try to get rid of the lingering div using:

template.hello.rendered = function () {
    $('input').removeData('tagsinput');
    $(".bootstrap-tagsinput").remove();
    $('input').tagsinput({...});
}

This code gets the job done, but it's super hackish and it is likely to break when meteor or tagsinput are updated.

If any of you meteor-ninjas out there can tell the right way of initializing tagsinput, that would be awesome!

1

There are 1 answers

2
gabrielhpugliese On

My fix with typeahead was wrapping all other reactive regions of template inside isolated regions: http://docs.meteor.com/#isolate

You must do in all of them.