Knockoutjs Function Expected Exception on updating model - javascript

After much research and trail and error, I haven't come up with a solution yet. Please help! The SearchCustomer method in the code has comments on the scenarios that work and don't work.
Situation
I use knockoutjs with the mapping plugin. I take a view model which contains a Workorder from the server and it contains some properties about it along with a Customer model underneath it and a Contact model underneath Customer.
On the workorder screen the user can search for a customer which pops up a modal search window. They select that customer and the customer's id and customer model comes back to the workorder. I update the workorder's customerID no problem, but when I try to update the customer data (including contact) I get the Function Expected error.
Code
function WorkorderViewModel(data) {
var self = this;
data = data || {};
mapping = {
'Workorder': {
create: function (options) {
return new Workorder(options.data, self);
}
}
}
ko.mapping.fromJS(data, mapping, self);
self.ViewCustomer = function () {
self.Workorder.Customer.View();
}
self.SearchCustomer = function () {
self.Workorder.Customer.Search(function (customerID, customer) {
self.Workorder.CustomerID(customerID); //Works
self.Workorder.Customer(customer) //Function Expected, I feel this should work! Please help!
self.Workorder.Customer = new Customer(customer, self.Workorder); //No Error doesn't update screen
self.Workorder.Customer.Contact.FirstName(customer.Contact.FirstName); //Works, updates screen, but I don't want to do this for every property.
self.Workorder.SaveAll(); //Works, reload page and customer data is correct. Not looking to reload webpage everytime though.
})
}
}
function Workorder(data, parent) {
var self = this;
data = data || {};
mapping = {
'Customer': {
create: function (options) {
return new Customer(options.data, self);
}
}
}
ko.mapping.fromJS(data, mapping, self);
}
function Customer(data, parent) {
var self = this;
data = data || {};
mapping = {
'Contact': {
create: function (options) {
return new Contact(options.data, self);
}
}
}
ko.mapping.fromJS(data, mapping, self);
}
function Contact(data, parent) {
var self = this;
data = data || {};
mapping = {};
ko.mapping.fromJS(data, mapping, self);
self.AddedOn = ko.observable(moment(data.AddedOn).year() == 1 ? '' : moment(data.AddedOn).format('MM/DD/YYYY'));
self.FullName = ko.computed(function () {
var fullName = '';
if (self.FirstName() != null && self.FirstName() != '') {
fullName = self.FirstName();
}
if (self.MiddleName() != null && self.MiddleName() != '') {
fullName += ' ' + self.MiddleName();
}
if (self.LastName() != null && self.LastName() != '') {
fullName += ' ' + self.LastName();
}
return fullName;
})
}
Thanks Everyone!

Since self.Workorder.Customer is originally populated using ko.mapping, when you want to repopulate it, you should use ko.mapping again, like:
ko.mapping.fromJS(customer, self.Workorder.Customer)

Try changing:
self.Workorder.Customer(customer);
to:
self.Workorder.Customer = customer;
My guess is that the Customer property of the Workorder is not an observable.

Related

Javascript & knockoutjs: how to refactor the following code to be able to access the properties outside the function

Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

How to create an autocomplete combobox with KnockoutJS and JQuery UI

