bootstrap Dual Listbox knockout binding - asp.net-mvc

I am using bootstrap dual listbox pluging in my ASP.NET MVC project.
I am also using Knockout in the project. I am trying to create bindingHandler for the this plugin to make it working smoothly with knockout.
here is what I tried
Binding Handler
ko.bindingHandlers.dualList = {
init: function (element, valueAccessor) {
$(element).bootstrapDualListbox({
selectorMinimalHeight: 160
});
$(element).on('change', function () {
$(element).bootstrapDualListbox('refresh', true);
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).bootstrapDualListbox('destroy');
});
},
update: function (element, valueAccessor) {
$(element).bootstrapDualListbox('refresh', true);
}
}
HTML
<select class="form-control" data-bind="foreach:{data: EditedElement().Operations, as : 'op'} , dualList: EditedElement().Operations" multiple>
<option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
</select>
View Model
function SelectOperationVM(operationId, isSelected, name) {
var self = this;
self.OperationID = ko.observable(operationId);
self.IsSelected = ko.observable(isSelected);
self.Name = ko.observable(name);
self.copy = function () {
return new SelectOperationVM(self.OperationID(), self.IsSelected(), self.Name());
}
}
My problem is that I can not make sync between the viewModel observableArray, and the plugin.
In other words, I want the changes in the plugin (the user removed some options or added some options) to be reflected on the view model property, and vice verse

to sync, you need to pass multiple observables to custom binding
so your html should be like
<select class="form-control" data-bind="foreach: { data: Operations, as: 'op'}, dualList: { options: Operations, selected: Selected }" multiple>
<option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
</select>
and custom binding be like
ko.bindingHandlers.dualList = {
init: function(element, valueAccessor) {
var data = ko.utils.unwrapObservable(valueAccessor());
$(element).bootstrapDualListbox({
selectorMinimalHeight: 160
});
$(element).on('change', function() {
// save selected to an observable
data.selected($(element).val());;
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).bootstrapDualListbox('destroy');
});
},
update: function(element, valueAccessor) {
// to view if there is an update (without this "update" will not fire)
var options = ko.utils.unwrapObservable(valueAccessor()).options();
$(element).bootstrapDualListbox('refresh', true);
}
}
also i have created a dirty working jsfiddle

Related

Knockout Binding Not Working with jQueryUI Dialogue

