Knockoutjs select value change doesn't update observable - javascript

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

Related

Calling a private/nested javascript function from an outer scope

I have my javascript code like this . Inside that I have an init() function and in that function I have an options JSON object and in that object I have a function defined as objectselected(). How I call that function in a button click event
I have tried like this WorkFlow.init().options.Objectselected() but it is not working,
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
},
}
How to call that function.
One way around is you can return options and than call it.
init: function () {
var options = {
...your code..}
return options;
},
and call it than
var options = WorkFlow.init();
options.Objectselected();
As it stands, you have no access to options because it's a local variable - that is, local to its scope.
To access its contents, you'll need to return it from init().
Think about it:
WorkFlow.init()
Currently this returns undefined, because your init() returns nothing. You're trying to chain like in jQuery, but that relies on the API always returning the instance. Your path finds a dead-end at init().
To fix this, have init() return options - or at least the part of it you want to access from outside - an "export".
So (basic example)
init: function() {
var options {
my_func: function() { }, //<-- we want outside access to this
private: 'blah' //<-- this can stay private - leave it out of the export
}
//return an export, exposing only what we need to
return {
my_func: options.my_func
}
}
You need to return options as it is inside init function's scope
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
return options;
},
}
And call it as WorkFlow.init().objectSelected();
Building on Patrick's comment, you'd need to return options from the init function:
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
...
options.initialize();
return options;
},
}

iCheck Plug in & Knockout Js

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>

KnockoutJS - ViewModel (Grandparent - Parent - Child) Accessing a Parent Function within Child element

I have a Grandparent, Parent, Child ViewModel relationship setup in knockout and knockout mapping, CustomerViewModel, WorkOrderViewModel, and RepairViewModel.
For each level I flag if the record has been Modified. Then I have a save button that saves the entire Model. The function that Saves the Model is within the Grandparent ViewModel (CustomerViewModel)
Example of a Child level element
<input class="form-control input-sm text-right" name="RepairCost" id="RepairCost" data-bind="value: RepairCost, event: {change: flagRepairAsEdited}" />
Is there a way within the flagRepairAsEdited function I can call the SAVE function within the parent/grandparent?
Thanks so much!
Here is the JS code I'm using (simplified):
var ObjectState = {
Unchanged: 0,
Added: 1,
Modified: 2,
Deleted: 3
};
var workOrderMapping = {
'WorkOrders': {
key: function (workOrders) {
return ko.utils.unwrapObservable(workOrders.WorkOrderId);
},
create: function (options) {
return new WorkOrderViewModel(options.data);
}
},
'Repairs': {
key: function (repairs) {
return ko.utils.unwrapObservable(repairs.RepairId);
},
create: function (options) {
return new RepairViewModel(options.data);
}
}
};
RepairViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
self.flagRepairAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
//WOULD LOVE TO CALL SAVE FUNCTION HERE
return true;
}
;
}
WorkOrderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
self.flagWorkOrderAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
//WOULD LOVE TO CALL SAVE FUNCTION HERE
return true;
}
;
}
CustomerViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, workOrderMapping, self);
self.save = function () {
$.ajax({
url: "/Customers/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
success: function (data) {
ko.mapping.fromJS(data.customerViewModel, workOrderMapping, self);
if (data.newLocation != null)
window.location = data.newLocation;
},
});
},
self.flagCustomerAsEdited = function () {
if (self.ObjectState() != ObjectState.Added) {
self.ObjectState(ObjectState.Modified);
}
return true;
}
;
}
There are 2 ways to do this
a) Pass viewModels as parameters of the child flagRepairAsEdited function:
data-bind="value: RepairCost, event: {change: flagRepairAsEdited.bind($data, $parent, $root)}"
b) Save the link of the parent viewModel inside child viewModel
WorkOrderViewModel = function (data, parent) {
this.parent = parent;
...
}
And use parent.flagWorkOrderAsEdited and parent.parent.flagWorkOrderAsEdited to save parent and grandparent viewmodels

Animating knockout template views

Is there a way to have transitional animations between "page" changes while using knockout for changing templates? I'm looking for something similar to Knockback-Navigators. I cant figure out a way to do this? Is there a package I can use to make this easier? Here is a JSFiddle with the same type of binding my project uses. And a sample of my javascript here:
var View = function (title, templateName, data) {
var self = this;
this.title = title;
this.templateName = templateName;
this.data = data;
this.url = ko.observable('#' + templateName);
};
var test1View = {
test: ko.observable("TEST1")
};
var test2View = {
test: ko.observable("TEST2")
};
var viewModel = {
views: ko.observableArray([
new View("Test 1", "test1", test1View),
new View("Test 2", "test2", test2View)]),
selectedView: ko.observable(),
}
//Apply knockout bindings
ko.applyBindings(viewModel);
//Set up sammy url routes
Sammy(function () {
//Handles only groups basically
this.get('#:view', function () {
var viewName = this.params.view;
var tempViewObj = ko.utils.arrayFirst(viewModel.views(), function (item) {
return item.templateName === viewName;
});
//set selectedView
viewModel.selectedView(tempViewObj);
});
}).run('#test1');
There are plenty of ways of doing this, here is one
ko.bindingHandlers.withFade = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var observable = valueAccessor();
var wrapper = ko.observable(observable());
observable.subscribe(function(value) {
var current = wrapper();
fadeIn = function() {
wrapper(value);
$element.fadeIn();
};
if(current) {
$element.fadeOut(fadeIn);
} else {
$element.hide();
fadeIn();
}
});
ko.applyBindingsToNode(element, { with: wrapper }, bindingContext);
return { controlsDescendantBindings: true };
}
};
http://jsfiddle.net/7E84t/19/
You can abstract the effect like this
ko.transitions = {
fade: {
out: function(element, callback) {
element.fadeOut(callback);
},
in: function(element) {
element.fadeIn();
}
},
slide: {
out: function(element, callback) {
element.slideUp(callback);
},
in: function(element) {
element.slideDown();
}
}
};
html
<div data-bind="withFade: { data: selectedView, transition: ko.transitions.slide }">
http://jsfiddle.net/7E84t/23/

knockout and Select2 get selected object

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')
})

Categories