I've been trying to create an autocomplete dropdown based on the accepted response in this post but the autocomplete dropdown simply isn't showing up. It could be because the response is 9 years old or perhaps I'm doing something wrong. I have tried all of the suggestions that I've come across. Is there an updated way to create this combobox using jquery version 1.12.3, jquery-ui version 1.12.1, and knockoutjs version 3.4.1?
To me is seems like the bindings aren't really taking place because I could rename the custom binding to "jqAuto1" instead of "jqAuto" and there would be no errors, even though "jqAuto1" isn't defined anywhere. Why isn't that being picked up?
Here is my code. Note that the JS script is in a separate, parent solution from the CSHTML and TS files. The browser still finds and executes the JS script.
CSHTML
<input class="form-control form-control-xs" data-bind="value: companyName, jqAuto: { autoFocus: true }, jqAutoSource: myComp, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'coname', jqAutoSourceValue: 'id'" placeholder="Type Company Name and select from list" />
TS
// For list of Companies
class Comp {
_id: KnockoutObservable<string>;
_coname: KnockoutObservable<string>;
_coid: KnockoutObservable<string>;
constructor(id: string, coname: string, coid: string) {
this._id = ko.observable(id);
this._coname = ko.observable(coname);
this._coid = ko.observable(coid);
}
}
myComp: KnockoutObservableArray<Comp>;
mySelectedGuid: KnockoutObservable<string>;
displayName: KnockoutComputed<string>;
...
this.myComp = ko.observableArray([
new Comp("1", "Company 1", "CO1"),
new Comp("2", "Company 2", "CO2"),
new Comp("3", "Company 3", "CO3"),
new Comp("4", "Company 4", "CO4"),
new Comp("5", "Company 5", "CO5")
]);
this.companyName = ko.validatedObservable<string>("");
this.displayName = ko.computed(function () {
return this.myComp.coname + " [" + this.myComp.coid + "]";
}, this);
this.mySelectedGuid = ko.observable("5");
JS
Pretty much what's in the linked post
(function () {
var global = this || (0, eval)('this'),
document = global['document'],
moduleName = 'knockout-binding-jqauto',
dependencies = ['knockout', 'jquery'];
var moduleDance = function (factory) {
// Module systems magic dance.
if (typeof define === "function" && define["amd"]) {
define(moduleName, dependencies.concat('exports'), factory);
} else {
// using explicit <script> tags with no loader
global.CPU = global.CPU || {};
factory(global.ko, global.Globalize);
}
};
var factory = function (ko, $) {
ko.bindingHandlers.jqauto = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var options = valueAccessor() || {},
allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = allBindings.jqAutoValue,
source = allBindings.jqAutoSource,
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
labelProp = allBindings.jqAutoSourceLabel || valueProp;
//function that is shared by both select and change event handlers
function writeValueToModel(valueToWrite) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(valueToWrite);
} else { //write to non-observable
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite);
}
}
//on a selection write the proper value to the model
options.select = function (event, ui) {
writeValueToModel(ui.item ? ui.item.actualValue : null);
};
//on a change, make sure that it is a valid value or clear out the model value
options.change = function (event, ui) {
var currentValue = $(element).val();
alert(currentValue);
var matchingItem = ko.utils.arrayFirst(unwrap(source), function (item) {
return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
}
//handle the choices being updated in a DO, to decouple value updates from source (options) updates
var mappedSource = ko.dependentObservable(function () {
mapped = ko.utils.arrayMap(unwrap(source), function (item) {
var result = {};
result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString(); //show in pop-up choices
result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString(); //show in input box
result.actualValue = valueProp ? unwrap(item[valueProp]) : item; //store in model
return result;
});
return mapped;
}, null, { disposeWhenNodeIsRemoved: element });
//whenever the items that make up the source are updated, make sure that autocomplete knows it
mappedSource.subscribe(function (newValue) {
$(element).autocomplete("option", "source", newValue);
});
options.source = mappedSource();
//initialize autocomplete
$(element).autocomplete(options);
},
update: function (element, valueAccessor, allBindings, viewModel) {
//update value based on a model change
var allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = unwrap(allBindings.jqAutoValue) || '',
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;
//if we are writing a different property to the input than we are writing to the model, then locate the object
if (valueProp && inputValueProp !== valueProp) {
var source = unwrap(allBindings.jqAutoSource) || [];
var modelValue = ko.utils.arrayFirst(source, function (item) {
return unwrap(item[valueProp]) === modelValue;
}) || {}; //probably don't need the || {}, but just protect against a bad value
}
//update the element with the value that should be shown in the input
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
}
}
};
moduleDance(factory);
})();
I have not fully understood your question. But knockout is not relevant to UIComplete. Please see a simple example using UI complete.
async function autocomplete() {
const sthings= await getSthings(); //gets json array, or ajax call, this is a promise
$("#sthHighlightSearch").autocomplete({
source: sthings
});
//This is an extension method for autocomplete
//Should filter the list with starts with characters written in the autocomplete
$.ui.autocomplete.filter = function (array, term) {
var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(term), "i");
return $.grep(array, function (value) {
return matcher.test(value.label || value.value || value);
});
};
}

