While experimenting with nodejs I encountered a problem of enabling isntances creation via Constructors. I create simple cart basket functionality.
I got file cart.js
var items = [];
function addItem (name, price) {
items.push({
name: name,
price: price
});
}
exports.total = function () {
return items.reduce(function (a,b) {
return a + b.price;
}, 0);
};
exports.addItem = addItem;
I run it with node
var cart = require('./cart')
But what if I need to create multiple instances of a Cart?
I tried to refactor my code, creating a Constructor, that holds items[] addItem() and total() functions, like this:
exports.Cart = function () {
var items = [];
function addItem (name, price) {
items.push({
name: name,
price: price
});
}
function total () {
return items.reduce(function (a,b) {
return a + b.price;
}, 0);
}
};
I run it like this:
var cart = require('./cart');
cart.addItem('Pepsi',199); // no problem with this
cart2 = new cart.Cart(); // it gives me undefined can't be a function
I understand, that I can use PROTOTYPE property to add functions and props to my Cart
So I create a second file cart2.js and place something like:
function Cart () {
this.items = [];
}
Cart.prototype.addItem = function (name, price) {
this.items.push({
name: name,
price: price
});
};
Cart.prototype.total = function () {
return this.items.reduce(function (a,b) {
return a + b.price;
}, 0);
};
module.exports = Cart;
And now it works.
But in order to explore all possiblities, I want to know how I can solve it the first way I tried. When I can use it as "instanceble" Class thing and as singleton thing, with only one instance, at the same time.
Can you please advice me how to solve it the way I wanted in the first place?
I'll appreciate if you provide some other ways to solve this task of creating instanceable Classes.
The first option might look like this:
exports.Cart = function () {
var items = [];
// ...other private stuff...
return {
addItem: function (name, price) {
items.push({
name: name,
price: price
});
},
total: function() {
return items.reduce(function (a,b) {
return a + b.price;
}, 0);
}
// ...other public stuff...
}
};
Usage:
var carts = require('carts');
firstCart = carts.Cart();
second = carts.Cart();
Related
How to ensure, in JavaScript (jquery) that some actions are performed one after other, in an order.
Say, I need to load schools collection BEFORE loading teachers, in order to assing the myTeacher.SchoolName = schools[myTeacher.SchoolId].name;
The pseudo code bellow:
const studentsUrl='api/students', teachersUrl='api/teachers', schoolsUrl='api/schools';
let students = null, teachers = null, schools = null;
$(document).ready(function () {
getSchools();
getTeachers();
getStudents();
});
function getSchools() {
$.get(schoolsUrl, function (data) {
window.schools = data;
});
}
function getTeachers() {
$.get(teachersUrl, function (data) {
window.teachers = data;
// >>> SHOULD BE SURE, SCHOOLS already loaded!!!
$.each(teachers, function (key, item) {
item.school = schools[item.schoolId].name;
});
});
}
function getStudents() {
$.get(studentsUrl, function (data) {
window.students = data;
// >>> SHOULD BE SURE, TEACEHRS already loaded!!!
$.each(students, function (key, item) {
item.teacher = teachers[item.teacherId].name;
});
});
}
PS.
Is there another way to assure order but the encapsulation of one function at the end of another?
As others already suggested you can chain requests.
I made few changes to your code.
Added Strict Mode it helps to prevent bugs
The code wrapped in IFFE in order to prevent global pollution
If all apis belong to the same server you can process all this data on server side
and return one filled json.
in this way your server will do a little extra work on constructing this json but in other hand you will make only one ajax request instead of 3.
This will work faster and you can cache this json for some time
Code for the first solution
(function () {
'use strict';
const studentsUrl = 'api/students';
const teachersUrl = 'api/teachers';
const schoolsUrl = 'api/schools';
let students = null;
let teachers = null;
let schools = null;
var scoolData = {
schools: null,
teachers: null,
students: null
};
$(document).ready(function () {
getSchools().then(function (schools) {
scoolData.schools = schools;
getTeachers().then(function (teachers) {
scoolData.teachers = teachers;
$.each(scoolData.teachers, function (key, item) {
item.school = scoolData.schools[item.schoolId].name;
});
});
});
});
function getSchools() {
return $.get(schoolsUrl);
}
function getTeachers() {
return $.get(teachersUrl,
function (result) {
scoolData.teachers = result;
// >>> SHOULD BE SURE, SCHOOLS already loaded!!!
$.each(teachers, function (key, item) {
item.school = scoolData.schools[item.schoolId].name;
});
});
}
})();
Since you only need all the results available and each request does not depend on the previous you can use jQuery.when
let students = null;
let teachers = null;
let schools = null;
$(document).ready(function() {
$.when(
getSchools(),
getTeachers()
).done(function(shoolResults, teacherResults) {
window.schools = shoolResults;
window.teachers = teacherResults;
handleTeachers();
getStudents();
});
function getSchools() {
return $.ajax({
type: 'GET',
url: schoolsUrl
});
}
function getTeachers() {
return $.ajax({
type: 'GET',
url: teachersUrl
});
}
function handleTeachers() {
$.each(teachers, function (key, item) {
item.school = schools[item.schoolId].name;
});
}
});
If you want them in order (though I'm not sure I understand why, since you retrieve all schools/teachers/students anyway), you can simply do this.
Note: get* functions are dummies in the following sample. Instead, just return the result of $.get calls from them:
function getSchools() {
return Promise.resolve({1: {name: 'school1'}});
}
function getTeachers() {
return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}
function getStudents() {
return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}
(async () => {
const schools = await getSchools();
const teachers = await getTeachers();
const students = await getStudents();
// Alternative for the $.each code
Object.values(teachers).forEach(teacher => teacher.school = schools[teacher.schoolId].name);
Object.values(students).forEach(student => student.teacher = teachers[student.teacherId].name);
console.log(schools, teachers, students);
})();
Another note: this is ES8 code, I'll post a non async/await version if you need to support older browsers and can't use a transpiler like Babel.
Non ES8-dependent code:
function getSchools() {
return Promise.resolve({1: {name: 'school1'}});
}
function getTeachers() {
return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}
function getStudents() {
return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}
let schools = null, teachers = null, students = null;
getSchools().then(_schools => {
schools = _schools;
return getTeachers();
}).then(_teachers => {
teachers = _teachers;
return getStudents();
}).then(_students => {
students = _students;
for (var _ in teachers) {
teachers[_].school = schools[teachers[_].schoolId].name;
}
for (var _ in students) {
students[_].teacher = teachers[students[_].teacherId].name
}
console.log(schools, teachers, students);
});
Call getTeachers(); when getSchools(); return success or complete, success preferred since complete runs if there's an error..
I think you are looking for this one.
getSchools().done(function(data){
var someId = data.findThatId;
getTeachers(someId);
});
You will need to return data from ajax call to get data in done.
You may load them asynchronously but you have to wait until both calls are finished.
To achieve this, add return before your ajax calls and combine the results in your ready function (not in the success handler of the teachers call):
let schoolsPromise = getSchools();
let teachersPromise = getTeachers();
$.when(schoolsPromise, teachersPromise)
.then((schools, teachers) => {
$.each(teachers, (key, item) => {
item.school = schools[item.schoolId].name;
});
});
I am trying to display(log) the items that I added using the addItems() function when I call(log) the getItems() function..
console.log(cart.addItem("ITEMMSSS", 100, 10)) << puts out
ShoppingCart { itemName: 'ITEMMSSS', quantity: 100, price: 10 }
as expected
but the console.log(cart.getItems()) puts out -1-
when I console.log(this.addedItems) it logs out -undefined-(twice)
I don't understand why I don't have access to the returned value from the
addItem() function.
class ShoppingCart {
constructor(itemName, quantity, price) {
this.itemName = itemName
this.quantity = quantity
this.price = price
}
addItem(...items) {
const addedItems = new ShoppingCart(...items)
return addedItems
}
getItems(addedItems) {
const el = []
const selected = this.addedItems
const newArr = el.push(selected)
return newArr
}
clear(...item) {
// return items.slice(0, ...items).concat(items.slice(...items + 1))
}
clone(...items) {
// console.log(this)
// copiedCart.map((item) => {
// return item
// })
}
}
FIXed the issue,
class ShoppingCart {
constructor(items) {
this.items = []
}
addItem(name, quantity, pricePerUnit) {
const shopCart = this.items.push({
name: name,
quantity: quantity,
pricePerUnit: pricePerUnit
})
return shopCart
}
getItems(...items) {
const displayItems = this.items
return displayItems
}
clear(...items) {
const emptyCart = this.items.length = []
return emptyCart
}
clone(...items) {
const copyCart = new ShoppingCart()
copyCart.items = JSON.parse(JSON.stringify(this.items))
return copyCart
}
}
//
// const cart1 = new ShoppingCart('banana', 12, 23)
// const cart2 = cart1.clone()
// //
// console.log(cart2)
// //
module.exports = ShoppingCart;
But can't seem to get an immutable copy of the shoppingCart <--
fixed issue after reading about deep copying
clone(...items) {
const copyCart = new ShoppingCart()
copyCart.items = JSON.parse(JSON.stringify(this.items))
return copyCart
}
Couple ideas for author to think about:
1) addItem should be a function of an existing instance of ShopppingCart. Creating a new object inside addItems should not be necessary. I only know of returning a value from a setter to be usually only done for "fluent" setters practices so that you can chain them together. But that would be returning the current object.
2) getItems should usually not perform any logic. Usually getters return the current state of a variable / object member.
To address authors direct question:
You are returning the addItems object from the function but not storing it.
Try:
cart = cart.addItem("ITEMMSSS", 100, 10)
I am trying to understand the OOP with the following example below. Can you please explain what am I doing wrong and why?
var shoppingcartModel = function() {
var _Cart = function() {
return {
totalPrice: {},
products: []
};
}
return {
cart: _Cart,
addProducts: function(product) {
return _Cart().products.push(product);
}
};
};
var shoppingCart = shoppingcartModel()
console.log(shoppingCart.cart())
shoppingCart.addProducts('product1')
shoppingCart.addProducts('product2')
console.log(shoppingCart.cart())
_Cart is a function that returns an object, not an object itself. Whenever you call Cart_(), including in addProducts, you create a new object, so whatever you push to one of the old objects is disregarded because no reference to the old object remains.
Try something like this instead:
var shoppingcartModel = function() {
const cart = {
totalPrice: {},
products: []
};
return {
cart,
addProducts: function(product) {
return cart.products.push(product);
}
};
};
var shoppingCart = shoppingcartModel()
console.log(shoppingCart.cart)
shoppingCart.addProducts('product1')
shoppingCart.addProducts('product2')
console.log(shoppingCart.cart)
I am trying to create a service that acts as a shopping basket, allowing users to add/remove items. However I am encountering the following error message Cannot read property 'addServiceItem' of undefined. Here is my code so far:
controller.js
$scope.addServiceItem = function(bookingSelected, title, price, length) {
if (bookingSelected === true) {
BusinessService.manageServiceItems.addServiceItemcont(title, price, length);
}
}
services.js
function BusinessService($http) {
function manageServiceItems() {
var serviceItem = [];
function addServiceItem(title, price, length) {
serviceItem.push({
title: title,
price: price,
length: length
});
}
this.getServiceItem = function() {
return serviceItem;
}
}
The service definition should be returning an object or function. From docs
The service factory function generates the single object or function
that represents the service to the rest of the application. The object
or function returned by the service is injected into any component
(controller, service, filter or directive) that specifies a dependency
on the service.
function BusinessService($http) {
var BusinessService = this, serviceItem = [];
BusinessService.manageServiceItems = {
addServiceItem: function(title, price, length) {
serviceItem.push({
title: title,
price: price,
length: length
});
},
getServiceItem: function() {
return serviceItem;
}
}
return BusinessService;
};
var myModule = angular.module('myModule', [])
.factory('BusinessService', BusinessService);
I have a class itemCollection that stores information about purchases. This class has array _items as property where purchases are stores. When user adds new purchase in cart this class using addItem method that adds that item in _items property if its already has this item this method iterates quantity property if not adds new item in array.
Problem is that instead of adding new item in array when other item is chosen its keeps incrementing quantity property of a first item that was added.
cartCollection class (object):
var cartCollection = {
_items: [],
addItem: function(obj) {
'use strict';
var purchase = {
item: {
id: obj.id,
name: obj.name,
price: obj.price
},
thisItemTotal: obj.price,
quantity: 1
};
var result = _.findWhere(this._items, purchase.item.id);
console.log(result);
if (typeof result != 'undefined') {
//console.log(result);
var index = _.findIndex(this._items, {
id: result.item.id
});
//console.log(index);
result.quantity++;
this._items[index] = result;
this._itemTotalPrice();
} else if (typeof result === 'undefined') {
console.log("Im was called!");
this._items.push(purchase);
console.log(this._items);
}
},
...
Since purchase doesn't have an ID, but has an "item" with an ID, The correct find statement should be:
var result = _.find(this._items, function(item) {
return item.item.id == purchase.item.id;
});
It might be better to rename _items to _purchases in order to disambiguate
The complete code should be something like:
addItem: function(obj) {
'use strict';
var purchase = {
item: _.pick(obj, 'id', 'name', 'price')
thisItemTotal: obj.price,
quantity: 1
};
var result = _.find(this._items, function(item) {
return item.item.id == purchase.item.id;
});
console.log(result);
if (result) {
result.quantity++;
this._itemTotalPrice();
}
else {
console.log("Im was called!");
this._items.push(purchase);
console.log(this._items);
}
},
Your findWhere statement is broken. It should be:
var result = _.findWhere(this._items, {id:purchase.item.id});
Good luck