I'm using knockout.js and moment.js to compute some values about a given month. The following code works as intended:
var calendarViewModel = {
selectedMonth: ko.observable(moment().format("M")),
daysInMonth: ko.observable(moment().month(3).daysInMonth())
};
ko.applyBindings(calendarViewModel);
The returned values are "4" for selectedMonth and "30" for daysInMonth.
But what I want to do is compute the value of daysInMonth based on the current value of selectedMonth (which will change). The best code I could come up with was this:
var calendarViewModel = {
selectedMonth: ko.observable(moment().format("M")),
daysInMonth: ko.observable(moment().month(calendarViewModel.selectedMonth() - 1).daysInMonth())
};
ko.applyBindings(calendarViewModel);
I get Uncaught TypeError: Cannot read property 'selectedMonth' of undefined in the console.
The thing I'm having trouble understanding is how to reference selectedMonth properly in this context. If I create selectedMonth in some other generic variable outside of my viewModel everything I'm doing works fine.
I'm quite certain that this has to do with my (poor) understanding of JavaScript objects and nothing to do with the libraries themselves.
You need to create a computed for that:
var calendarViewModel = function(){
var self = this;
self.selectedMonth = ko.observable(moment().format("M"));
self.daysInMonth = ko.computed(function(){
var selectedMonth = self.selectedMonths();
return moment().month(selectedMonth - 1).daysInMonth();
});
};
ko.applyBindings(new calendarViewModel());
As to the difference... here we are creating an actual instance of a calendarViewModel which will set up the this context the way you expect.
It also uses a closure to ensure you can access your own members correctly inside the computed.
Related
I know only one way of setting localstorage in HTML5
localStorage.name = "Peter Martin";
But, in the following discussion I found that there are 2 other ways to set localstorage.
localStorage - use getItem/setItem functions or access object directly?
localStorage.setItem(city, "New York");
localStorage[country] = "USA";
However, when I tried all 3 in the example below, seems the first works fine but issues with the other 2 methods. Can someone explain me out if all 3 methods are valid?
<html>
<head></head>
<body>
<button onclick="alpha()">Click Me</button>
<script>
function alpha(){
localStorage.name = "Peter Martin";
localStorage.setItem(city, "New York");
localStorage[country] = "USA";
}
</script>
</body>
</html>
As, there is some sandbox issue with StackOverflow, I am posting error image, as below:
This comes down to basic JavaScript syntax.
When you call localStorage.setItem(city, "New York");, you are referring to an identifier, city, which is not defined in the current scope. You should either define a variable named city, or just use the string "city" directly.
The same goes with localStorage[country] = "USA";. country is not defined in this scope, so the JavaScript engine throws an error.
Can someone explain me out if all 3 methods are valid?
Yes they are (ish). localstorage like pretty much everything in Javascript is an object. You can access properties in an object in two ways:
object.property
object['property']
See Property accessors in Javascript MDN. So there is no reason why you can't access properties of the localstorage object using property accessors as the above.
localStorage.setItem("city", "New York");
Is a method on the localstorage object that:
The setItem() method of the Storage interface, when passed a key name
and value, will add that key to the storage, or update that key's
value if it already exists.
MDN
So this is a valid way to "add that key to the storage, or update that key's
value if it already exists". Your having problems with this method because your passing an incorrect parameter city that doesn't exist. I believe you meant "city". As covered in this answer
You can break it down into
1> properties or values
localStorage.name = "Peter Martin";
localStorage["name"] = "Peter Martin";
The 2nd version allows you to use a JavaScript variable. But is harder to read because one needs to determine what value is in the variable.
IE
var tag= "name";
localStorage[tag] = "Peter Martin";
and
2> methods or functions that get / set value; etc.
localStorage.setItem("name", "Peter Martin");
vars allowed,
var obj = "name";
var value = "Peter Martin";
localStorage.setItem(obj, value);
With methods there are the .prototype goodies, which include things like .bind() that allows you to say do something when you call setItem. But generally when I want to do something after setting a property I do it on the very next line; I Keep it simple and readable.
I'm not sure why I would ever need to use the methods. But, on a modern system there should be no measurable difference in speed.
Maybe using methods would allow two tabs open at the same time to synchronize data between tabs?
There are situations in loops that change values of properties and either methods or properties works, I don't recall the details; but unlikely to need that with a localStorage string.
Now I recall, why a method or function is sometimes needed.
This fails using a property. (always returns 10 the last value of i)
var elements = document.getElementsByTagName('input');
var n = elements.length; // assume we have 10 elements for this example
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("This is element #" + i);
};
}
This works using a function
var elements = document.getElementsByTagName('input');
var n = elements.length; // assume we have 10 elements for this example
var makeHandler = function(num) { // outer function
return function() { // inner function
console.log("This is element #" + num);
};
};
for (var i = 0; i < n; i++) {
elements[i].onclick = makeHandler(i+1);
}
So if you are going through a long list and want to cancel then return to it using localStorage use the method.
Even though the localStorage may not require a method. Our subconscious brain recognizes patterns. And the loop with a property value pattern triggers a warning, anxiety because of how long it takes to find it, even though the details are not recalled. Though it would work since it does not store a reference to the variable it only stores static strings.
To program at max speed, people need to follow the patterns that they know work. Methods resolve the value of the value at the time the method was called.
<html>
<head></head>
<body>
<button onclick="alpha()">Click Me</button>
<script>
function alpha(){
localStorage.name = "Peter Martin";
localStorage.setItem("city", "New York");
localStorage["country"] = "USA";
}
</script>
</body>
</html>
I am struggling with what I know is a very basic question related to variable declaration. I've read everything I can find on variables but I don't know if my problem is related to 1) how I am declaring variables or 2) how I am setting the scope of the variables.
To start, my understanding of variables in Meteor is that if I use var, then I am setting file-scope, which would make that variable available to every helper for that particular template. If I do not use var, it will be global and therefore available to the helpers in every template. Is that correct?
The following block of code works fine, returning the correct value in the client:
Template.CompanyFinancials.helpers({
priceEarningsFy1: function () {
var compTicker = this.ticker
var price = Companies.findOne({ticker: compTicker}).capTable.lastClose;
var epsFy1 = Companies.findOne({ticker: compTicker}).fy1.eps;
return (price / epsFy1).toFixed(1)
});
I have dozens of similar calculations throughout this app and many which rely on more variables than this example, so I have been trying to factor out the variables and reuse them in the template, like so:
var compTicker = function() {
return this.ticker;
};
console.log(compTicker);
var price = function(compTicker) {
Companies.findOne({ticker: compTicker}).capTable.lastClose;
};
console.log(price);
var epsFy1 = function(compTicker) {
Companies.findOne({ticker: compTicker}).fy1.eps;
};
console.log(epsFy1);
Template.CompanyFinancials.helpers({
priceEarningsFy1: function (price, epsFy1) {
return (price / epsFy1).toFixed(1)
}
});
With this code, console.log() actually returns the text within each function (e.g., return this.ticker) for each variable, not the value. If I declare the variables without functions, like I’ve done within the helper, it returns undefined for compTicker.
I tried to follow this answer which explains reusable code, but not clear if same use case applies. My variables point to specific fields in the database, not necessarily calculations.
Can anyone help me repair my syntax? I'm writing multiples more code than I need to with my current understanding. Thank you.
EDIT
I also tried declaring the variables the same way they are declared in the helper, but these return undefined.
var compTicker = this.ticker;
console.log(compTicker);
var price = CompaniesFeed.findOne({ticker: this.ticker}).capTable.lastClose;
console.log(price);
var epsFy1 = CompaniesFeed.findOne({ticker: this.ticker}).fy1.eps;
console.log(epsFy1);
RESOLUTION:
Using global helpers and returning multiple values, then using dot notation to access in the template HTML:
Template.registerHelper('priceEarnings',function(){
var ticker = this.ticker;
var company = CompaniesFeed.findOne({ticker: ticker});
return {
peFy1: (company.capTable.lastClose / company.financial.fy1.eps).toFixed(1),
peFy2: (company.capTable.lastClose / company.financial.fy2.eps).toFixed(1)
};
});
<td>{{priceEarnings.peFy1}}x</td>
You might be looking for global helpers. These are helpers which can be reused across all templates.
For your priceEarningsFy1 function for example:
Template.registerHelper('priceEarningsFy1',ticker => {
const company = Companies.findOne({ticker: ticker});
return ( company.capTable.lastClose / company.fy1.eps ).toFixed(1);
});
In this case I've specified that ticker is to be provided as an argument. From a blaze template you would use {{priceEarningsFy1 this.ticker}} for example. To refer to this function from js code use UI._globalHelpers.priceEarningsFy1(ticker)
Note that any local functions you define inside a given file are available to any other functions inside the same file. My pattern is to put all my global helpers in one file sorted by name and then at the bottom add various utility functions for use by the global helpers. This keeps things relatively dehydrated.
This seems like the most basic part of using knockout, and I'm not sure why it isn't working, but for some reason , 2 of my 5 knockout observables are holding onto their new values.
In the setup of the model:
self.CProviderIdentifier = ko.observable();
self.ReferringProviderIdentifier = ko.observable();
self.BillableCareProviderIdentifier = ko.observable();
self.ServiceLocationIdentifier = ko.observable();
self.PracticeLocationIdentifier = ko.observable();
Inside of an AJAX call which returns a number of items inside of a JSON object, I extract the relevant pieces of information, and put them into the correct observable:
visitModel.CProviderIdentifier(data.CareProviderIdentifier);
visitModel.ReferringProviderIdentifier((data.ReferringProviderIdentifier == null ||
data.ReferringProviderIdentifier == "undefined") ? 0 : data.ReferringProviderIdentifier);
visitModel.BillableCareProviderIdentifier(data.BillableCareProviderIdentifier);
visitModel.PracticeLocationIdentifier(data.PracticeLocationIdentifier);
visitModel.ServiceLocationIdentifier(data.ServiceLocationIdentifier);
Now, if none of them worked, it would make (some) sense, but only CProviderIdentifier and ReferringProviderIdentifier have no data. I've checked the data in a break point right before getting into setting the properties, and the values from data are 1003 and 0, but the two observables are undefined are the above block of code.
I'm working on getting a fiddle working for this: https://jsfiddle.net/bz3mq6z9/
The assignment is made in the loadData function. Inside it, Javascript does not know what is visitModel. That variable does not exist and does not have any purpose in the setter.
Use self instead of visitModel. That way knockout knows that he is assigning values to the view model
Greetings
You have some bugs in your code:
should bind viewmode with DOM using:
ko.applyBindings(visitModel);
ko.observable is a function, so you should use call it before combine string.
<span data-bind="text: CProviderIdentifier() + 'cp'"></span>
it's not a good idea use visitModel in LoadData function, you can just use self to keep the reference.
see this demo: http://jsfiddle.net/bz3mq6z9/6/
i have two arrays defined like these
var theory= new Array();
var first;
var second;
function arrTheory () {
this.first= first;
this.second= second;
}
var subject= new Array();
...
function arrSubject () {
...
this.theory= theory;
...
}
how can i access to the elements in the theory array inside the bigger one?
I've tried
subject[0].theory.first;
but it doesn't work while
subject[0].name;
which is another field of the big array works fine. what i've missed?
I think the point is, you have an array with element as "obj" and added to another array.
So to access to a you could try
subject[0].theory[0].first;
I mean access to element of array by index and them access to the element of the object.
Ok, I have a good guess given the information you've provided.
theory in subject[0].theory is an array, right? How come you are using it like subject[0].theory.first? You should use an indexer like subject[0].theory[0].
In the code you showed us (before any potential edits), you don't add anything to the var theory= new Array();
Also beware to make sure that this is what you really expect. In JavaScript, the context of this changes depending on context of the caller and that's confusing sometimes.
subject[0].theory[0].first;
try that.
Using this in arrTheory() and arrSubject() will cause a syntax error because they are functions not objects and you need rather to set those properties to theory and subject arrays instead so you must use the correspondent array name instead of this like following:
var theory= new Array();
var first = "first";
var second = "second";
function arrTheory () {
theory.first= first;
theory.second= second;
}
var subject= new Array();
function arrSubject () {
subject.theory= theory;
}
arrTheory();
arrSubject();
alert(subject.theory.first + " / " + subject.theory.second);
then you can access first and second properties like this:
subject.theory.first
subject.theory.second
in the code above property name is not set in subject, if it's already set in the original code you can call it like this:
subject.name
jsfiddle
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Get actual HTML values using javascript
so i have two problems here. let me explain what i am trying to do first. I have a page that has values that change on it, however i want to grab the values before they change, keep them, and then once a button is pushed, change the html to the original html. Now first of all my biggest problem is that when i try to uncomment the initial2 function, it just doesnt work. it brings me to the webpage then for some reason the html url tries to change and it says it can not find the page. the second, and more understandable problem for me, is that the function previousaccept i cant get to use the values from the previousnames function.
function previousnames()
{
name= document.getElementById('name').innerHTML;
imagetitle= document.getElementById('imagetitle').innerHTML;
location=document.getElementById('location').innerHTML;
similarities = document.getElementById('similarities').innerHTML;
type = document.getElementById('type').innerHTML;
cost = document.getElementById('cost').innerHTML;
date = document.getElementById('date').innerHTML;
pictureid = document.getElementById('pictureid').src;
}
function previousaccept(name,imagetitle,location,similarities,value,type,cost,date,pictureid)
{
document.getElementById('name').innerHTML = name;
document.getElementById('location').innerHTML = location;
document.getElementById('similarities').innerHTML = similarities;
document.getElementById('type').innerHTML = type;
document.getElementById('cost').innerHTML = cost;
document.getElementById('date').innerHTML = date;
window.alert(pictureid);
document.getElementById('pictureid').src = pictureid;
}
window.onload=initial();
function initial()
{
myvalues;
previousnames;
}
/*
function initial2()
{
myvalues;
previousnames();
}*/
If you set the location (which is window.location), then the browser will go to a new web page. That's what you're doing in the previousnames() function with this line:
location=document.getElementById('location').innerHTML;
If you're trying to have a global variable named location, then give it a different name that isn't already used by the browser.
Also, you should explicitly declare any global variables you intend to use outside of your functions rather than use implicitly declared variables like you are which makes your code very prone to errors.
I think this will do what you want. The key is to make sure that the scope of the variables you are trying to store is such that the functions have access to them all. I do this by defining an empty object dataStore at the start of the onload function, and also defining the 2 other functions within the onload function. Putting all the stored data in a single object is convenient and avoids naming problems (such as the window.location problem noted by the previous answer.)
window.onload = function() {
var dataStore = {};
function getInitialData() {
dataStore = {
name: document.getElementById('name').innerHTML,
imagetitle: document.getElementById('imagetitle').innerHTML,
// and so on...
}
}
function resetData() {
document.getElementById('name').innerHTML = dataStore.name;
document.getElementById('imagetitle').innerHTML = dataStore.imagetitle;
// and so on...
}
getInitialData();
//... then later when you want to reset all the values
resetData();
}