I have a slider which has min 1, max 24 and steps of 1. Is it possible I can make the steps as 1, 2, 3, 4, 12, 18, 24? That's all the values that will be shown when the slider changes left or right.
<input id="ex1" data-slider-id="ex1Slider" type="text" data-bind="sliderValue: {value: loanperiod, min:0, max: 24, step: 1, formatter:formatter1}, event: { change: $root.getInvestmentDetailsForBorrower }, valueUpdate: 'afterkeydown' " style="display: none;">
Slider has knockout.js binding.
If needed then I am adding the slider binding here
// Custom binding for slider value
(function ($) {
ko.bindingHandlers.sliderValue = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var params = valueAccessor();
// Check whether the value observable is either placed directly or in the paramaters object.
if (!(ko.isObservable(params) || params['value']))
throw "You need to define an observable value for the sliderValue. Either pass the observable directly or as the 'value' field in the parameters.";
// Identify the value and initialize the slider
var valueObservable;
if (ko.isObservable(params)) {
valueObservable = params;
$(element).slider({value: ko.unwrap(params)});
}
else {
valueObservable = params['value'];
if (!Array.isArray(valueObservable)) {
// Replace the 'value' field in the options object with the actual value
params['value'] = ko.unwrap(valueObservable);
$(element).slider(params);
}
else {
valueObservable = [params['value'][0], params['value'][1]];
params['value'][0] = ko.unwrap(valueObservable[0]);
params['value'][1] = ko.unwrap(valueObservable[1]);
$(element).slider(params);
}
}
// Make sure we update the observable when changing the slider value
$(element).on('slide', function (ev) {
if (!Array.isArray(valueObservable)) {
valueObservable(ev.value);
}
else {
valueObservable[0](ev.value[0]);
valueObservable[1](ev.value[1]);
}
}).on('change', function (ev) {
if (!Array.isArray(valueObservable)) {
valueObservable(ev.value.newValue)
}
else {
valueObservable[0](ev.value.newValue[0]);
valueObservable[1](ev.value.newValue[1]);
}
});
// Clean up
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).slider('destroy');
$(element).off('slide');
});
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var modelValue = valueAccessor();
var valueObservable;
if (ko.isObservable(modelValue))
valueObservable = modelValue;
else
valueObservable = modelValue['value'];
if (!Array.isArray(valueObservable)) {
$(element).slider('setValue', parseFloat(valueObservable()));
}
else {
$(element).slider('setValue', [parseFloat(valueObservable[0]()),parseFloat(valueObservable[1]())]);
}
}
};
})(jQuery);
// Custom binding for slider
(function ($) {
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);
}
};
})(jQuery);
I think you just want to have a function that takes the raw slider value and translates it to your custom values. Then use the result of that wherever you need the value.
vm = {
sliderValues: [1, 2, 3, 4, 12, 18, 24],
rawSliderValue: ko.observable(1),
sliderValue: ko.pureComputed(function() {
return vm.sliderValues[vm.rawSliderValue() - 1];
})
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="range" min="1" data-bind="attr {max: sliderValues.length}, value: rawSliderValue, valueUpdate: 'input'" />
<div data-bind="text: sliderValue"></div>
Related
Thanks to this tutorial I managed to create a KnockOut binding handler for Google's DataTable.
This is my binding handler, so far:
ko.bindingHandlers.dataTable = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var table = new google.visualization.Table(element);
ko.utils.domData.set(element, "dataTable", table);
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
// Get options:
var options = allBindings.get("tableOptions") || {};
// Default options:
options.width = options.width || "200px";
options.height = options.height || "200px";
options.showRowNumber = options.showRowNumber || false;
// Get events:
var onSelected = allBindings.get("select") || false;
if (onSelected) {
$(element).on("select", function(event, ui) {
valueAccessor()(ui.value);
});
}
var table = ko.utils.domData.get(element, "dataTable");
table.draw(value, options);
}
};
This is my HTML part:
<div data-bind="dataTable: $root.getData(), tableOptions: {width: '100%',height: '200px', 'allowHtml': true, 'cssClassNames': {'selectedTableRow': 'orange-background'} }"></div>
So far I get a table with fixed headers which works just fine.
Now I want to extent to binding handler to react on the 'select row' event.
I tried this using the // Get events section in my handler but this is not working.
In my HTML I add select: $root.selectedRow(),
In my function selectedRow() I put a console.log("In selectedRow"). When I load the page I see selectedRow is called for every row, but when I click on a row it is not called.
The row its background is changed to orange, so Google is adding the selectedTableRow class.
How to wrap/bind to the select event?
The main thing that's going wrong is, if I'm not mistaken, the way you're trying to attach your event listener.
Your $(element).on("select", onSelect) is not how the library you're using attaches event listeners. In the documentation you can see that you actually need to use: google.visualization.events.addListener(table, 'select', selectHandler);
Additionally, it's better to attach the event listener in the init method. update is called whenever your data changes, so it might add multiple event listeners.
Here's a working example of your code:
google.charts.load('current', {
'packages': ['table']
});
google.charts.setOnLoadCallback(function() {
ko.bindingHandlers.dataTable = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var table = new google.visualization.Table(element);
ko.utils.domData.set(element, "dataTable", table);
// Get events:
var onSelected = allBindings.get("select") || false;
if (onSelected) {
google.visualization.events.addListener(table, 'select', function() {
// TODO: null/undefined/multiple selection checks
var data = valueAccessor();
var row = table.getSelection()[0].row;
onSelected(data.getValue(row, 1)); // Sends salary of clicked row
});
}
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
// Get options:
var options = allBindings.get("tableOptions") || {};
// Default options:
options.width = options.width || "200px";
options.height = options.height || "200px";
options.showRowNumber = options.showRowNumber || false;
var table = ko.utils.domData.get(element, "dataTable");
table.draw(value, options);
}
};
ko.applyBindings({
onSelect: function(value) {
alert(value);
},
getData: function() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Name');
data.addColumn('number', 'Salary');
data.addColumn('boolean', 'Full Time Employee');
data.addRows([
['Mike', {
v: 10000,
f: '$10,000'
},
true
],
['Jim', {
v: 8000,
f: '$8,000'
},
false
],
['Alice', {
v: 12500,
f: '$12,500'
},
true
],
['Bob', {
v: 7000,
f: '$7,000'
},
true
]
]);
return data;
}
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div data-bind="dataTable: getData(), tableOptions: {width: '100%',height: '200px', 'allowHtml': true, 'cssClassNames': {'selectedTableRow': 'orange-background'} }, select: onSelect"></div>
function Employee() {
var self = this;
self.name = ko.observable("John");
}
ko.bindingHandlers.test = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// implementation
}
}
<div data-bind="test: name"></div>
Is there a way to get the observable name? not the value of the observable.
TIA.
Update:
This is the code snippet.
function ViewModel() {
var self = this;
$.ajax({
type: type,
url: url,
success: function (data) {
ko.mapping.fromJS(data, {} , self);
}
});
self.item = ko.observable(self.my_item);
self.selectedItem = ko.observable();
}
ko.bindingHandlers.item = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
$el = $(element);
var propName = allBindings().name;
var val = ko.unwrap(valueAccessor());
$el.attr("src", val);
$el.click(function () {
viewModel.selectedItem(propName);
});
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
$el = $(element);
var ops = allBindings().name;
var val = ko.unwrap(valueAccessor());
$el.attr("src", val);
}
};
ko.bindingHandlers.selectItem = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
$el = $(element);
$el.attr("src", valueAccessor());
$el.click(function () {
bindingContext.$data.item()[ko.unwrap(bindingContext.$data.selectedItem)](valueAccessor());
});
}
};
<img height="25" width="25" data-bind="item: item().img1, name: 'img1'" />
<img height="20" width="20" data-bind="selectItem: '/images/myimage1.png'" />
<img height="20" width="20" data-bind="selectItem: '/images/myimage2.png'" />
<img height="20" width="20" data-bind="selectItem: '/images/myimage3.png'" />
When you click images that has selectItem the first image should replaced its src attribute. If you have better way to do this please suggest.
FYI, the properties inside items observables are link of images.
TIA.
You are getting ahead of yourself with the custom binding.
The bottom line is: You don't need a custom binding for what you want to do. It's easy - if you don't make it complicated:
function loadImages() {
// return $.get(url);
// mockup Ajax response, in place of a real $.get call
return $.Deferred().resolve({
items: [
{height: 20, width: 20, src: 'http://placehold.it/150/30ac17', title: 'image 1'},
{height: 20, width: 20, src: 'http://placehold.it/150/412ffd', title: 'image 2'},
{height: 20, width: 20, src: 'http://placehold.it/150/c672a0', title: 'image 3'}
]
}).promise();
}
function ImageList() {
var self = this;
// data
self.items = ko.observableArray();
self.selectedItem = ko.observable();
// init
loadImages().done(function (data) {
ko.mapping.fromJS(data, {}, self);
});
}
ko.applyBindings(new ImageList())
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<div data-bind="with: selectedItem">
<img data-bind="attr: {height: 25, width: 25, src: src}">
</div>
<div data-bind="foreach: items">
<img data-bind="attr: {height: height, width: width, src: src}, click: $root.selectedItem" />
</div>
<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
Note how I use selectedItem as a click event handler. This is possible because in event handlers, knockout passes the relevant object (in this case, an image object from the array) as the first argument. Conveniently, observables set their value to the first argument you call them with. And presto: You have click event handler that sets the last clicked object.
EDIT
"I need multiple selected item then my items are just my context menu not just one selected item."
function loadImages() {
// return $.get(url);
// mockup Ajax response, in place of a real $.get call
return $.Deferred().resolve({
items: [
{height: 20, width: 20, src: 'http://placehold.it/150/30ac17', title: 'image 1'},
{height: 20, width: 20, src: 'http://placehold.it/150/412ffd', title: 'image 2'},
{height: 20, width: 20, src: 'http://placehold.it/150/c672a0', title: 'image 3'}
]
}).promise();
}
function ImageList() {
var self = this;
// data
self.items = ko.observableArray();
self.selectedItems = ko.observableArray();
// init
loadImages().done(function (data) {
ko.mapping.fromJS(data, {}, self);
});
self.selectItem = function (item) {
var pos = ko.utils.arrayIndexOf(self.selectedItems(), item);
if (pos === -1) self.selectedItems.push(item);
};
self.deselectItem = function (item) {
var pos = ko.utils.arrayIndexOf(self.selectedItems(), item);
if (pos !== -1) self.selectedItems.remove(item);
};
}
ko.applyBindings(new ImageList())
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<div data-bind="foreach: selectedItems">
<img data-bind="attr: {height: 25, width: 25, src: src}, click: $root.deselectItem">
</div>
<div data-bind="foreach: items">
<img data-bind="attr: {height: height, width: width, src: src}, click: $root.selectItem" />
</div>
<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
<div data-bind="foreach: {data: skills, as: 'skill'}" >
<div data-bind="foreach: Object.keys(skill)" >
<a data-bind="text: $data"></a> : <a data-bind="text: skill[$data]"></a>
</div>
</div>
v = new function AppViewModel() {
this.skills = [{rates:"sdfdcvxcsd", cat: 2, car:55}, {color:"sdfdcvxcsd", zoo: 2,boat:55}];
}
ko.applyBindings(v);
Try something like this
view:
<div data-bind="test:name,propertyName:'name'"></div>
viewModel:
ko.bindingHandlers.test = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var propertyName = allBindings().propertyName; //property name here
ko.bindingHandlers.text.update(element, valueAccessor);
alert(propertyName)
}
};
function Employee() {
var self = this;
self.name = ko.observable("John");
}
ko.applyBindings(new Employee());
working fiddle here
This will get you the observable name.
This converts the valueAccessor (function(){return name }) to a string which is then split to remove the observable name.
ko.bindingHandlers.GetObservableName = {
init: function (element, valueAccessor) {
var observableName = String(valueAccessor).split(' ')[1];
alert(observableName);
}
};
function Employee() {
var self = this;
self.name = ko.observable("John");
}
<div data-bind="GetObservableName: name"></div>
Here is a JSFiddle
The index of the split method in the fiddle is different to the one in the example above. The above works for me in Visual Studio 2013.
Thanks
I have this jsfiddle which shows a table and some users with roles.
I want to have a modal form pop up when some clicks add roles etc.
There seems to be an error on the update property of this ko.bindingHandlers.modal function:
ko.bindingHandlers.modal = {
init: function (element, valueAccessor) {
$(element).modal({ show: false }).on("hidden", function () {
var data = valueAccessor();
if (ko.isWriteableObservable(data))
data(null);
});
return ko.bindingHandlers["with"].init.apply(this, arguments);
},
update: function (element, valueAccessor) {
var data = ko.unwrap(valueAccessor());
$(element).modal( data ? "show" : "hide" );
return ko.bindingHandlers["with"].update.apply(this, arguments); // Error on this line
}
};
I don't why this is happening, I have copied the code from Ryan Niemeyer dev video
Its 34mins in.
It's a Bootstrap modal dialogue, using Knockout JS as the binding library
The with binding does no longer have the update function
From the init function use
ko.applyBindingsToNode(element, { with: valueAccessor() });
Update
ko.bindingHandlers.modal = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).modal({ show: false }).on("hidden", function () {
var data = valueAccessor();
if (ko.isWriteableObservable(data))
data(null);
});
ko.applyBindingsToNode(element, { with: valueAccessor() }, bindingContext);
return { controlsDescendantBindings: true };
},
update: function (element, valueAccessor) {
var data = ko.unwrap(valueAccessor());
$(element).modal( data ? "show" : "hide" );;
}
};
I have a custom knockoutJs binding for the date picker.
ko.bindingHandlers.valueAsDatePicker = {...}
When the bound input field status (enabled/disabled) is bound to a KO observable it doesn't enable/disable the datepicker icon.
HTML
<input id="txtRequestedTo" type="text" placeholder="dd/mm/yyyy"
data-bind="valueAsDatePicker: reqDateTo, disable: reqDateFrom().length < 1" />
Custom Binding
ko.bindingHandlers.valueAsDatePicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);
formatAndSetDateValue(element, valueAccessor, allBindingsAccessor);
// Init UI datepicker
var dateFormat = allBindingsAccessor.dateFormat
$(element).datepicker({
dateFormat: dateFormat,
changeMonth: true,
changeYear: true,
yearRange: '1900:' + new Date().getFullYear(),
maxDate: 0,
showOn: "button",
buttonImage: "Content/images/sprite-base/sprite/icon-calender.png",
buttonImageOnly: true,
constrainInput: false,
buttonText: ""
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
// Use the value binding
ko.bindingHandlers.value.update(element, valueAccessor, allBindingsAccessor);
formatAndSetDateValue(element, valueAccessor, allBindingsAccessor);
valueAccessor().valueHasMutated();
}
};
I want the datepicker to be disabled if the element is disabled, and Vice Versa.
Thanks a million Robert,
Here is the solution that worked.
Using KnockOutJS V3.1
init: function (element, valueAccessor, allBindings) {
...
//Disable the datepicker if the item is disabled or enabled.
if (allBindings.has('disable')) {
if (allBindings.get('disable')()) {
$(element).datepicker('disable');
}
else {
$(element).datepicker('enable');
}
var subscription = allBindings.get('disable').subscribe(function (newValue) {
if (newValue) {
$(element).datepicker('disable');
} else {
$(element).datepicker('enable');
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
subscription.dispose();
});
}
}
You have to use
if (allBindings.has('disable')) {
otherwise
allBindings.get('disable')
will be undefined as not all datepicker bound fields in the view have the disabled attribute.
The trick here is to get access to the disabled binding as an observable so you can attach a manual subscription. At the moment the result of the disable expression is passed into bindingHandler ( via allBindingAccessor ).
One you get a subscription you can invoke DatePicker's disable option
HTML
<input id="txtRequestedTo" type="text" placeholder="dd/mm/yyyy"
data-bind="valueAsDatePicker: reqDateTo, disable: reqDateFrom.disabled" />
JAVASCRIPT
var reqDateFrom = ko.observable();
var reqDateTo = ko.observable();
reqDateTo.disabled = ko.computed( function() {
return (reqDateFrom() || '').length === 0;
} );
ko.bindingHandlers.valueAsDatePicker {
init: function(element, valueAccessor, allBindingsAccessor) {
....
// ko 3.0 syntax. For 2.x use allBindingAccessor['disable']
var subscription = allBindingAccessor.get('disable').subscribe( function(newValue) {
$(element).datepicker('option', 'disabled', newValue);
});
// Make sure the subscription is disposed when the element is torn down
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
subscription.dispose();
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
}
}
I need a some help. I have a code http://jsfiddle.net/ZNvWR/19/. I'm newbie in knockout, and I can't find any solution.
So, how to rewrite this code for getting working inputs (change values in inputs changes slider values)?
<div data-bind="jqSlider: percent, jqOptions: { min: 0, max: 100, range:true }"></div>
<hr/>
Percent: <input data-bind="value: percent()[0]" />
Percent: <input data-bind="value: percent()[1]" />
ko.bindingHandlers.jqSlider = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize the control
var options = allBindingsAccessor().jqOptions || {};
$(element).slider(options);
//handle the value changing in the UI
ko.utils.registerEventHandler(element, "slide", function() {
//would need to do some more work here, if you want to bind against non-observables
var observable = valueAccessor();
observable($(element).slider("values"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).slider("destroy");
});
},
//handle the model value changing
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).slider("values", value);
}
};
var viewModel = {
percent: ko.observableArray([10,50])
};
ko.applyBindings(viewModel)
I just helped antoher SO user with a slider, it can be altered like this to do what you want
http://jsfiddle.net/N9uwx/3/
<input data-bind="value: min" /><input data-bind="value: max" /><div data-bind="slider: { min: min, max: max }, sliderOptions: {min: 0, max: 100, step: 1}"></div>