I have a very simple Bootstrap Typeahead binding (jsFiddle) for Knockout.js, like this:
ko.bindingHandlers.typeahead = {
init: function (element, valueAccessor) {
var $e = $(element),
source = valueAccessor();
$e.typeahead({
source: source,
minLength: 0
});
},
};
Its usage is simple:
<input data-bind='typeahead: source, value: item,
valueUpdate: "afterkeydown"' />
The binding works as expected in the jsFiddle. However when loaded with RequireJS, it does not always work as expected. There seems to be a race condition as Knockout and jQuery are loading in parallel.
If I look at the change event handlers for a bound input element, if jQuery is the handler then the binding works as expected. If Knockout is the handler for the change event then, for example, if you type "alp", and Typeahead suggests "Alpha", and you select "Alpha" from the Typeahead dropdown list then the <input> element will show the selected text ("Alpha"), but the bound observable will be "alp".
In the current system, the Typeahead binding cannot directly modify the given observables, so passing { source: src, observable: item } would not resolve the issue. The role of this Typeahead binding is only to update the content of the bound input area.
I have experimented with triggering related events from the binding, such as 'keydown', 'keypress' and 'keyup', but for obvious reasons this is not a very resilient option.
I suspect the problem is related to the indeterministic nature of RequireJS, and the Typeahead plugin works or not depending on whether jQuery or Knockout loaded first. In particular, it seems that this problem is resolved by making jQuery a dependency of Knockout (i.e. loading jQuery first) with RequireJS's shim:
requirejs.config({
shim: {
knockout: { deps: ['jquery'] },
}
});
This seems odd, and I would like to understand better what is happening here and if this is a reasonable resolution of the problem – i.e. am I actually addressing the underlying problem here, or could the issue crop up with subsequent versions of Knockout or jQuery.
I would be grateful for any thoughts.
EDIT
You can simply replicate the problem by loading the scripts in-order inline with eg:
<script src='knockout.js'></script>
<script src='jquery.js'></script>
Here is a jsFiddle exhibiting the issue.
In the above order the plugin will fail; otherwise it will work as expected.
In any case, the RequireJS config is like this:
requirejs.config({
baseUrl: "/script",
shim: {
knockout: { deps: ['jquery'] }, # remove this for race condition
}
});
A simplified version of the main has require(['jquery', 'knockout', 'bindings'], ...), where bindings defines the Knockout handler. The bindings.js file effectively starts with define(['knockout', 'jquery'], ...).
In this case though the problem is not RequireJS, but the ordering of the scripts.
define(['ko', 'jq'], function(){ //bindings })? Then in your viewmodel module, you loadbindingsas a dependency. - Jon Jaques