How to bind jQuery UI option to a Knockout observable - jquery-ui

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

Related

bootstrap Dual Listbox knockout binding

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

setting jQuery ui slider params from html data attributes

I want to embed the max of my sliders range in an html data parameter. I did some debug, and despite the fact that the data can be accessed and is a number, the slider will still use the default max of 100.
My HTML:
<div class="slider" data-max="10"></div>
<label for="slider_value">Slider Value:</label>
<input type="text" id="slider_value" />
My Javascript:
$(document).ready(function () {
$("div.slider").slider({
min: 0,
max: $(this).data("max"),
slide: function (event, ui) {
$("input#slider_value").val(ui.value);
}
});
});
See this fiddle
When the argument object for slider() is evaluated, this is a reference to the document object, not div.slider. You'd need to find div.slider again or save a reference to it (demo):
$(document).ready(function () {
var div = $("div.slider");
div.slider({
min: 0,
max: div.data("max"),
slide: function (event, ui) {
$("input#slider_value").val(ui.value);
}
});
});
Use the jQuery UI create event with option method to set options dynamically:
$(document).ready(function() {
$('div.slider').slider({
min: 0,
create: function(event, ui) {
$(this).slider('option', 'max', $(this).data('max'));
},
slide: function(event, ui) {
$("input#slider_value").val(ui.value);
}
});
});

Using knockout js with jquery ui sliders

