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!
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.