I managed to recreate a MVC calorie tracker app from a course and I am trying to convert it to ES6 classes now.
I am a little stuck in understanding how to call the methods in the Module inside the Controller to return the items I need.
class Item {
constructor() {
this.data = {
items: [{
name: 'Salad',
calories: 200,
id: 0
},
{
name: 'Eggs',
calories: 500,
id: 1
}],
totalCalories: 0,
currentItem: null
}
};
getItems() {
return this.data.items
};
logData = () => {
console.log(data.items);
};
}
class App {
constructor(Item, UI) {
this.Item = Item;
this.UI = UI;
}
init() {
const items = Item.getItems();
UI.populateItemList(items)
}
}
const application = new App(new Item(), new UI())
When I try to call Item.logData() in the console it gives me TypeError: this.data is undefined.
I researched online and it seems that the method I declared is for the constructor only. How would I go about declaring methods that I'll use in the Controller or in any other class, just like I did below by returning a method out of the constructor?
What Im trying to convert initially looks like this:
const ItemCtrl = (function () {
const Item = function (id, name, calories) {
this.name = name;
this.id = id;
this.calories = calories;
}
const data = {
items: StorageCtrl.getStorage(),
totalCalories: 0,
currentItem: null
}
return {
getItems: function () {
return data.items
},
logData: function () {
return data;
}
}
const App = (function (ItemCtrl, StorageCtrl, UICtrl) {
return {
init: function () {
const items = ItemCtrl.getItems();
UICtrl.populateItems(items);
}
}
})(ItemCtrl, StorageCtrl, UICtrl);
App.init();
You need to initialise the controller first:
class App {
constructor(Item, UI) {
this.item = new Item();
this.UI = new UI();
}
init() {
const items = this.item.getItems();
this.UI.populateItemList(items)
}
}
Related
I'm making a real time app and want to convert this class code to a function.
Would the function below be the equivalent? And from what anyone can see, would there be a benefit to keeping it a class as opposed to a function?
class IdeaService {
constructor() {
this.ideas = [];
}
async find() {
return this.ideas;
}
async create(data) {
const idea = {
id: this.ideas.length,
text: data.text,
tech: data.tech,
viewer: data.viewer
};
idea.time = moment().format('h:mm:ss a');
this.ideas.push(idea);
return idea;
}
}
FUNCTION
function ideaService() {
let ideas = [];
async find() {
return ideas;
}
async create(data) {
const idea = {
id: ideas.length,
text: data.text,
tech: data.tech,
viewer: data.viewer
}
idea.time = moment().formate('h:mm:ss a');
ideas.push(idea);
return idea;
}
}
try this
let IdeaService = (function() {
let ideas = [];
async function find() {
return ideas;
}
async function create(data) {
const idea = {
id: ideas.length,
text: data.text,
tech: data.tech,
viewer: data.viewer
};
idea.time = moment().format('h:mm:ss a');
ideas.push(idea);
return idea;
}
return {
find,
create
}
})();
EDIT! If youd like this module to NOT be instantiated upon this file running remove the (); at the end. so the function would be:
IdeaService = (function() {
});
and to instantiate it would be:
let ideaService = IdeaService();
there is a strange behavior in an argument inside a Global Javascript object:
I need to test my code with jasmine.js, but I can´t pass the expected value to the argument always return undefined in jasmine test.
//My model
myGlobalObject = function(){
_myCart = function(){
return {
total : 0,
products : []
}
}
return {
init: function(strangeArgument){
console.log(strangeArgument) //this return undefined in jasmine test
},
myCart : _myCart,
addProduct : function(Products){
return _myCart()
},
.....
}
}
The test:
const c{
empty : {
total: {
beforeVAT: 0,
afterVAT: 0,
VAT: 0
},
products: []
}
}
beforeEach(() => {
this.instance = myGlobalObject();
this.instance.init();
this.productWithoutQuantity = Object.assign({}, _.productA);
delete this.productWithoutQuantity.quantity;
this.productWithQuantity = Object.assign({}, _.productB);
});
test(`the cart should be empty`, () => {
expect(this.instance.getCart()).toEqual(c.empty);
});
.... more tests
And my main js:
var e = myGlobalObject();
var initialState = function (){
return {
total: {
beforeVAT: 0,
afterVAT: 0,
VAT: 0
},
products: []
}
}
e.init(initialState);
What its wrong?
Although I fail to completely understand the intent of the OP here, following is my take on the question
_myCart can be a local variable because it doesn't seem to serve any greater purpose, atleast from the code provided by OP
The call to instance.init can be with empty parenthesis or with a legitimate variable- depends on what OP is trying to achieve here.
I've included both main.js code snippet as well as testVariable.instance.init(); (on a simple note its undefined if it is undefined anyway as commented by #Bergi)
See it in action here
myGlobalObject = function() {
this._myCart = function() {
return {
total: 0,
products: []
}
}
return {
init: function(strangeArgument) {
console.log(strangeArgument)
},
myCart: this._myCart,
addProduct: function(Products) {
return this._myCart()
}
}
}
var e = myGlobalObject();
var initialState = function() {
return {
total: {
beforeVAT: 0,
afterVAT: 0,
VAT: 0
},
products: []
}
}
e.init(initialState);
describe('ajax test suite', function() {
var testVariable = {}
var c = {
empty: {
total: 0,
products: []
}
}
beforeEach(function() {
testVariable.instance = myGlobalObject();
testVariable.instance.init("hello");
testVariable.instance.init();
});
it('the cart should be empty', function() {
expect(testVariable.instance.myCart()).toEqual(c.empty);
});
});
I have problems with object scope.
Here is my class code
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype = (function () {
var _self = this;
var fetchItemsList = function (postData, jtParams) {
return _self.dataSet;
};
var createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
_self.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
var removeItem = function (postData) {
_self.items = removeFromArrayByPropertyValue(_self.items, "id", postData.id);
_self.dataSet["Records"] = _self.items;
_self.generateId = makeIdCounter(findMaxInArray(_self.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
return {
setupTable: function () {
$(_self.settings["table-container"]).jtable({
title: _self.settings['title'],
actions: {
listAction: fetchItemsList,
deleteAction: removeItem
},
fields: _self.fields
});
},
load: function () {
$(_self.settings['table-container']).jtable('load');
},
submit: function () {
_self.dataHiddenInput.val(JSON.stringify(_self.dataSet["Records"]));
}
};
})();
I have problems with accessing object fields.
I tried to use self to maintain calling scope. But because it is initialized firstly from global scope, I get Window object saved in _self.
Without _self just with this it also doesn't work . Because as I can guess my functions fetchItemsList are called from the jTable context and than this points to Window object, so I get error undefined.
I have tried different ways, but none of them work.
Please suggest how can I solve this problem.
Thx.
UPDATE
Here is version with all method being exposed as public.
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype.fetchItemsList = function (postData, jtParams) {
return this.dataSet;
};
DynamicItemList.prototype.createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
this.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
DynamicItemList.prototype.setupTable = function () {
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
};
DynamicItemList.prototype.load = function () {
$(this.settings['table-container']).jtable('load');
};
DynamicItemList.prototype.submit = function () {
this.dataHiddenInput.val(JSON.stringify(this.dataSet["Records"]));
};
DynamicItemList.prototype.removeItem = function (postData) {
this.items = removeFromArrayByPropertyValue(this.items, "id", postData.id);
this.dataSet["Records"] = this.items;
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
DynamicItemList.prototype.updateItem = function (postData) {
postData = parseQueryString(postData);
var indexObjToUpdate = findIndexOfObjByPropertyValue(this.items, "id", postData.id);
if (indexObjToUpdate >= 0) {
this.items[indexObjToUpdate] = postData;
return DynamicItemList.RESULT_OK;
}
else {
return DynamicItemList.RESULT_ERROR;
}
};
Your assigning a function directly to the prototype. DynamicItemList.prototype= Normally it's the form DynamicItemList.prototype.somefunc=
Thanks everyone for help, I've just figured out where is the problem.
As for last version with methods exposed as public.
Problematic part is
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: this.fetchItemsList,
createAction: this.createItem,
updateAction: this.updateItem,
deleteAction: this.removeItem
},
fields: this.fields
});
};
Here new object is created which has no idea about variable of object where it is being created.
I've I changed my code to the following as you can see above.
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
And now it works like a charm. If this method has drawbacks, please let me know.
My problem was initially in this part and keeping methods private doesn't make any sense because my object is used by another library.
Thx everyone.
You need to make your prototype methods use the this keyword (so that they dyynamically receive the instance they were called upon), but you need to bind the instance in the callbacks that you pass into jtable.
DynamicItemList.prototype.setupTable = function () {
var self = this;
function fetchItemsList(postData, jtParams) {
return self.dataSet;
}
function createItem(item) {
item = parseQueryString(item);
item.id = self.generateId();
self.items.push(item);
return {
"Result": "OK",
"Record": item
};
}
… // other callbacks
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: fetchItemsList,
createAction: createItem,
updateAction: updateItem,
deleteAction: removeItem
},
fields: this.fields
});
};
Here is a very basic attempt to create a "hello world"-like JS app using the module and MVC patterns.
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(appModules.exampleModul.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
appModules.exampleModul.view.display();
This works fine, but I'm not happy how I have to reference the model function from the view, using the full object path: appModules.exampleModul.model.getAsString(). How can I expose the public model methods to the view, so I could simply use something like model.getAsString()? Or do I need to organize the code differently?
One option is you can convert those objects into private implementations.
appModules.exampleModul = (function() {
var _data = ['foo', 'bar'];
// private variable
var _view = {
display : function() {
$('body').append(_model.getAsString());
},
};
var _model = {
getAsString : function() {
return _data.join(', ');
},
};
return {
view : _view,
model : _model
};
})();
You could do something like this:
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(this.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
var display = appModules.exampleModul.view.display.bind(appModules.exampleModul);
display();
Which isn't really the prettiest of solutions, but does offer a more generic solution inside the display function!
I am currently trying to learn KnockOutJS. I thought it would be a great idea to create a simple task-list application.
I do not want to write a long text here, let's dive into my problem. I appreciate all kind of help - I am new to KnockOutJS tho!
The tasks are declared as followed:
var Task = function (data) {
var self = this;
self.name = ko.observable(data.name);
self.status = ko.observable(data.status);
self.priority = ko.observable(data.priority);
}
And the view model looks like this
var TaskListViewModel = function() {
var self = this;
self.currentTask = ko.observable();
self.currentTask(new Task({ name: "", status: false, priority: new Priority({ name: "", value: 0 }) }));
self.tasksArr = ko.observableArray();
self.tasks = ko.computed(function () {
return self.tasksArr.slice().sort(self.sortTasks);
}, self);
self.sortTasks = function (l, r) {
if (l.status() != r.status()) {
if (l.status()) return 1;
else return -1;
}
return (l.priority().value > r.priority().value) ? 1 : -1;
};
self.priorities = [
new Priority({ name: "Low", value: 3 }),
new Priority({ name: "Medium", value: 2 }),
new Priority({ name: "High", value: 1 })
];
// Adds a task to the list
// also saves updated task list to localstorage
self.addTask = function () {
self.tasksArr.push(new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() }));
self.localStorageSave();
self.currentTask().name("");
};
// Removes a task to a list
// also saves updated task list to localstorage
self.removeTask = function (task) {
self.tasksArr.remove(task);
self.localStorageSave();
};
// Simple test function to check if event is fired.
self.testFunction = function (task) {
console.log("Test function called");
};
// Saves all tasks to localStorage
self.localStorageSave = function () {
localStorage.setItem("romaTasks", ko.toJSON(self.tasksArr));
};
// loads saved data from localstorage and parses them correctly.
self.localStorageLoad = function () {
var parsed = JSON.parse(localStorage.getItem("romaTasks"));
if (parsed != null) {
var tTask = null;
for (var i = 0; i < parsed.length; i++) {
tTask = new Task({
name: parsed[i].name,
status: parsed[i].status,
priority: new Priority({
name: parsed[i].priority.name,
value: parsed[i].priority.value
})
});
self.tasksArr.push(tTask);
}
}
};
self.localStorageLoad();
}
What I want to do in my html is pretty simple.
All tasks I have added are saved to localStorage. The save function is, as you can see, called each time an element has been added & removed. But I also want to save as soon as the status of each task has been changed, but it is not possible to use subscribe here, such as
self.status.subscribe(function() {});
because I cannot access self.tasksArr from the Task class.
Any idea? Is it possible to make the self.tasksArr public somehow?
Thanks in advance!
Try this:
self.addTask = function () {
var myTask = new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() })
myTask.status.subscribe(function (newValue) {
self.localStorageSave();
});
self.tasksArr.push(myTask);
self.localStorageSave();
self.currentTask().name("");
};