My extended observable is getting "deleted" after call to ko.mapping.fromJS

I have an observable extended with the following extender:
ko.extenders.e2mElementName = function (target, options) {
var result = ko.dependentObservable({
read: target,
write: function (newValue) {
var current = target.peek();
if (newValue != current) {
newValue = newValue.replace(/[^0-9A-Za-z_]/g, "").replace(" ", "_");
//check if name already exists
if (!document._editor.elementNames)
document._editor.elementNames = [];
if ($.inArray(newValue, document._editor.elementNames)) {
alert("The name '" + newValue + "' was already assigned to an element on this page");
target.notifySubscribers(current)
} else {
document._editor.elementNames.remove(current);
document._editor.elementNames.push(newValue);
target(newValue);
}
}
}
}).extend({ notify: 'always' });
//result(target());
return result;
};
When i extend the observable at construction everything works fine, in my object construction i have:
this.Name = ko.observable("").extend({ e2mElementName: true });
but after i make a call to mapping.fromJs my observable gets changed to a string ex: "My Name" ...
ko.mapping.fromJS(rawData, mapping, this);
if I remove the extender everything works fine and i have the observable wrapping the string....
what am I doing wrong?
you can add the extender after calling the ko.mapping.fromJS
var applyMapping = function(rawData) {
ko.mapping.fromJS(rawData, {}, this);
this.name = this.name.extend({e2ElementName: true});
}
or you can use the mapping plugin options for this
var mappingOptions = {
// customize the creation of the name property
name: {
create: function(data) {
return ko.observable(data.name).extend( {e2ElementName: true} );
}
}
};
ko.mapping.fromJS(data, mappingOptions, this));

knockout baseEntity property availability is confusing

I am trying to implement a baseEntity class for entities such as user and groups but isFavourite property can not read the correct Id() from persistanceId() when it is in baseEntity. (type and Id() come out as undefined and surprisingly type has the correct value in confirmDelete)
define(["knockout"], function (ko) {
var app = require('durandal/app');
ko.baseEntity = function (data) {
var self = this;
self.Id = ko.observable();
self.confirmDelete = function () {
var result;
app.showMessage(
'Are you sure you want to delete the ' + self.type + ' ' + self.Name() + '?',
'Deleting ' + self.type, ['Yes', 'No']).then(
function (dialogResult) {
dialogResult === "Yes" ? result = true : result = false;
});
return result;
};
self.persistanceId = function () {
return self.type + '-' + self.Id() + "-IsFavourite";
};
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
self.toggleFavourite = function () {
self.isFavourite(!self.isFavourite());
};
}
return {
model: ko.baseEntity
}
});
but if isFavourite instead of being here in baseEntity it is for example part of group then it works fine.
define(["knockout", "models/baseentity"], function (ko, baseEntity) {
var
model = function (data) {
var self = this;
baseEntity.model.call(self);
self.type = 'group';
self.Id(data.Id);
self.Name = ko.observable(data.Name);
self.Description = ko.observable(data.Description);
self.Members = ko.observableArray(data.Members);
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
}
return {
model: model
}
});
Could someone explain to me what is going on here and how can I move my base property back in my baseentity as it is shared across various other things too.
I am no javascript master but i would look to decouple the inheritance of your model hierarchy from knockout - does the model behave as expected if you implement it vanilla?
I don't follow why you would want to modify the knockout object itself? I believe Dave Lowe is correct in suggesting that you do this in JavaScript alone. Properties on your model, to the extent that they affect your view, should be observable, but your model doesn't need to be attached to knockout.
Also, consider spending some time at http://objectplayground.com, which has a great tutorial on learning object oriented JavaScript. Your paradigm should look a little more like this:
function Model(obj) {
this.attribute = obj.attribute;
this.observable = ko.observable(obj.observable);
this.localFunction = function(val) {
if (obj.attr == true) this.observable(val);
};
}
Model.prototype.globalFunction = function(data) {
this.observable(data);
};
Notice, in particular, that if the method depends on the local variable, that is, the argument passed to the constructor, then it needs to be defined in the constructor. Otherwise, you should define methods on the prototype.
This paradigm works excellently with require in durandal since you can do the following:
define(function(require) {
var Model = require('model');
var object = new Model({});
})
OK, apparently the easier way to do this subClass business in ko is using ko itself. Who would have thought :)
So now I have defined my base model to be:
define(["knockout"], function (ko) {
var app = require('durandal/app');
ko.baseEntity = function (type, data) {
var self = this;
self.Id = ko.observable(data.Id);
self.Type = ko.observable(type);
self.Name = ko.observable();
self.persistanceId = ko.computed(function () {
return self.Type() + '-' + self.Id() + "-IsFavourite";
});
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
self.toggleFavourite = function () {
self.isFavourite(!self.isFavourite());
};
self.confirmDelete = function () {
var result;
app.showMessage('Are you sure you want to delete the ' + self.Type() + ' ' + self.Name() + '?', 'Deleting ' + self.Type(), ['Yes', 'No'])
.then(function (dialogResult) {
dialogResult === "Yes" ? result = true : result = false;
});
return result;
};
}
return {
model: ko.baseEntity
}
});
As you can see I have now added the same data parameter that I pass into my concrete implementation. This is possible as later I will use ko.utils.extend to create an instance of this and extend it:
define(["knockout", "models/baseentity", "config"], function (ko, baseEntity, config) {
var
model = function (data) {
var self = this;
ko.utils.extend(self, new baseEntity.model(config.subClassTypes.user, data));
self.Id(data.Id);
self.FirstName = ko.observable(data.FirstName);
self.LastName = ko.observable(data.LastName);
self.JobTitle = ko.observable(data.JobTitle);
self.UserLevel = ko.observable(data.UserLevel);
self.Groups = ko.observableArray(data.Groups);
self.ImageUrl = data.ImageUrl;
self.Name(self.FirstName() + ' ' + self.LastName());
}
return {
model: model
}
});
another subclass example:
define(["knockout", "models/baseentity", "config"], function (ko, baseEntity, config) {
var
model = function (data) {
var self = this;
ko.utils.extend(self, new baseEntity.model(config.subClassTypes.group, data));
self.Id(data.Id);
self.Name(data.Name);
self.Description = ko.observable(data.Description);
self.Members = ko.observableArray(data.Members);
}
return {
model: model
}
});
This way I have managed to get my methods transferred over to the base and it works fine. I hate answering my own questions, so waiting for someone to add some nice valuable answers I can tick.

