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.
Related
I am new to Javascript development.
I am trying to assign HTML elements IDs stored in an array to shorthands to be used in my function later.
So that instead of writing :
let addprop = document.querySelector(`#addprop`);
let readprop = document.querySelector(`#readprop`);
let editprop = document.querySelector(`#editprop`);
let footer = document.querySelector(`#footer`);
let association = document.querySelector(`#association`);
I can attribute elements ids that i store in an array like this :
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"] ;
arrayElements.forEach(el => { return(new Function (`${el} = document.querySelector("#${el}");`)()); });
Now, this bit of code works but from what I read here :
Execute JavaScript code stored as a string
This is probably not a good way to do it and also declares global variables.
One problem I encountered is that if I try to directly execute the assignment like this :
el = document.querySelector(`#${el}`);
Then the el value takes the value of the named access ID element (https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object) and breaks the code.
So I resorted to generate a string first then execute it.
I could simply assign each shorthand manually but I spent way too much time trying to make this work and am now left curious as to what would be a good solution or approach for this.
And would the scope limitations for loops simply forbid me to do this without using global variables ?
edit : switched the working code in one line
Possible answer :
1 - does it matter to declare global variables like that ? As these variables already exist globally because of browsers named access for elements IDs.
2 - By kiranvj's answer, a solution can be to store in an object structured as keys being the shortcuts and the full strings being the values, and calling the shortcuts with the object[key] method ; or using destructuring to assign the values to variable directly with :
const {addprop, readprop, editprop, idfooter, assocpatients} = elements;
I feel like I am missing something on this last one but it also seems to work.
In the end I will stick with my first code as condensing the function in one line seems to negate the risks of cross site scripting (?), and global values for the variables assigned though this method anyway already exist because of named access.
You can create a dictionary with all the elements with ID and then destroy it into your variables, ignoring the unused ones.
function getAllElementsWithId() {
let elements = {}
for (let el of document.querySelectorAll('[id]')) {
if (!(el.id in elements)) {
elements[el.id] = el
}
}
return elements
}
let { addprop, readprop, editprop, footer, association } = getAllElementsWithId()
This uses document.querySelectorAll (link to MDN) to get all elements with an ID. Notice that for big pages this could be a performance issue.
Also, what you would usually do is to add them into a container, in this case it seems like a dictionary.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"]
let elementsId = Object.fromEntries(arrayElements.map(id => [id, document.getElementById(id)]))
This uses Object.fromEntries (link to MDN) to generate the dictionary. Also I'm using document.getElementById (link to MDN) instead of document.querySelector so you don't need to add the hashtag before the id.
If you are concerned about global scope, you can try something like below. Use forEach instead of map . map also work but since you are not handling the return of map, forEach would be a better choice.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"];
let elements = {};
arrayElements.forEach(el => elements[el] = document.querySelector(`#${el}`));
// access variables like elements.ID-NAME
console.log(elements);
<div id="addprop"></div>
<div id="readprop"></div>
Object destructing can be used if you know the object key name.
example : let {addprop} = element;
Another thing which you might be interested is Automatic global variables
This means a new variable (scoped to window) with the name of element id is created for all the elements in page. See the html5 spec. I would not recommend using it though.
So you don't have to call like document.querySelector('addprop')
addprop variable will have the DOM object.
See this example
// these works due to automatic global varaibles binding
alert(addprop);
console.log(addprop);
<div id="addprop">Some contents</div>
As I understand it, eval() can be harmful. And it's annoying seeing all the warnings in my JSLint.
I've got a number of functions that are identical for my Wishlist / Shopping Cart. So I'd like to make it dynamic and only have one function of each.
Instead of cart.addItem() and wish.addItem(), I want cartWish.addItem(type).
Inside of cartWish.addItem() I need to access cart.data or wish.data, depending on the type argument.
How can I do this without resorting to eval(type).data?
I tried this[type].data and it didn't seem to work right.
What's the difference between
cart.addItem(...);
wish.addItem(...)
and
cartWish.addItem("cart", ...);
cartWish.addItem("wish", ...);
Seems like the same number of lines of code, and then all you've done is obfuscate what you are really doing. Maybe create a function that takes either a cart or wish object and assume they have the same interface:
function addItem(x, data) {
x.addItem(data);
}
var cart = ...
var wish = ...
addItem(cart, {...});
addItem(wish, {...});
Another option is to create a class:
function Item(type) {
this.type = type;
}
Item.prototype.add = function add(...) {
// ...
};
var cart = new Item("cart");
var wish = new Item("wish");
cart.add(...)
wish.add(...)
It was poor programming on my part. I didn't know that JavaScript tended to make references of objects instead of copies.
So doing...
var myReference=(type == "cart") ? cart.data : wish.data;
myReference[0].name="Bob Dole's Grill";
... will actually change cart.data[0].name outside of the function. And it will do so without making a copy of the cart object in memory.
Note: You could also just pass in the object by reference into the function, but I'm not sure if I can, because I'm sometimes invoking this function from a KnockoutJS click event.
I'm trying to code my first non-hacked up jQuery plug-in and I'm struggling to create a basic object with a constructor, public variables, private variables, and functions.
http://jqueryboilerplate.com/ has a great guide for creating objects that extend jQuery, but I don't think this is the right way to go for a generic object not attached to a DOM element.
Does anyone have a boilerplate template for creating a basic reusable object? i.e.
var calc = new CustomCalculator({'starting_value': 42});
calc.add(3);
calc.multiplyBy(2);
alert(calc.total); // Alerts (42 + 3) * 2 = 90
From your code sample it seems you just need a basic way of creating a JS object.
This would do the trick (but there are lots of other ways to do it):
function CustomCalculator(options){
var self = this;
self.total = options.starting_value;
self.add = function(term){
self.total += term;
};
self.multiplyBy = function(term){
self.total = self.total * term;
};
}
var calc = new CustomCalculator({'starting_value': 42});
calc.add(3);
calc.multiplyBy(2);
alert(calc.total); // Alerts 90
You'd still have to implement the string concatenation, but I'll leave that up to you
If you want private variables you should use closures.
I recently wrote an article about namespaces and modules in JavaScript which could help you: http://www.kenneth-truyers.net/2013/04/27/javascript-namespaces-and-modules/
And here's another one specifically about private variables: http://www.kenneth-truyers.net/2012/04/22/private-variables-in-javascript/
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();
}
So basically, I'm creating variables within the keyUp method of input box that get their data from a smarty loop (this is within the $(document.ready)
Here is the code
{section name=unitEl loop=$allNavies}
$("#attack-navy{$allNavies[unitEl].ID}-number").keyup(function(){
var unit = {$allNavies[unitEl]};
var element = $("#attack-navy" + unit.ID + "-number");
var available_count = {$NAVY_{$allNavies[unitEl].ID}_AVAILABLE_COUNT|default:'0'};
alert(unit.ID);
// Unit max = available count
if(element.val() > available_count)
{
completeUnitValue(element, available_count);
}
// If transport navy: Increase capacity
if({$allNavies[unitEl].ID} == 16 || {$allNavies[unitEl].ID} == 19 || {$allNavies[unitEl].ID} == 20)
{
$("#attack-max-capacity").text(getMaxCapacity());
}
});
{/section}
The problem is, when I alert any of the variables (unit, element, available_count) I receive undefined, but when i use the smarty {$allNavies[unitEl]} instead of variables, everything works fine. I just created variables to make the code more readable.
Anyone know why?
I call what you're doing "smarvascript". I loathe it and beg my coworkers to avoid it. But then, I loathe Smarty altogether, so there ya go.
This line:
var unit = {$allNavies[unitEl]};
assigns some PHP value into a JS var.
This line:
alert(unit.ID);
makes it look like you believe 'unit' is an object with properties. You cannot directly assign a PHP object into a JS object and expect it to work...
I'd need to see some of your PHP code and data structures to explain how you should do it, but it is possible that this might help
var unit = {$allNavies[unitEl]|json_encode};
Or, if $allNavies[unitEl] is an array:
var unit = {$allNavies[unitEl]|#json_encode};
I could probably help most if I knew what the structure of $allNavies was.
Also, I am curious...where are your {literal} markings to keep the JS curly braces from making Smarty freak out?
Edit:
Here is a little trick I like to use when I am forced into injecting Smarty into JS:
//{literal}
( function( allNavies )
{
/*
allNavies is now a JS object and you can work purely with JS in here
*/
}(
//{/literal}
{$allNavies|#json_encode}
//{literal}
) );
//{/literal}