I'm trying to achieve a degree of inheritance in JavaScript and here is what I have so far:
function Address() {
this.address1 = ko.observable();
this.address2 = ko.observable();
this.country = ko.observableSafe(null, new Country(-1, '', false));
this.city = ko.observable('');
this.state = ko.observable();
this.province = ko.observable('');
this.zipCode = ko.observable();
this.countryLookupID = '';
this.stateLookupID = '';
ko.computed(function () {
var newCountry = this.country();
if (newCountry) {
this.countryLookupID = newCountry.id.toString();
if (newCountry.international) {
this.clearDomestic();
}
else {
this.clearInternational();
}
}
else {
this.countryLookupID = "";
}, this);
ko.computed(function () {
var newState = this.state();
if (newState) {
this.stateLookupID = newState.id.toString();
}
else {
this.stateLookupID = "";
}
}, this);
}
Address.prototype.clearDomestic = function () { return true; };
Address.prototype.clearInternational = function () { return true; };
function Company() {
this.base = Address;
this.base(this);
this.legalEntityID = ko.observable(0);
this.legalEntityName = ko.observable('');
this.isFemaleOwned = ko.observable(false);
this.isMinorityOwned = ko.observable(false);
this.webAddress = ko.observable();
this.businessPhone = ko.observable();
this.faxNumber = ko.observable();
this.locked = ko.observable(false);
}
Company.prototype.constructor = Address;
Company.prototype.clearDomestic = function () {
this.businessPhone('');
this.state(null);
this.zipCode('');
};
Company.prototype.clearInternational = function () {
this.province('');
};
If you are unfamiliar with the Knockout framework, that is OK, as it's probably not pertinent to this discussion. I haven't seen inheritance done exactly like this anywhere that I've looked. As it currently stands, this works exactly how you think it should. When clearDomestic() is called, the correct version of the function is called in the inherited class and this points to a Company object. If I take out the base and call to base(this), it breaks.
Can anyone explain why this is working? If this is a bad practice, can someone tell me how to rewrite it so it functions the same? I don't really want to include another library to achieve this.
UPDATE
If inside Address you invoke this.clearDomestic() outside of the ko.computed, it tries to call the clearDomestic() attached to Company but then this points to an Address object and so businessPhone is no longer defined.
UPDATE 2
I've moved things around again, and I've settled on this method. It's not ideal, but it's the only way that consistently works.
function Address() {
this.address1 = ko.observable();
this.address2 = ko.observable();
this.country = ko.observableSafe(null, new Country(-1, '', false));
this.city = ko.observable('');
this.state = ko.observable();
this.province = ko.observable('');
this.zipCode = ko.observable();
this.countryLookupID = '';
this.stateLookupID = '';
}
Address.prototype.clearDomestic = function () { return true; };
Address.prototype.clearInternational = function () { };
function Company() {
this.legalEntityID = ko.observable(0);
this.legalEntityName = ko.observable('');
this.isFemaleOwned = ko.observable(false);
this.isMinorityOwned = ko.observable(false);
this.webAddress = ko.observable();
this.businessPhone = ko.observable();
this.faxNumber = ko.observable();
this.locked = ko.observable(false);
ko.computed(function () {
var newCountry = this.country();
if (newCountry) {
this.countryLookupID = newCountry.id.toString();
if (newCountry.international) {
this.clearDomestic();
}
else {
this.clearInternational();
}
}
else {
this.countryLookupID = "";
}
}, this);
ko.computed(function () {
var newState = this.state();
if (newState) {
this.stateLookupID = newState.id.toString();
}
else {
this.stateLookupID = "";
}
}, this);
}
Company.prototype = new Address;
Company.prototype.clearDomestic = function () {
// Since we are entering this method via Address, we need a reference back to a real company object with self.
this.businessPhone('');
this.state(null);
this.zipCode('');
};
Company.prototype.clearInternational = function () {
this.province('');
};
I'm going to have to do the logic in the newstate and newcountry in every object that inherits from Address which makes this far from ideal, but until I find a better suggestion, I'm stuck with this.
I'm not sure I understand the problem exactly, but instead of this.base(this);, try calling this.base.call(this);? (in the first version of your code).
Related
I've been working on this code for an hour and a half and I have still been having issues. This is my first day with JavaScript objects and I'm unsure if any of my code is best practice.
function Car(carModel, carManufacturer, carHorsepower, carOwner) {
this.model = carModel;
this.manufacturer = carManufacturer;
this.horsepower = carHorsepower;
this.owner = carOwner;
}
function changeCarModel(car,newModel) {
this.model = newModel;
}
function changeCarManufacturer(car,newmanufacturer) {
this.manufacturer = newmanufacturer;
}
function changeCarHorsepower(car,newHorsepower) {
this.horsepower = newHorsepower;
}
function changeCarOwner(car, newOwner) {
this.owner = newOwner;
}
function logCarInfo(car) {
console.log(this.model);
console.log(this.manufacturer);
console.log(this.horsepower);
console.log(this.owner);
}
//TODO Add an owner object!
function Owner(newName, newAddress) {
name = newName;
address = newAddress;
}
function changeOwnerName(owner, newName) {
owner.name = newName;
}
function changeOwnerAddress(owner, newAddress) {
owner.address = newAddress;
}
function main() {
var toyota = Car('Supra', 'Toyota', '800');
logCarInfo(toyota);
changeCarHorsepower(toyota, '10000');
console.log('-------');
logCarInfo(toyota);
changeCarHorsepower(toyota, '1000');
console.log('-------');
logCarInfo(toyota);
changeCarManufacturer(toyota, 'Ford');
console.log('-------');
logCarInfo(toyota);
var cooperCodes = Owner('Cooper', '1919 12th Street');
console.log('New Owner Made');
changeCarOwner(toyota, cooperCodes);
logCarInfo(toyota);
}
main();
So I need help with this, when I attempted to add this.owner = carOwner and then add a new owner to the car object, the console.log prints undefined. I am unsure why this is happening and if you anyone knows this issue it would mean so much to me.
You are missing this in your Owner function
function Owner(newName, newAddress) {
this.name = newName;
this.address = newAddress;
}
Also, if you don't want to show undefined, check if the value is present before assigning
function Car(carModel, carManufacturer, carHorsepower, carOwner) {
this.model = carModel ? carModel : null;
this.manufacturer = carManufacturer ? carManufacturer : null;
this.horsepower = carHorsepower ? carHorsepower : null;
this.owner = carOwner ? carOwner : null;
}
How can I programmatically assign knockout js attributes to a view model?
I need some kind of factory structure, but I'm not sure about the best way to go about it...
I'm trying to assign attributes that don't exist yet to a model..
I want to reduce this:
function aViewModel() {
var self = this;
self.attr1Text = ko.observable("");
self.attr1Text.focused = ko.observable(false);
self.attr2Text = ko.observable("");
self.attr2Text.focused = ko.observable(false);
self.attr3Text = ko.observable("");
self.attr3Text.focused = ko.observable(false);
self.attr4Text = ko.observable("");
self.attr4Text.focused = ko.observable(false);
self.attr5Text = ko.observable("");
self.attr5Text.focused = ko.observable(false);
}
vm = new aViewModel();
ko.applyBindings(vm);
to something like this:
function aViewModel(attrs) {
var self = this;
for (var attr of attrs){
self[attr+"Text"] = ko.observable("");
self[attr+"Text"]["focused"] = ko.observable(false);
}
}
attr_list = ["attr1", "attr2", ..., "attrN"]
vm = new aViewModel(attr_list);
ko.applyBindings(vm);
And have it actually work.
This works:
function aViewModel() {
var self = this;
}
vm = new aViewModel();
attr_list = ["attr1", "attr2", ..., "attrN"]
for (var attr of attr_list){
self[attr+"Text"] = ko.observable("");
self[attr+"Text"]["focused"] = ko.observable(false);
}
ko.applyBindings(vm);
But more encapsulation would be nice so I just created a wrapper function,
function createGrid(things, data){
vm = new gridViewModel(things);
for (var thing of things){
vm[thing+"Text"] = ko.observable("");
vm[thing+"Text"]["focused"] = ko.observable(false);
}
\\ other stuff with data
return vm;
}
then used
vm = createGrid(things, data)
ko.applyBindings(vm);
I feel like I'm answering my own question. But is there a better way to do this?
use the knockout mapping plugin
var modelClass = function(data){
ko.mapping.fromJS(data,this);
//additional values or computed
this.full_name = ko.computed(function(){
return ko.unwrap(this.first_name) + ' ' + ko.unwrap(this.last_name);
}
}
var mapping = {
create : function(options){
return new modelClass(options.data);
}
}
var viewModel = function(){
this.myModel = ko.mapping.fromJS(data,mapping);
}
I have a view model called ProductsViewModel
This contains an observableArray of ProductViewModel
A ProductViewModel also contains an observableArray - of ProductPriceViewModel
One feature I have is that I can duplicate a ProductViewModel and insert it into the ProductsViewModel array.
When I clone using:
ko.mapping.fromJS(ko.toJS(itemToCopy));
It doesn't appear to copy correctly - the prices observable array, isn't populated with ProductPriceViewModels - just Object
Here's the view models
var ProductsViewModel = function() {
var self = this;
self.products = ko.observableArray([new ProductViewModel()]);
self.addNewProduct = function() {
self.products.push(new ProductViewModel());
};
self.duplicateProduct = function() {
var itemToCopy = ko.utils.arrayFirst(self.products(), function(item) {
return item.visible();
});
//if i look at itemToCopy.prices() it is an array of ProductViewModel
var newItem = ko.mapping.fromJS(ko.toJS(itemToCopy));
//if i look at newItem.prices() it is an array of Object
self.products.push(newItem);
};
};
var ProductViewModel = function() {
var self = this;
self.name = ko.observable();
self.visible = ko.observable(true);
self.prices = ko.observableArray([new ProductPriceViewModel()]);
self.addPrice = function() {
self.prices.push(new ProductPriceViewModel());
};
};
var ProductPriceViewModel = function() {
var self = this;
self.name = ko.observable();
self.price = ko.observable();
};
I solved this by passing in a mapping configuration like this:
var mapping = {
'prices': {
create: function (options) {
return new ServicePriceViewModel(options.data);
}
}
};
on
var newItem = ko.mapping.fromJS(ko.toJS(productToCopy), mapping);
and changing my ProductPriceViewModel to accept data as a parameter:
var ProductPriceViewModel = function (data) {
var self = this;
self.name = ko.observable();
self.description = ko.observable();
self.price = ko.observable();
self.priceIsFrom = ko.observable();
if (data)
ko.mapping.fromJS(data, {}, this);
};
I'm doing some Node.js and I want to use the closure representation to create my objects. I think I'm missing something, because something simple like this isn't working:
var Room = function(foo) {
this.name = foo;
this.users= [];
return {
getName : function() {
return this.name;
}
}
}
var room = new Room("foo");
console.log(room.getName());
I also have tried without the parameter.. and still not working.
var Room = function() {
this.name = "foo";
this.users= [];
return {
getName : function() {
return this.name;
}
}
}
var room = new Room();
console.log(room.getName());
However, something like this works:
var Room = function(foo) {
this.name = foo;
this.users= [];
}
var room = new Room("foo");
console.log(room.name);
I can't understand why this isn't working.
--Edited
Thanks to Amadan I have found the right way to do it:
var Room = function() {
var name = "foo";
var users= [];
return {
getName : function() {
return name;
}
}
}
var room = new Room();
console.log(room.getName());
This way "name" and "users" are encapsulated.
return in a constructor will overwrite this. So the right way to do this is:
var Room = function(foo) {
this.name = foo;
this.users= [];
this.getName = function() {
return this.name;
}
}
or
var Room = function(foo) {
return {
name: "foo",
users: [],
getName : function() {
return this.name;
}
}
}
The first one does everything on the original this; the second one replaces this with everything you need.
Can't map json to observable array. I use code from tutorial (http://learn.knockoutjs.com/).
function Movie(data) {
this.name = ko.observable(data.name);
this.description = ko.observable(data.description);
this.duration = ko.observable(data.duration);
this.id = ko.observable(data.id);
this.imdb_id = ko.observable(data.imdb_id);
this.original_name = ko.observable(data.original_name);
this.poster = ko.observable(data.poster);
this.type = ko.observable(data.type);
this.year = ko.observable(data.year);
}
function MovieListViewModel() {
// Data
var self = this;
self.moviesArray = ko.observableArray([]);
self.searchQuery = ko.observable();
self.searchMovies = function () {
$.getJSON("/api/v1/movies/search/", {"query": self.searchQuery }, function(allData) {
var mappedMovies = $.map(allData.movies, function(item) { return new Movie(item) });
console.log(mappedMovies); // in this line output: [Movie, Movie, Movie, Movie, Movie, Movie]
self.moviesArray(mappedMovies);
console.log(self.moviesArray); // in this line output: []
});
};
}
ko.applyBindings(new MovieListViewModel());
I do not understand what is wrong.
P.S. Sorry for my english
This
{"query": self.searchQuery }
Should be
{"query": self.searchQuery() }
This
console.log(self.moviesArray)
Should be
console.log(self.moviesArray())