I need some help to check a checkbox on page load using knockout & icheck plug in.
I have created a custom binding in order to listen to 'ifChecked' method of check but it's not working.
<input type="checkbox" id="access-user-information" name="edit_existing_user" data-bind = "iCheck: { checked: selectedUser() && selectedUser().edit_existing_user==1}">
Knockout Code:
ko.bindingHandlers.iCheck = {
init: function (element, valueAccessor) {
$(element).iCheck({
checkboxClass: 'icheckbox_square-red'
});
$(element).on('ifChecked', function (event) {
var observable = valueAccessor();
observable.checked(true);
});
},
update: function (element, valueAccessor) {
var observable = valueAccessor();
}
};
Some changes:
Listen for a change at the checkbox ('ifToggled' event)
The observable of a checkbox should be a boolean, so set the value according to the checkbox state (true/false). I used the jQuery .is(':checked') for that.
Set the initial state in the "update" function.
The code for the binding:
ko.bindingHandlers.iCheck = {
init: function(element, valueAccessor) {
$(element).iCheck({
checkboxClass: 'icheckbox_flat-red'
});
$(element).on('ifToggled', function(event) {
var observable = valueAccessor();
observable($(element).is(':checked'));
});
},
update: function(element, valueAccessor) {
var observable = valueAccessor();
observable($(element).is(':checked'));
}
};
Here a little jsbin to test it.
Hope that helps.
If your checkbox is enabled, and you can give it a new value by tapping it, it has to be a ko.computed with a read and write method.
When you select the current user, you want it to automatically be checked. This is the read part:
this.isEditingCurrentUser = ko.computed(function() {
return this.selectedUser() &&
this.selectedUser().edit_existing_user === 1;
}, this);
You should recognise this expression: it's what you used as your data-bind. Having a ko.computed in your viewmodel is very similar to using expressions in data-binds.
Now, there's a problem when you want to override this value with true or false: knockout can't decide for you how to reverse the logic. This, you have to specify. It'll look like this:
this.isEditingCurrentUser = ko.computed({
read: function() { /* the expression above */ },
write: function(newValue) { /* How to handle a user input override */ }
});
I show how this works in the example below. I've left out the custom binding since the real logic problem needs to be solved first:
var VM = function() {
var currentUserId = 1;
this.users = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
];
this.selectedUser = ko.observable(2);
this.editCurrent = ko.computed({
read: function() {
return this.selectedUser() &&
this.selectedUser().id === currentUserId;
}.bind(this),
write: function(val) {
var newUser = val
? this.users.find(function(user) {
return user.id === currentUserId;
})
: null;
this.selectedUser(newUser);
}.bind(this)
}, this);
};
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: users, value: selectedUser, optionsCaption: 'Select a user', optionsText: 'name'"></select>
<div>
<input type="checkbox" data-bind="checked: editCurrent">
Edit current user
</div>
Related
I am struggling a bit with implementing a custom tooltip binding that is invoked with a mouseover, so I need an event handler to display the tooltip. Now, the content of the tooltip can be either a static string, or an observable with content that can change - but the tooltip module itself is a singleton. Plus, there can be more than one tooltip elements on the page, so the tooltip show function just receives the event and the content to display. For me, the easiest way would be if I could access the actual bound value from the event target in the listener alone.
Now I know there is the ko.dataFrom function, but for the static variant, I cannot see the actual bound value anywhere (i.e. the same that is returned with valueAccessor() in the init function), just the whole viewmodel.
Here is some simplified code to illustrate it:
<span class="icon help" data-bind="tooltip: 'some static string'"></span>
(But as I said there also might be an observable behind the tooltip binding)
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
element.addEventListener("mouseover", ko.bindingHandlers.tooltip.showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("mouseover", ko.bindingHandlers.tooltip.showTooltip);
});
},
showTooltip: function(event) {
var data = ko.dataFor(this);
tooltip.show(event, data);
}
}
The problem is now that "data" only contains the whole viewmodel the view was bound to. But where is 'some static string' to be found?
I also tried with "ko.contextFor", but then $root, $data and $rawData were all the same. I am sure I overlooked something here, or is that not possible without adding the string to a data attribute or something similar, which I would like to avoid.
The fundamental issue here is that you shouldn't be trying to reuse the event handler, or at least, if you're going to, bind it.
I'd just move showTooltip into init:
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var showTooltip = function(event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
};
element.addEventListener("mouseover", showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("mouseover", showTooltip);
});
}
};
Small functions are very cheap.
Live Example (using click instead of mouseover and just appending to the document):
var tooltip = {
show: function(event, value) {
var div = document.createElement('div');
div.innerHTML = "tooltip: " + value;
document.body.appendChild(div);
}
};
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var showTooltip = function(event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
};
element.addEventListener("click", showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", showTooltip);
});
}
};
var vm = {
items: [
{text: "one", tooltip: "uno"},
{text: "two", tooltip: "due"},
{text: "three", tooltip: "tre"},
{text: "four", tooltip: "quattro"}
]
};
ko.applyBindings(vm, document.body);
// To prove the observables work, update one after a delay
setTimeout(function() {
vm.items[2].tooltip = "TRE";
}, 100);
<div data-bind="foreach: items">
<div data-bind="text: text, tooltip: tooltip"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Alternately, use bind:
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var boundHandler = ko.bindingHandlers.tooltip.showTooltip.bind(
null,
valueAccessor
);
element.addEventListener("click", boundHandler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", boundHandler);
});
},
showTooltip: function(valueAccessor, event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
}
};
var tooltip = {
show: function(event, value) {
var div = document.createElement('div');
div.innerHTML = "tooltip: " + value;
document.body.appendChild(div);
}
};
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var boundHandler = ko.bindingHandlers.tooltip.showTooltip.bind(
null,
valueAccessor
);
element.addEventListener("click", boundHandler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", boundHandler);
});
},
showTooltip: function(valueAccessor, event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
}
};
var vm = {
items: [
{text: "one", tooltip: "uno"},
{text: "two", tooltip: "due"},
{text: "three", tooltip: "tre"},
{text: "four", tooltip: "quattro"}
]
};
ko.applyBindings(vm, document.body);
// To prove the observables work, update one after a delay
setTimeout(function() {
vm.items[2].tooltip = "TRE";
}, 100);
<div data-bind="foreach: items">
<div data-bind="text: text, tooltip: tooltip"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Note how the bound value will be in front of the event argument.
We're using a tree-style navigation element which needs to allow other directives/controllers to know:
What the current selection is, and
When the row selection changes
I'm trying to determine the best angular-way to handle this.
Until now, we've been firing an event the entire app can listen to - less than ideal but ensures nothing is hard-coded to communicate directly with the component.
However, we now have a need to obtain the current selection when another component is activated. An event won't fly.
So I'm considering a service, some singleton which holds the current selection and can be updated directly by the tree, and read from by anyone who needs it.
However, this present some other issues:
Would it be better to ditch the event entirely, and have components which need to know when it changes $watch the service's nodeId?
If I use $watch, it seems like I should expose the object directly - so using getters/setters won't work unless I want to complicate the needed $watch code?
Part of my concern is that this would allow any component to set the value and this is intentionally not something we'll allow - the change will not impact the tree but will de-sync the service values from the true values, and will fire invalid $watches.
Implementing a getter should not lead to a complicated $watcher:
Service:
angular.service('myService', function() {
var privateVar = 'private';
return {
getter: function() {
return privateVar;
};
});
Controller:
angular.controller('myController', function(myService){
$scope.watch(myService.getter, function(){
//do stuff
};
});
See this plunker: http://plnkr.co/edit/kLDwFg9BtbkdfoSeE7qa?p=preview
I think using a service should work and you don't need any watchers for it.
In my demo below or in this fiddle I've added the following:
One service/factory sharedData that's storing the data - selection and items
Another service for eventing sharedDataEvents with observer/listener pattern.
To display the value in component2 I've used one-way binding, so that component can't change the selection.
Also separating data from events prevents a component from changing the selection. So only MainController and Component1 can change the selection.
If you're opening the browser console you can see the listeners in action. Only listener of component3 is doing something (after 3 selection changes it will do an alert) the others are just logging the new selection to console.
angular.module('demoApp', [])
.controller('MainController', MainController)
.directive('component1', Component1)
.directive('component2', Component2)
.directive('component3', Component3)
.factory('sharedData', SharedData)
.factory('sharedDataEvents', SharedDataEvents);
function MainController(sharedData) {
sharedData.setItems([{
id: 0,
test: 'hello 0'
}, {
id: 1,
test: 'hello 1'
}, {
id: 2,
test: 'hello 2'
}]);
this.items = sharedData.getItems();
this.selection = this.items[0];
}
function Component1() {
return {
restrict: 'E',
scope: {},
bindToController: {
selection: '='
},
template: 'Comp1 selection: {{comp1Ctrl.selection}}'+
'<ul><li ng-repeat="item in comp1Ctrl.items" ng-click="comp1Ctrl.select(item)">{{item}}</li></ul>',
controller: function($scope, sharedData, sharedDataEvents) {
this.items = sharedData.getItems();
this.select = function(item) {
//console.log(item);
this.selection = item
sharedData.setSelection(item);
};
sharedDataEvents.addListener('onSelect', function(selected) {
console.log('selection changed comp. 1 listener callback', selected);
});
},
controllerAs: 'comp1Ctrl'
};
}
function Component2() {
return {
restrict: 'E',
scope: {},
bindToController: {
selection: '#'
},
template: 'Comp2 selection: {{comp2Ctrl.selection}}',
controller: function(sharedDataEvents) {
sharedDataEvents.addListener('onSelect', function(selected) {
console.log('selection changed comp. 2 listener callback', selected);
});
},
controllerAs: 'comp2Ctrl'
};
}
function Component3() {
//only listening and alert on every third change
return {
restrict: 'E',
controller: function($window, sharedDataEvents) {
var count = 0;
sharedDataEvents.addListener('onSelect', function(selected, old) {
console.log('selection changed comp. 3 listener callback', selected, old);
if (++count === 3) {
count = 0;
$window.alert('changed selection 3 times!!! Detected by Component 3');
}
});
}
}
}
function SharedData(sharedDataEvents) {
return {
selection: {},
items: [],
setItems: function(items) {
this.items = items
},
setSelection: function(item) {
this.selection = item;
sharedDataEvents.onSelectionChange(item);
},
getItems: function() {
return this.items;
}
};
}
function SharedDataEvents() {
return {
changeListeners: {
onSelect: []
},
addListener: function(type, cb) {
this.changeListeners[type].push({ cb: cb });
},
onSelectionChange: function(selection) {
console.log(selection);
var changeEvents = this.changeListeners['onSelect'];
console.log(changeEvents);
if ( ! changeEvents.length ) return;
angular.forEach(changeEvents, function(cbObj) {
console.log(typeof cbObj.cb);
if (typeof cbObj.cb == 'function') {
// callback is a function
if ( selection !== cbObj.previous ) { // only trigger if changed
cbObj.cb.call(null, selection, cbObj.previous);
cbObj.previous = selection; // new to old for next run
}
}
});
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.js"></script>
<div ng-app="demoApp" ng-controller="MainController as ctrl">
<p>Click on a list item to change selection:</p>
<component1 selection="ctrl.selection"></component1> <!-- can change the selection -->
<component2 selection="{{ctrl.selection}}"></component2>
<component3></component3>
</div>
I'm working on a project where im using .net web api, knockout and in this example, the jquery plugin select2.
What im trying to do is to set some field values after the change of the selection.
The select2 control list is loaded after ajax call and the objects contain more data than just id and text.
How can i get the rest of the data, so i can fill the other inputs with it?
Shortly, im trying to update a viewmodel after the change of the selection (but i get the data when this plugin makes the ajax call).
Here is a sample data that the selected object should contain:
{
"Customer":{
"ID":13,
"No":"0000012",
"Name":"SomeName",
"Address":"SomeAddress",
"ZipCode":"324231",
"City":"SimCity",
"Region":"SS",
"Phone":"458447478",
"CustomerLocations":[]
}
}
Here is where i am for now:
Sample html:
<input type="hidden" data-bind="select2: { optionsText: 'Name', optionsValue: 'ID', sourceUrl: apiUrls.customer, model: $root.customer() }, value: CustomerID" id="CustomerName" name="CustomerName" />
<input type="text" data-bind="........" />
<input type="text" data-bind="........" />
etc...
and this is the custom binding:
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var optionsText = ko.utils.unwrapObservable(obj.optionsText);
var optionsValue = ko.utils.unwrapObservable(obj.optionsValue);
var sourceUrl = ko.utils.unwrapObservable(obj.sourceUrl);
var selectedID = ko.utils.unwrapObservable(allBindings.value);
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
$(element).select2({
placeholder: "Choose...",
minimumInputLength: 3,
initSelection: function (element, callback) {
if (model && selectedID !== "") {
callback({ id: model[optionsValue](), text: model[optionsText]() });
}
},
ajax: {
quietMillis: 500,
url: sourceUrl,
dataType: 'json',
data: function (search, page) {
return {
page: page,
search: search
};
},
results: function (data) {
var result = [];
$.each( data.list, function( key, value ) {
result.push({ id: value[optionsValue], text: value[optionsText] });
});
var more = data.paging.currentPage < data.paging.pageCount;
return { results: result, more: more };
}
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
var selectedID = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('val', selectedID);
$(element).on("change", function (e) {
//...
});
}
};
Getting the selected id or text is not a problem, but how not to loose the rest of the information after the ajax call?
Where can i set/get this object so i can have all the data that it contains?
Thank you
When you build a object literal for your results, added the full object as a "data" property.
result.push({ id: value[optionsValue], text: value[optionsText], data: value });
Then handle the select2-selected event thrown by select2. The event object this should contain your object literal as the choice property.
$element.on('select2-selected', function(eventData) {
if ( eventData.choice ) {
// item selected
var dataObj = eventData.choice.data;
var selectedId = eventData.choice.id;
} else {
// item cleared
}
});
For select2 v4, you can use $(elem).select2('data') to get the selected objects.
$('selected2-enabled-elements').on('change', function(e) {
console.log($(this).select2('data'));
});
Example: https://jsfiddle.net/calvin/p1nzrxuy/
For select2 versions before v4.0.0 you can do:
.on("select2-selecting", function (e) {
console.log(e.object);
})
From v4.0.0 on and upwards the following should work:
.on("select2-selecting", function (e) {
$(this).select2('data')
})
I have an observable array with items bound to an ul. I need to calculate the width of the entire set of li items once the items have been added so I can set the width of the ul element to the total width of the child li elements.
How can I go about doing this with a foreach binding?
<div>
<h2 data-bind="visible: items().length">Test</h2>
<ul data-bind="foreach: { data: items, afterAdd: $root.myAfterAdd }">
<li data-bind="text: name"></li>
</ul>
</div>
And the JavaScript:
var viewModel = {
items: ko.observableArray([
{ name: "one" },
{ name: "two" },
{ name: "three" }
]),
myAfterAdd: function(element) {
if (element.nodeType === 1) {
alert("myAfterAdd");
}
},
addItem: function() {
this.items.push({ name: 'new' });
}
};
ko.applyBindings(viewModel);
// once foreach has finished rendering I have to set the width
// of the ul to be the total width of the children!
I tried using afterAdd so I can 'update' the width of the ul after each element is added, unfortunately I have discovered that afterAdd does not fire for the initial items! It will only fire when pushing additional items...
Note that the content inside the li items will be of unknown width :)
See this fiddle for a sample scenario.
I had a similar problem. You can use this to make that initial calculation:
ko.bindingHandlers.mybinding {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var foreach = allBindings().foreach,
subscription = foreach.subscribe(function (newValue) {
// do something here (e.g. calculate width/height, use element and its children)
subscription.dispose(); // dispose it if you want this to happen exactly once and never again.
});
}
}
After much 'googling' I have found the solution. The way to achieve this with a foreach is too register a custom event handler:
ko.bindingHandlers.runafter = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor();
setTimeout(function (element, value) {
if (typeof value != 'function' || ko.isObservable(value))
throw 'run must be used with a function';
value(element);
}, 0, element, value);
}
};
This handler will fire whenever an update occurs (or just once if value is not an observable).
More importantly it will fire once the foreach loop has completed!
See here.
You could do something like this :
var viewModel = {
items: ko.observableArray([
{ name: "one" },
{ name: "two" },
{ name: "three" }
]),
myAfterAdd: function(event) {
if (event.originalEvent.srcElement.nodeType === 1) {
alert("myAfterAdd");
}
},
addItem: function() {
this.items.push({ name: 'new' });
}
};
$('#bucket').bind('DOMNodeInserted DOMNodeRemoved', viewModel.myAfterAdd);
ko.applyBindings(viewModel);
Which bucket is the ul element
See fiddle
I hope it helps
I have a select option that gets its initial value from EmailField and its options from allEmailFields:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
When I change the value of the select my model doesn't get updated. Isn't this something two way binding should take care of? Or I need to write handler for the change event?
Module is here:
define('mods/fieldmapping', ["knockout", "libs/knockout.mapping", "datacontext", "mods/campaigner", "text!templates/fieldmapping.html", "text!styles/fieldmapping.css"],
function (ko, mapping, datacontext, campaigner, html, css) {
'use strict';
var
fieldMappingItem = function (data) {
var self = this;
self.CrmField = ko.observable(data.CrmField);
self.EmailField = ko.observable(data.EmailField);
},
dataMappingOptions = {
key: function (data) {
return data.PlatformFieldName;
},
create: function (options) {
return new fieldMappingItem(options.data);
}
},
fieldMappingViewModel = {
contentLoading: ko.observable(false)
},
showFieldMapping = function () {
campaigner.addStylesToHead(css);
campaigner.addModalInnerPanel(html);
},
init = function (connectionId, connectionName) {
fieldMappingViewModel.fieldMappings = mapping.fromJS([]);
fieldMappingViewModel.allEmailFields = mapping.fromJS([]);
fieldMappingViewModel.originatorConnectionName = ko.observable();
fieldMappingViewModel.originatorConnectionName(connectionName);
fieldMappingViewModel.saveFieldMappings = function () {
console.log(ko.toJSON(fieldMappingViewModel.fieldMappings));
amplify.request("updateExistingFieldMappings",
{
cid: connectionId,
RequestEntity: ko.toJSON(fieldMappingViewModel.fieldMappings)
},
function (data) {
console.log(data);
});
};
showFieldMapping();
amplify.request('getExistingFieldMappings', { cid: connectionId }, function (data) {
amplify.request("getCampaignerFields", function (breezerData) {
mapping.fromJS(breezerData.ResponseEntity, fieldMappingViewModel.allEmailFields);
});
mapping.fromJS(data.ResponseEntity, dataMappingOptions, fieldMappingViewModel.fieldMappings);
ko.applyBindings(fieldMappingViewModel, $('#fieldMapping')[0]);
});
};
return {
init: init,
fieldMappingViewModel: fieldMappingViewModel,
html: html,
css : css
}
});
Replace:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
With:
<select data-bind="options: $parent.allEmailFields, value: EmailField"></select>
if you want to create bi-derectinonal dependency, so you should pass into binding observable.
P.S.: http://knockoutjs.com/documentation/observables.html#observables