My viewModel has an array called 'Items'. I want to display the contents of 'Items' using a foreach binding. Everything works fine when I use regular HTML. But does not work with a dialogue box which I created using jQueryUI.
HTML:
<div id="skus0">
<div id="skus1">
<ul data-bind="foreach: Items">
<li data-bind="text:Name"></li>
</ul>
</div>
<input type="button" id="openQryItems" class="btn btn-info" value="Open" data-bind="click:openQueryItems" />
</div>
JavaScript:
// my view model
var viewModel = {
Items: [{Name:'Soap'},{Name:'Toothpaste'}]
};
// JS to configure dialogue
$("#skus1").dialog({
autoOpen: false,
width: 500,
modal: true,
buttons: {
"OK": function () {
$(this).dialog("close");
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
// for mapping my model using ko.mapping plugin
var zub = zub || {};
zub.initModel = function (model) {
zub.cycleCountModel = ko.mapping.fromJS(model);
zub.cycleCountModel.openQueryItems = function () {
$("#skus1").dialog("open");
}
ko.applyBindings(zub.cycleCountModel, $("#skus0")[0]);
}
zub.initModel(viewModel);
I have created a fiddle here my fiddle
$.fn.dialog removes the element from its place in the DOM and places it in a new container; this is how it can create a floating window. The problem with this happening is that it breaks data binding, since the dialog DOM is no-longer nested within the top-level data-bound DOM.
Moving the dialog initialization to after ko.applyBindings will enable dialog to yank stuff out of the DOM after the list is populated. Of course, this means that after that point, future changes will still not be reflected, which may be important if you're wanting the opened dialog to change automatically.
If you are wanting the dialog contents to be fully dynamic, you could create a binding handler; we did this in our project. Here's a rough outline of how we did this:
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingCtx) {
var bindingValues = valueAccessor();
var hasAppliedBindings = false;
var elem = $(element);
var options = {
id: ko.utils.unwrapObservable(bindingValues.id),
title: ko.utils.unwrapObservable(bindingValues.title),
// etc...
onOpen: function () {
if (!hasAppliedBindings) {
hasAppliedBindings = true;
var childCtx = bindingCtx.createChildContext(viewModel);
ko.applyBindingsToDescendants(childCtx, element);
}
}
};
elem.dialog(options);
}
return { controlsDescendantBindings: true };
}
...which we used like this:
<div data-bind="dialog: { title: 'some title', id: 'foo', ... }">
<!-- dialog contents -->
</div>
What return { controlsDescendantBindings: true } does is makes sure that outer bindings do not affect anything using the dialog binding handler. Then we create our own Knockout binding "island" after it is pulled out of the DOM, based on the original view model.
Although in our project we also used hybrid jQuery+Knockout, I would highly recommend you avoid this whenever possible. There were so many hacks we had to employ to sustain this type of application. The very best thing you should do is prefer Knockout binding handlers (and I think it has a "component" concept now which I haven't played with) over DOM manipulations to avoid buggy UI management.

Ractive isn't capturing a change from jquery-ui datepicker

I'm using jquery-ui's datepicker with a Ractive template, and the two-way binding doesn't seem to be working properly.
My input looks like this:
<input type="text" value="{{value}}" decorator="picker">
Then I'm instantiating the date picker through the "picker" decorator:
decorators: {
picker: function(node) {
$(node).datepicker();
return {
teardown: function() {
$(node).datepicker("destroy");
}
};
},
}
The datepicker shows up perfectly, but value doesn't get updated properly. If I through and observer on {{value}} I see that Ractive doesn't think the value has changed once it's been set by the datepicker. If I click into the field, and back off again (losing focus), the observer triggers, and the value is set.
In my decorator I can setup a callback to trigger using the datepickers "onSelect" event, but how do I force a ractive change event from the decorator function?
decorators: {
picker: function(node) {
$(node).datepicker({
onSelect: function(dateValue) {
console.log("datevalue set");
//I've tried this already
$(node).change();
//and
$(node).trigger("change");
//neither work
}
});
return {
teardown: function() {
$(node).datepicker("destroy");
}
};
},
}
In the decorator function, this refers to the current ractive instance, so you can call ractive.updateModel() to let it know the model needs to be updated based on the view:
decorators: {
picker: function(node) {
var ractive = this
$(node).datepicker({
onSelect: function(dateValue) {
ractive.updateModel()
}
});
return {
teardown: function() {
$(node).datepicker("destroy");
}
};
},
}
(See http://jsfiddle.net/arcca09t/)
Edited
The current version of ractive use the as-* convention for decorators, you should edit the html like this:
<input type="text" value="{{value}}" as-picker>
ractive no longer uses decorator attributes like that. But martypdx's suggestion sent me in the right direction.
$(function(){
var r = new Ractive({
el: document.body,
template: '#template',
data: {
value: null
},
decorators: {
picker: function(node) {
var ractive = this
$(node).datepicker({
dateFormat: 'yy-mm-dd',
onSelect: function(dateValue) {
ractive.updateModel()
}
});
return {
teardown: function() {
$(node).datepicker("destroy");
}
};
},
}
})
})
<script src="https://cdn.jsdelivr.net/npm/ractive#0.9.5/ractive.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<script src="https://code.jquery.com/ui/1.11.1/jquery-ui.js"></script>
<script id='template' type='text/ractive'>
value: {{value}}
<br>
<input type="date" value="{{value}}" as-picker>
</script>

Connect knockout and jQueryUI autocomplete

I have a jQueryUI autocomplete that pulls from a list of customers and is attached based on the selector [input data-role="customer-search"]. Once a customer is selected, I make a AJAX call to get the full customer detail. This part I have working fine. The issue is that I am having trouble figuring out a way to incorporate knockout into this. My ideal situation is a custom binding like "onSelect: customerSelected", which would take in the selected Customer JSON and integrate it into the overall model, which would then cause updates to a bunch of fields on the page with bingings such as model.Customer.Address, model.Customer.Type.
The place I am butting my head against is that connection point after I've gotten the Customer JSON back from the AJAX call, how to send it to the "customerSelected" method on the viewmodel tied to the same input I attached the jQuery autocomplete.
Here is a simplified version of a bindinghandler my team wrote for handling autocomplete. When an item is selected, the item is inserted into an observable array in the view model. It is bound in the following manner:
<input type="text" data-bind="autoComplete:myObservableArray, source:'myUrl'" />
You can customize what happens when an item is selected in the 'select:' area.
ko.bindingHandlers.autoComplete = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var postUrl = allBindingsAccessor().source; // url to post to is read here
var selectedObservableArrayInViewModel = valueAccessor();
$(element).autocomplete({
minLength: 2,
autoFocus: true,
source: function (request, response) {
$.ajax({
url: postUrl,
data: { term: request.term },
dataType: "json",
type: "POST",
success: function (data) {
response(data);
}
});
},
select: function (event, ui) {
var selectedItem = ui.item;
if (!_.any(selectedObservableArrayInViewModel(), function (item) { return item.id == selectedItem.id; })) { //ensure items with the same id cannot be added twice.
selectedObservableArrayInViewModel.push(selectedItem);
}
}
});
}
};
Hopefully, it's something like this that you're looking for. If you need something clarified, let me know.
Note Besides jquery and knockout, this example uses underscore.js ( the _.any() function)
valueUpdate: blur
data-bind="value: textbox, valueUpdate: blur" binding fixed the problem for me:
$(function() {
$(".autocomplete").autocomplete({
source: [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Scheme"]
});
});
var viewModel = {
textbox: ko.observable()
};
ko.applyBindings(viewModel);
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.11.3/jquery-ui.min.js"></script>
<input type="text" class="autocomplete" data-bind="value: textbox, valueUpdate: blur"/>

How to bind jQuery UI option to a Knockout observable

This fiddle shows how to bind a jQuery slider 'slide' event to a Knockout observable. How would this need to change to also bind the 'max' option of the slider to an observable? Do you have to create an entirely new ko.bindingsHandler entry? Or can the existing one be used?
Here is the code from the fiddle for reference.
HTML
<h2>Slider Demo</h2>
Savings: <input data-bind="value: savings, valueUpdate: 'afterkeydown'" />
<div style="margin: 10px" data-bind="slider: savings, sliderOptions: {min: 0, max: 100, range: 'min', step: 1}"></div>
Spent: <input data-bind="value: spent, valueUpdate: 'afterkeydown'" />
<div style="margin: 10px" data-bind="slider: spent, sliderOptions: {min: 0, max: 100, range: 'min', step: 1}"></div>
Net: <span data-bind="text: net"></span>
JS
ko.bindingHandlers.slider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
$(element).slider(options);
ko.utils.registerEventHandler(element, "slidechange", function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
ko.utils.registerEventHandler(element, "slide", function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (isNaN(value)) value = 0;
$(element).slider("value", value);
}
};
var ViewModel = function() {
var self = this;
self.savings = ko.observable(10);
self.spent = ko.observable(5);
self.net = ko.computed(function() {
return self.savings() - self.spent();
});
}
ko.applyBindings(new ViewModel());
Look at this fiddle. I added checking if max is observable and subscribing to it:
if (ko.isObservable(options.max)) {
options.max.subscribe(function(newValue) {
$(element).slider('option', 'max', newValue);
});
options.max = ko.utils.unwrapObservable(options.max);
}
I have a collection of jQUery Ui bindings for KO. I havent done the slider because I havent needed that control in a project. But check my button binding
https://github.com/AndersMalmgren/Knockout.Bindings
ko.bindingHandlers.button = {
initIcon: function (options) {
if (options.icon) {
options.icons = { primary: options.icon };
}
},
init: function (element, valueAccessor) {
var options = ko.utils.unwrapObservable(ko.toJS(valueAccessor())) || {};
ko.bindingHandlers.button.initIcon(options);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).button("destroy");
});
$(element).button(options);
},
update: function (element, valueAccessor) {
var options = ko.toJS(valueAccessor());
ko.bindingHandlers.button.initIcon(options);
if (options) {
$(element).button(options);
}
}
};
The magic is done in the update function, KO will by default subscribe to all observables in a object literal, so if you bind to { max: aObservable } the update function will trigger when any child updates.
I then do ko.toJS(valueAccessor()); to un observify the object and use that to update the jQuery control. This method can be used for slider as well, its generic and you do not need to add extra code for each setting

