Let's say I have this object:
var order = {
customer: 'Joe',
items: [
{
id: 123,
title: 'VCR',
}
]
};
I want to create logic for both the order and the items object. So I need to write a class that instantiate another class.
The Order class:
function Order(customer, items){
this.customer = customer;
this.items = items;
}
Order.prototype = {
getCustomer: function(){
console.log(this.customer);
}
};
The Item class:
function Item(id, title){
this.id = id;
this.title = title;
}
Item.prototype = {
getItemId: function(){
console.log(this.id);
}
};
The instance:
var myOrder = new Order(order.customer, order.items);
Now this is what I want to do:
myOrder.items[0].getItemId() //123
myOrder.items[0].getCustomer() //Joe
How do I make this work? I need to connect the classes somehow and add a prototype to the item objects.
Jsbin: https://jsbin.com/nofofifuka/edit?html,js,console
You can use Array.prototype.map to call the Item constructor on the elements in items.
function Order(order){
this.customer = customer;
this.items = order.items.map(function (item) {
return new Item(item);
});
}
Array.prototype.map calls the given function once for each element in the source array, and returns a new array, of the same length, containing the results of the calling the function.
Alternatively you could make your Item constructor have a few extra smarts so it can instantiate itself if called without new:
function Item(item){
if (! (this instanceof Item)) { return new Item(item); }
this.id = id;
this.title = title;
}
Then you can use it directly in the map:
function Order(order){
this.order = order;
order.items = order.items.map(Item);
}
What you want to achieve has nothing to do with inheritance, I think. Can you check if code below satisfies what you want to achieve?
function Order(id){
this.id = id;
this.items = [];
}
Order.prototype.addItem = function(item) {
this.items.push(item);
};
Order.prototype.getItems = function() {
return this.items;
};
Order.prototype.getItem = function(id) {
for (var i = 0, l = this.items.length; i<l; i++) {
if (this.items[i].id === id) return this.items[i];
}
return;
};
function Item(id, customer) {
this.id = id;
this.customer = customer;
}
var order1 = new Order(1);
order1.addItem(new Item(123, 'customer1'));
order1.addItem(new Item(345, 'customer2'));
order1.addItem(new Item(456, 'customer3'));
console.log(order1.getItems());
console.log(order1.getItem(345));
On my glance, such case should reflect the following situation in real life:
Certain buyer(customer) makes an order which may contains many product positions (items). That items have their unique characteristics and should added separately into the order.
The Order class:
function Order(customer){
this.customer = customer;
this.items = [];
}
Order.prototype = {
constructor: Order,
addItem: function(id, title){
this.items.push(new Item(id, title));
},
getCustomer: function(){
console.log(this.customer);
}
};
The Item class:
function Item(id, title){
this.id = id;
this.title = title;
}
Item.prototype = {
constructor: Item,
getItemId: function(){
console.log(this.id);
}
};
The instance:
var myOrder = new Order(order.customer);
order.items.map(function (item) {
myOrder.addItem(item.id, ite.title);
});
myOrder.items[0].getItemId() //123
Array.prototype.map is the right tool, but in order to deliver the logic you ask for, I think it should be used more like this:
function Order(o){
this.items = o.items.map(function(obj){
obj.customer = o.customer;
var r = obj;
r.getItemId = function(){ console.log(obj.id); };
r.getItemTitle = function(){ console.log(obj.title); };
r.getCustomer = function(){ console.log(obj.customer); };
return r;
});
}
var myOrder = new Order(order);
myOrder.items[0].getCustomer();
myOrder.items[0].getItemId();
myOrder.items[0].getItemTitle();
Considering it is obvious from the code, I didn't add above, but thanks to line 4 (var r = obj;) you can use customer, id, and title properties of myOrder.items[n] as well as all getters.
Related
I'm testing the observable pattern in javascript. My callbacks in the array never seem to execute. What is wrong with my syntax?
<script type="text/javascript">
var Book = function (value) {
var onChanging = [];
this.name = function () {
for (var i = 0; i < onChanging.length; i++) {
onChanging[i]();
}
return value;
}
this.addTest = function (fn) {
onChanging.push(fn);
}
}
var b = new Book(13);
b.addTest(function () { console.log("executing"); return true; });
b.name = 15;
</script>
From your code above it looks like you need to call your function name instead of assigning a value something like:
var b = new Book(13);
b.addTest(function () { console.log("executing"); return true; });
b.name(); //<-- Before b.name = 15
Setting b.name = 15 doesn't execute the function, it just overwrites the value of b.name.
You could use getters and setters to react to a changing value. See John Resig's blog post or the MDN reference
I edited your code to use them:
var Book = function (value) {
this.onChanging = [];
this._name = "";
}
Book.prototype = {
addTest: function (fn) {
this.onChanging.push(fn);
},
get name() {
return this._name;
},
set name(val) {
for (var i = 0; i < this.onChanging.length; i++) {
this.onChanging[i](val);
}
this._name = val;
}
};
var b = new Book(13);
b.addTest(function (val) {
console.log("executing", val);
return true;
});
b.name = 15;
b.name = 17;
working demo.
You can also make a more generic solution that can work for all your properties without having to define the getters and setters, a lot of frameworks use this approach.
Book = function () {
this._events = [];
this._rawdata = {};
}
Book.prototype = {
bind: function (fn) {
this._events.push(fn);
},
// pass the property, and it returns its value, pass the value and it sets it!
attr: function (property, val) {
if (typeof val === "undefined") return this._rawdata[property];
this._rawdata[property] = val;
for (var i = 0; i < this._events.length; i++)
// we pass out the val and the property
this._events[i](val, property);
}
};
b = new Book();
b.bind(function (val) {
console.log("executing", val);
return true;
});
b.attr("name","The Hobbit");
b.attr("SKU" ,1700109393901);
console.log(b.attr("name")); // --> The Hobbit
http://jsfiddle.net/wv4ch6as/
Of course you would want to change the binder so that you can bind onto properties not one bind for all properties, but I think this gets the idea.
I have a javascript compositer pattern which i implemented (see code below).
In my main class i instantiate either the MenuItem or the Menu. I have to call the method update() on the component and they should return the corresponding code.
However it doesnt return the correct amount of totalitems. it alwasy returns the default value 0 which is defined in MenuComponent.
I think it has something to do with the this keyword but i can not find the exact solution.
MenuItem:
//MENU ITEM
//----------
var MenuItem = function(id) {
MenuComponent.apply(this, [id, name]);
};
MenuItem.prototype = Object.create(MenuComponent.prototype);
MenuItem.prototype.constructor = MenuItem;
MenuItem.prototype.update = function() {
//works
console.log(this.ingredients)
//Doesnt work, this should display same as this.ingredients
console.log(this.calculateIngredients())
console.log("--------------")
};
Menu:
//MENU
//--------
var Menu = function(id, name) {
MenuComponent.apply(this, [id, name]);
this.menuitems = [];
};
Menu.prototype = Object.create(MenuComponent.prototype);
Menu.prototype.constructor = Menu;
Menu.prototype.add = function(menuitem) {
this.menuitems.push(menuitem);
};
Menu.prototype.remove = function(menuitem) {
for(var s, i = 0; s = this.getMenuItem(i); i++) {
if(s == menuitem) {
this.menuitems.splice(i, 1);
return true;
}
if(s.remove(menuitem)) {
return true;
}
}
return false;
};
Menu.prototype.getMenuItem = function(i) {
return this.menuitems[i];
};
Menu.prototype.calculateIngredients = function() {
this.ingredients = 0;
for(var key in this.menuitems) {
this.ingredients += this.menuitems[key].calculateIngredients();
}
return this.ingredients;
};
MenuComponent
//MenuComponent
//-------------
var MenuComponent = function(id, name) {
if(this.constructor === MenuComponent) {
throw new Error("Can't instantiate abstract class");
}
this.id = id;
this.name = name;
this.ingredients = 0;
};
MenuComponent.prototype.calculateIngredients = function() {
return this.ingredients;
};
MenuComponent.prototype.update = function() {
console.log(this.ingredients)
console.log("-----------------")
};
example
// HANDLER
var menuitem1 = new MenuItem(1)
, menuitem2 = new MenuItem(2)
, menuitem3 = new MenuItem(3)
, menuitem4 = new MenuItem(4)
, menuitem5 = new MenuItem(5)
, menuitem6 = new MenuItem(6)
, menuitem7 = new MenuItem(7)
, menu = new Menu(1);
menu.add(menuitem1);
menu.add(menuitem2);
menu.add(menuitem3);
menu.add(menuitem4);
menuitem1.ingredients = 1
menuitem2.ingredients = 5
menuitem3.ingredients = 7;
menuitem4.ingredients = 2
// lets say i want to update the ingredient count of the following
menuitem1.update();
menuitem2.update();
menu.update();
//the update goes wrong, it doesnt display the correct amount, it alwasy displays 0 on the amounts where i commented
JSFiddle
Instead of
MenuComponent.prototype.update = function() {
console.log(this.ingredients) // 0
};
You want to call
MenuComponent.prototype.update = function() {
console.log(this.calculateIngredients()) // 15
};
whole code on jsfiddle: http://jsfiddle.net/krzysztof_safjanowski/gjTb4/
I am building a Knockout viewmodel. The model has some fields like dateFrom, DateTo, Status and so forth. In addition, there is a list of invoices.
The invoices have some pricing information, which is a price object. My main object also have a price object, which should iterate all the invoice objects and find the total price.
My problem is the following:
The code runs smooth, until I add the following in my view:
<label data-bind="text:totalPrice().price().priceExVat"></label>
Here I get an:
TypeError: $(...).price is not a function
Which refers to my:
exVat += $(ele).price().priceExVat;
I don't understand it, because in my each function, I should have the element. The element have a price() function, so why would it not work? Is it some scope issue?
My viewmodel:
function invoice(invoiceDate, customerName, pdfLink, status) {
var self = this;
self.pdfLink = pdfLink;
self.print = ko.observable(0);
self.customerName = customerName;
self.status = status;
self.pdfPagesCount = function () {
return 1;
};
self.invoiceDate = invoiceDate;
self.price = function () {
return new price(1.8, 2.1);
};
}
function price(exVat, total) {
var self = this;
self.currency = '€';
self.total = total;
self.priceExVat = exVat;
self.vatPercentage = 0.25;
self.vatAmount = self.exVat - self.total;
self.priceExVatText = function() {
return self.priceExVat + ' ' + self.currency;
};
}
var EconomicsViewModel = function (formSelector, data) {
var self = this;
self.dateFrom = data.dateFrom;
self.dateTo = data.dateTo;
self.invoices = ko.observableArray([
new invoice('05-05-2014', 'LetterAmazer IvS', "http://www.google.com","not printed"),
new invoice('05-05-2014', 'LetterAmazer IvS', "http://www.google.com", "not printed")
]);
self.totalPrice = function () {
var exVat = 0.0;
$(self.invoices).each(function (index, ele) {
console.log(ele);
exVat += $(ele).price().priceExVat;
});
return price(exVat, 0);
};
};
From what I read, totalPrice is actually a price object, you don't need to put a .price():
<label data-bind="text:totalPrice().priceExVat"></label>
EDIT:
Sorry, there were also problems on your javascript:
self.totalPrice = function () {
var exVat = 0.0;
$(self.invoices()).each(function (index, ele) { //<-- add () to self.invoices to get the array
console.log(ele);
exVat += ele.price().priceExVat; //<-- remove useless jQuery
});
return new price(exVat, 0); //<-- add 'new'
};
Check this fiddle
EDIT2:
To answer robert.westerlund's comment, you could remove $().each and replace with ko.utils.arrayForEach or even simpler use a for loop:
var arr = self.invoices();
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
exVat += arr[i].price().priceExVat;
}
Updated fiddle
By looking through the code of BackboneJS, i am interested about extend the implemented. by when i try to make it myself i am stuck. my code is the following.
var extend = function(child) {
var base = this;
if(child) {
for(var prop in child) {
base[prop] = child[prop];
}
}
return base;
};
var Test = Mod.Test = function() {
this.data = {};
}
Test.prototype.set = function(key, value) {
this.data[key] = value;
}
Test.prototype.get = function(key) {
return this.data[key];
}
Test.extend = extend;
when i try like this i am not able to attach hello method to Mod.Test
var testObj = new Mod.Test.extend({
hello : function() {
console.log('hello');
}
});
How is it possible. how its implemented in backbonejs.
Backbone's extend method accepts two parameters - instance properties and static properties. The first ones are copied to the instance being created, the second are assigned to the instance's prototype. Usually you should invoke the extend method without the new operator but in this case here is a working version of your code:
var extend = function(child) {
var base = this;
if(child) {
for(var prop in child) {
base[prop] = child[prop];
}
for(var prop in child) {
base.prototype[prop] = child[prop];
}
}
return base;
};
var Test = Backbone.Model.Test = function() {
this.data = {};
}
Test.prototype.set = function(key, value) {
this.data[key] = value;
}
Test.prototype.get = function(key) {
return this.data[key];
}
Test.extend = extend;
and then:
Test = Backbone.Model.Test.extend({
hello : function() {
console.log('hello');
}
});
var testObj = new Test;
I was reading this link http://addyosmani.com/largescalejavascript/#modpattern
And saw the following example.
var basketModule = (function() {
var basket = []; //private
return { //exposed to public
addItem: function(values) {
basket.push(values);
},
getItemCount: function() {
return basket.length;
},
getTotal: function(){
var q = this.getItemCount(),p=0;
while(q--){
p+= basket[q].price;
}
return p;
}
}
}());
basketModule.addItem({item:'bread',price:0.5});
basketModule.addItem({item:'butter',price:0.3});
console.log(basketModule.getItemCount());
console.log(basketModule.getTotal());
It stats that "The module pattern is a popular design that pattern that encapsulates 'privacy', state and organization using closures" How is this different from writing it like the below? Can't privacy be simply enforced with function scope?
var basketModule = function() {
var basket = []; //private
this.addItem = function(values) {
basket.push(values);
}
this.getItemCount = function() {
return basket.length;
}
this.getTotal = function(){
var q = this.getItemCount(),p=0;
while(q--){
p+= basket[q].price;
}
return p;
}
}
var basket = new basketModule();
basket.addItem({item:'bread',price:0.5});
basket.addItem({item:'butter',price:0.3});
In the first variant you create an object without the possibility to create new instances of it (it is an immediately instantiated function). The second example is a full contructor function, allowing for several instances. The encapsulation is the same in both examples, the basket Array is 'private' in both.
Just for fun: best of both worlds could be:
var basketModule = (function() {
function Basket(){
var basket = []; //private
this.addItem = function(values) {
basket.push(values);
}
this.getItemCount = function() {
return basket.length;
}
this.getTotal = function(){
var q = this.getItemCount(),p=0;
while(q--){
p+= basket[q].price;
}
return p;
}
}
return {
basket: function(){return new Basket;}
}
}());
//usage
var basket1 = basketModule.basket(),
basket2 = basketModule.basket(),