How do I use a constructor in javascript

Here is my problem.
I am using Backbone js and every collection I have defined requires the same check on save or destroy. Except that the destroy success functions need to be passed an element to remove from the page when the destroy succeeds.
I didn't want to copy and paste the same code into every save or destroy method so I created this:
window.SAVE_TYPE_DESTROY = 'destroy';
window.SaveResponseHandler = function(el,type){
if (!type){
this.success = function() {
this._success();
};
}else if (type == window.SAVE_TYPE_DESTROY){
this.success = function() {
this._success();
$(el).remove();
};
}
};
SaveResponseHandler.prototype._success = function(model, response, options) {
if ((response.success * 1) === 0) {
persistError(model, {
responseText: response.message
}, {});
}
};
SaveResponseHandler.prototype.error = persistError;
var saveResponseHandler = new SaveResponseHandler();
And I use it like this:
destroy: function() {
var el = this.el;
var model = this.model;
this.model.destroy(new SaveResponseHandler(el,'destroy'));
},
change: function() {
this.model.set({
job_category_name: $($(this.el).find('input')[0]).val()
});
var viewView = this.viewView;
this.model.save(null, saveResponseHandler);
}
The problem is when success is called I get the following error:
Uncaught TypeError: Object [object Window] has no method '_success'
Any help will be much appreciated. I'm also open to any suggestions on better ways to handle this.
this inside of SaveResponseHandler.success isn't SaveResponseHandler, it's window.
window.SaveResponseHandler = function(el, type) {
var self = this;
if (!type) {
this.success = function() {
self._success();
};
} else if (type == window.SAVE_TYPE_DESTROY) {
this.success = function() {
self._success();
$(el).remove();
};
}
};
http://jsfiddle.net/ethagnawl/VmM5z/

Categories