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>
Related
I've written a script for a simple jquery ui slider. There are two sliders (later I'll add more) and when you change their value, it displays below them, and then updates a total. What I'm having a hard time figuring out, is how to make it so as you're sliding the values are getting updated, instead of getting updated after you've finished.
Thanks for any help!
Fiddle of Demo
http://jsfiddle.net/tMmDy/
HTML
<div class="slider_1 slider"></div>
<p id="slider_1-value"></p>
<div class="slider_2 slider"></div>
<p id="slider_2-value"></p>
CSS
.slider {
width:100px;
height:5px;
background:blue;
}
JS
$(".slider").slider({
animate: "fast",
max: 25,
min: 0,
step: 1,
value: 10,
option: {}
});
$(".slider_1").on("slidechange", function (event, ui) {
total_1 = $(".slider_1").slider("value");
$("#slider_1-value").html(total_1);
total_value_update();
});
$(".slider_2").on("slidechange", function (event, ui) {
total_2 = $(".slider_2").slider("value");
$("#slider_2-value").html(total_2);
total_value_update();
});
function total_value_update() {
total_values = total_1 + total_2;
$("#total_value").html(total_values);
}
Use the slider's .slide() event and try it like this:
jsFiddle example
var total_1, total_2;
$(".slider").slider({
animate: "fast",
max: 25,
min: 0,
value: 10,
slide: function (event, ui) {
total_1 = $(".slider_1").slider("value");
total_2 = $(".slider_2").slider("value");
$("#slider_1-value").html(total_1);
$("#slider_2-value").html(total_2);
total_value_update();
},
change: function (event, ui) {
total_1 = $(".slider_1").slider("value");
total_2 = $(".slider_2").slider("value");
$("#slider_1-value").html(total_1);
$("#slider_2-value").html(total_2);
total_value_update();
}
});
function total_value_update() {
total_values = total_1 + total_2;
$("#total_value").html(total_values);
}
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
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);
}
});
});
Say I have the following jQuery UI sliders that are linked to a textbox.
Is it possible to allow the user to enter values in the textbox that are greater than the sliders value (and set the slider to max)?
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);
}
};
In this example: http://jsfiddle.net/jearles/Dt7Ka/12/ I would like to be able to save values over 100 in the textboxes and have the sliders appear at their max value.
You can do with only updating the observable value in your slidechange event if the observable's value is less then options.max:
ko.utils.registerEventHandler(element, "slidechange", function (event, ui) {
var observable = valueAccessor();
var currentMax = $(element).slider("option", "max");
if (observable() <= currentMax)
observable(ui.value);
});
JSFiddle demo.
Put this lines after the line: if (isNaN(value)) value = 0; at the binding update function
if(value>100){
$(element).slider("option","max",value);
}
Example:
http://jsfiddle.net/Razaz/Qy6jR/4/
It changes the maximum value of the slider to the new value entered in the textbox if the value is greater than 100
Greetings.
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