JQM issue Knockout

I'm trying to buid a moible Web Application with JQM and Knockout.
After my Knockout script is working i tried to get some style in it.
<form action="" data-bind=" template:{ 'if': loginVM, data: loginVM }">
//Some Code
</form>
<form action="" data-bind="template: { 'if': startVM, data: startVM }">
//Some Code
</form>
This is my more or less my Knockout script
var masterViewModel = {
loginVM: ko.observable(),
startVM: ko.observable(),
projektUnterbrechen: ko.observable(),
logout: ko.observable(),
projectStartVM: ko.observable()
};
var LoginVM = function () {
var self = this;
self.showDetails = function () {
if ((self.user() == "Gregor") && (self.password() == "gregrech")) {
masterViewModel.loginVM(null);
masterViewModel.startVM(new StartVM());
}
};
var StartVM = function () {
//Some Code
};
now after adding
<script src="../../Scripts/jquery.mobile-1.2.0.js" type="text/javascript"></script>
to my project my knockout doesn't work anymore.
It seems like my masterViewModel is not updatet!
For example i tried this:
var LoginVM = function () {
var self = this;
self.showDetails = function () {
if ((self.user() == "Gregor") && (self.password() == "gregrech")) {
masterViewModel.loginVM(null);
masterViewModel.startVM(new StartVM());
alert(masterViewModel.startVM()==null) //Messagebox displays "false"
}
};
var StartVM = function () {
alert(masterViewModel.startVM()==null) //Messagebox displays "true"
};
what could be a soloutin for this starnge problem?
JQM will alter the DOM once it is initialised, rendering many of the default ko bindings useless. Custom bindings can be written to work around these issues. It is hard to tell what the actual problem is here but that should give you something to read up on.
This is a useful article to start with: http://www.scottlogic.co.uk/blog/colin/2012/10/integrating-knockout-and-jquerymobile/

Resources