I'm trying to figure out if knockout js would work nicely for the following problem:
I have multiple sliders that I want to link to textboxes.
When the textbox is changed the corresponding slider must update to the new value and vice versa.
On changing the slider value or textbox a function needs to be called that uses the input from all textboxes to calculate a result.
I have my quick and dirty jQuery solution here.
Would it be easy to achieve the same result in a more elegant way using knockout js?
I guess I would need to create a custom binding handler like its done in jQuery UI datepicker change event not caught by KnockoutJS
Here is an example: http://jsfiddle.net/jearles/Dt7Ka/
I use a custom binding to integrate the jquery-ui slider and use Knockout to capture the inputs and calculate the net amount.
--
UI
<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>
View Model
ko.bindingHandlers.slider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
$(element).slider(options);
$(element).slider({
"slide": function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
},
"change": function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
},
update: function (element, valueAccessor) {
var value = ko.unwrap(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());
I know it's some days ago but I made a few adjustments to John Earles code:
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, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (isNaN(value)) value = 0;
$(element).slider("option", allBindingsAccessor().sliderOptions);
$(element).slider("value", value);
}
};
The reason for this is that if you use options that change (fx another observable) then it won't affect the slider even if you wanted it to do so.
#John Earles and #Michael Kire Hansen: thanks for your wonderful solutions!
I used the advanced code from Michael Kire Hansen. I tied the "max:" option of the slider to a ko.observable and it turned out that the slider does not correctly update the value in this case. Example: Lets say the slider is at value 25 of max 25 und you change the max value to 100, the slider stays at the most right position, indicating that it is at the max value (but value is still 25, not 100). As soon as you slide one point to the left, you get the value updated to 99.
Solution:
in the "update:" part just switch the last two lines to:
$(element).slider("option", allBindingsAccessor().sliderOptions);
$(element).slider("value", value);
This changes the options first, then the value and it works like a charm.
Thanks so much for the help, I needed to use a range slider in my scenario so here is an extension to #John Earles and #Michael Kire Hansen
ko.bindingHandlers.sliderRange = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
$(element).slider(options);
ko.utils.registerEventHandler(element, "slidechange", function (event, ui) {
var observable = valueAccessor();
observable.Min(ui.values[0]);
observable.Max(ui.values[1]);
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
ko.utils.registerEventHandler(element, "slide", function (event, ui) {
var observable = valueAccessor();
observable.Min(ui.values[0]);
observable.Max(ui.values[1]);
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (isNaN(value.Min())) value.Min(0);
if (isNaN(value.Max())) value.Max(0);
$(element).slider("option", allBindingsAccessor().sliderOptions);
$(element).slider("values", 0, value.Min());
$(element).slider("values", 1, value.Max());
}
};
and then the HTML to accompany it
<div id="slider-range"
data-bind="sliderRange: { Min: 0, Max: 100 },
sliderOptions: {
range: true,
min: 0,
max: 100,
step: 10,
values: [0, 100]
}"></div>

jquery ui autocomplete inside jQuery Ui Dialog

Hi I have a JQuery Ui (jquery-ui-1.8.13.custom.min.js) inside a Dialog. When I start typing on the box I get the dropdown of items but it hides right away? Does anyone know why? Here is my code:
$(".openDialog").live("click", function (e) {
e.preventDefault();
var itemId = $(this).attr("data-item-id");
var ajaxurl = $(this).attr('data-ajax-refresh-url');
var dialogId = $(this).attr("data-dialog-id");
$('<div><img src="Content/images/spinner.gif" /> Loading...</div>')
.addClass("dialog")
.attr("id", $(this).attr("data-dialog-id"))
.appendTo("body")
.dialog({
width: 'auto',
title: $(this).attr("data-dialog-title"),
buttons: {
"Save": function () {
$(this).find('form').submit();
},
close: function () {
if (typeof itemId != "undefined") {
$.get(ajaxurl, { id: itemId },
function (data) {
// The data returned is a table <tr>
$("#Row" + itemId).replaceWith(data);
});
bindConfirm();
}
$(this).remove();
}
},
modal: true
}).load(this.href, function () {
$(this).find("input[data-autocomplete]").autocomplete({ source: $(this).find("input[data-autocomplete]").attr("data-autocomplete") });
});
});
They also had problems in early 1.8 releases. I remember applying a custom CSS selector to increase zIndex manually.
See also: http://forum.jquery.com/topic/autocomplete-inside-a-dialog-1-8rc2

jquery close datepicker when input lose focus

I'm using datepicker inside my input , my last field is the datepicker input , after validating it i want to set focus on another input inside my form , but the problem is the datepicker is not closed even taht it does not have the focus..
how can I close the datepicker when i set the focus on another input field?
(I tried .datepicker("hide"); but it did not worked for me).
UPDATE:
this is my code:
$(function()
{ $( "#test_date" ).datepicker({
dateFormat: "dd/mm/yy"
});
});
//when i call my function:
$( "#test_date" ).datepicker("hide"); //---> this does not work!
Thank's In Advance.
Question Edited to work with the latest version of jqueryUI
JqueryUi auto-closes the datepicker when an element loses focus by user interaction, but not when changing focus with JS.
Where you are calling your function which removes focus from the input assigned a datepicker you also need to call:
$("#test_date ~ .ui-datepicker").hide();
This code is hiding the datepicker which is a sibling (~) of #test_date.
To be dynamic, and using the class assigned by jQueryui you can do:
$(".hasDatepicker").on("blur", function(e) { $(this).off("focus").datepicker("hide"); });
;(function() {
function eventOnFocusDP(e, par) {
if (par.ver == $.fn.jquery) {
if (this.tmr) clearTimeout(this.tmr);
par.lbl1.text(par.msgs[1]);
this.tmr = setTimeout(function() { par.inpNP.focus(); }, par.secs*1e3);
}
}
function eventOnFocusNP(e, par) {
if (par.ver == $.fn.jquery) {
par.lbl1.text(par.msgs[0]);
par.lbl2.text(par.msgs[2]);
}
}
function eventOnBlurNP(e, par) {
if (par.ver == $.fn.jquery) par.lbl2.text("");
}
function eventOnBlurHDP(e, par) {
if (par.ver == $.fn.jquery) {
$(this).off("focus").datepicker("hide");
}
}
function test(secs) {
this.ver = $.fn.jquery;
this.secs = (typeof secs)=='number'?secs:2;
this.msgs = [
'This will lose focus to box below '+this.secs+' seconds after it gains focus.',
'Losing focus in '+this.secs+' seconds!',
'Focus now on bottom box.'
];
this.inpDP = $('[name=datePicker]');
this.inpNP = $('[name=nextPicker]');
this.lbl1 = $('#dPMsg').text(this.msgs[0]);
this.lbl2 = $('#dPMsg2');
var par = this;
this.inpDP.datepicker({ dateFormat: "dd/mm/yy" })
.on('focus', function(e) { eventOnFocusDP.apply(this, [e, par]) });
this.inpNP.on('focus', function(e) { eventOnFocusNP.apply(this, [e, par]) });
this.inpNP.on('blur', function(e) { eventOnBlurNP.apply(this, [e, par]) });
$(document).on('blur', '.hasDatepicker', function(e) { eventOnBlurHDP.apply(this, [e, par]) });
return this;
}
function init() {
window.Test = test;
setTimeout(function() {
$(document).on('change', '.switcher, .switcher-ui', function(e) { if (window.Test) new Test(); });
$(jQueryUISwitcher).trigger('change');
}, 1e3);
}
if (document.readyState == "complete") init();
else jQuery(window).on('load', init);
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/JDMcKinstry/cfc32292cbbfa548fb9584db05b2b2fc/raw/4f16f7ee441dfb98aa166a2e84193b14574a46d1/jquery.switcher.js"></script>
<form action="javascript: void 0">
<input type="text" name="datePicker" id="dP" placeholder="mm/dd/yyyy" />
<label for="dP" id="dPMsg"></label>
<hr />
<input type="text" name="nextPicker" placeholder="tab to here" />
<label for="dP" id="dPMsg2"></label>
</form>
<hr />
<hr />
<hr />
Here's a modified solution that worked for me:
$(".hasDatepicker").each(function (index, element) {
var context = $(this);
context.on("blur", function (e) {
// The setTimeout is the key here.
setTimeout(function () {
if (!context.is(':focus')) {
$(context).datepicker("hide");
}
}, 250);
});
});
My version of js:
<script type="text/javascript"> $(function () {
$("#dtp1").on("dp.change", function (e) {
$('#dtp1').data("DateTimePicker").hide();
});
});
I hope it's help you
This worked for me:
$("#datepickerTo").datepicker({
onSelect: function () {
$(this).off( "focus" );
}
});

Resources