Underscore no with templates undefined variable when running test - javascript

I am using underscore templates to create some markup. I am using the no with approach so I am defining the variable setting.
So my template declarations look like so:
_.template('<div><%= data.title %></div>', { titles: title }, { variable: 'data'});
It works fine and evaluates the template fine, but when i run my unit test, it seems that, I get the error data is undefined.
By replacing data with either this, or self, or obj, it seems to work fine. I am wondering if there are any penalties to using those words like so:
_.template('<div><%= this.title %></div>', { titles: title }, { variable: 'this'});
OR
_.template('<div><%= this.title %></div>', { titles: title }, { variable: 'obj'});
OR
_.template('<div><%= self.title %></div>', { titles: title }, { variable: 'self'});
Thanks for the info.

You are expecting 'titles' as the key in your object, whereas the key that object contains is 'title'. Also, you need not pass the third parameter to _.template.
Since _.template returns a function, you can directly call this function by passing the object it needs as parameter as shown below:
Working Demo
JavaScript:
var temp = {"title": "Hello world!"};
/* Approach 1
var t = _.template('<div><%= title %></div>');
document.getElementById("output").innerHTML = t(temp);
*/
/* Approach 2 */
document.getElementById("output").innerHTML = _.template('<div><%= title %></div>', temp);

Related

JavaScript selecting Object Arraylike?

The Problem is the following:
I have a JSON file that has objects with the following name: "item0": { ... }, "item1": { ... }, "item2": { ... }. But I can't access them when going through an if method.
What I've done so far:
$.getJSON('/assets/storage/items.json', function(data) {
jsonStringify = JSON.stringify(data);
jsonFile = JSON.parse(jsonStringify);
addItems();
});
var addItems = function() {
/* var declarations */
for (var i = 0; i < Object.keys(jsonFile).length; i++) {
path = 'jsonFile.item' + i;
name = path.name;
console.log(path.name);
console.log(path.type);
}
}
If I console.log path.name it returns undefined. But if I enter jsonFile.item0.name it returns the value. So how can I use the string path so that it's treated like an object, or is there an other way on how to name the json items.
As others stated 'jsonFile.item' + i is not retrieving anything from jsonFile: it is just a string.
Other issues:
It makes no sense to first stringify the data and then parse it again. That is moving back and forth to end up where you already were: data is the object you want to work with
Don't name your data jsonFile. It is an object, not JSON. JSON is text. But because of the above remark, you don't need this variable
Declare your variables with var, let or const, and avoid global variables.
Use the promise-like syntax ($.getJSON( ).then)
Iterate object properties without assuming they are called item0, item1,...
Suggested code:
$.getJSON('/assets/storage/items.json').then(function(data) {
for (const path in data) {
console.log(data[path].name, data[path].type);
}
});
What you want is to use object notation using a dynamic string value as a key instead of an object key. So, instead of using something like object.dynamicName you either have use object[dynamicName].
So in your example it would be like this.
path = 'item' + i;
jsonFile[path].name
I'm afraid you cannot expect a string to behave like an object.
What you can do is this:
path = `item${i}`
name = jsonFile[path].name

Problems in accessing sub properties from separate JavaScript file

I was trying to access sub-properties from a javascript file and it's giving me something weird!
Suppose this is my JS file named data.js
module.exports = {
something: {
name: "Something",
num: 1,
email: "something#gmail.com"
},
somethingtwo: {
name: "Something Something",
num: 2,
email: "somethingtwo#gmail.com"
},
};
In my main js file named app.js, where I need to access it, it looks like
var persons = require('./data.js');
var getAName = function() {
for(var name in persons) {
console.log(name.email);
}
}
I really don't know what goes wrong but I have been trying this for quite a long time now. The expected output is the email Ids from the data.js file but instead, i get undefined times the number of entries (if there are 2 entries in data.js, then I get 2 undefine and so on).
How can I access the email or the num from the data.js without those undefines?
console.log(name) is returning something somethingtwo
Well, name.email is undefined because name is a string.
You can test that by writing
console.log(typeof name);
Now, to solve your problem, you need to access the property correctly:
var getAName = function() {
for (var name in persons) {
console.log(persons[name].email)
}
}
Returns:
something#gmail.com
somethingtwo#gmail.com
for(var name in persons) {
//persons is actually an object not array
//you are actually iterating through keys of an object
//var name represent a key in that object
console.log(persons[name]); //value corresponding to the key
}
I guess this code will give you the desired result.
You should be using console.log(persons[name].email)
require don't automatically calls the module
var DataArchive = require('./data.js');
var module = DataArchive.module;
var persons = module.exports;
var getAName = function() {
for(var person in persons) {
//person should be something (first iteration) and somethingtwo (second iteration)
console.log(person.email);
}

How to use options object in Javascript's Function

I am trying this to get input as an argument with some objects,
function write(param) {
var str = param.str;
var elem = param.elem;
document.getElementById(elem).innerHTML= str;
}
and I'm passing this as an argument,
write({
elem:"op",
str:"Hello"
});
The thing I have to do is that I am having font tag with id 'op',
<font id="op"></font>
and when I run this I wont print Hello, as I have given Hello as an object parameter with op element for output.
I'm not sure where exactly your code has gone wrong. Here you can see that both the javascript and the html you produced should work together just fine.
function write(param) {
var str = param.str;
var elem = param.elem;
document.getElementById(elem).innerHTML = str;
}
write({
elem: 'op',
str: 'Hello'
})
<font id="op"></font>
As far as I'm seeing it, your code works as intended.
Answer for using an options object on a function:
In ES6 default parameters can prevent values to be undefined, however it does not actually compensate for the case that you're passing an object with missing parameters.
In this case I would suggest using Object.assign():
function write(options){
options = Object.assign({}, {
myDefaultParam: 'Hello',
elem: null,
str: ''
}, options);
if(options.elem) document.getElementById(options.elem).innerHTML = options.str;
}
What Object.assign() does is to merge a object of default options with the provided functions, allowing you to set default parameters that you can rely on. In this case write({}) would result in options being this object:
{
myDefaultParam: 'Hello',
elem: null,
str: ''
}
If this is overkill for you, I would suggest to simply check wether the keys are defined on your param object like this:
function write(param){
if(!param || !param.elem || !param.str) return false;
return document.getElementById(param.elem).innerHTML = param.str;
}

Searching json array for a specific attribute

Actually I want to search an attribute's value in an json array for one of its child. Now one condition is that the attribute will not be there in all the child's of the array. This is my json array.
[{
"heading1":"heading1",
"heading2":"heading2",
"heading3":"heading3",
"heading4":"heading4",
"heading5":"heading5",
"heading6":"heading6"
},
{
"column1":65536,
"column2":"school",
"column3":"testing purpose",
"column4":"DESKTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
},
{
"column1":98305,
"column2":"Nikhil",
"column3":"Test",
"column4":"LAPTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
}]
So presently I am working with the each loop but like this
var obj = $.parseJSON(JSON.stringify(response));
$.each(obj, function () {
console.log("heading1", this['heading1']);
});
Here response comes from mserver and it is the json array
Now I want to know can I search for this attribute in the json array without using a loop in jQuery.
Based on your sample code what I understand you have is an array of objects and you want to find objects with one specific property and or value:
This will return true if the object has the property
var results= arr.filter(function(item){ return item.hasOwnProperty("column5"); });
Or you can perform additional action when you find the property:
arr.filter(function(item){
if (item.hasOwnProperty("column5")) {
return item["column5"] === 'demo 01'; //or item.column5 === 'demo 01'
}
return false;
});
This only works on IE9+ if you need this to run in older versions of IE, please follow the instructions under polyfill:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
The you can check like
var obj = $.parseJSON(response);
$.each(obj, function (index,value) {
if(typeof obj[index].heading2 !== "undefined")
{
alert(obj[index].heading2);
}
when in other object of array element not find then it returns undefined. and you can check like that.
you can check in this http://jsfiddle.net/gKRCH/
It's best to use a loop. But if the format of the JSON is regular, you could regex for the value in the response string.
I'm not recommending this method, just pointing out that it exists.
var value = "heading1";
if( (new RegExp('"' + value + '"')).test(response) ){
// Found value
};
Here, we take the required value, wrap it in quotation marks and search for it in the response.
This has several issues, such as:
It might find the pattern in a property name
If the value could contain regex special characters, they'll need escaping.
If your JSON contains values with escaped quotation marks, you could get a false positive from partial matches.
That's why it depends on you knowing the format of the data.
EDIT:
You can solve issue 2 by using this condition instead of regex. But it gives you less flexibility.
response.indexOf('"' + value + '"') !== -1
Try this,
$.each(object,function(key, value){
console.log(key);
console.log(value);
});
You can use this JS lib; DefiantJS (http://defiantjs.com). This lib extends the global object JSON with the method "search" - with which, you can perform XPath queries on JSON structures. Like the one you have exemplified.
With XPath expressions (which is standardised query language), you can find whatever you're looking for and DefiantJS will do the heavy-lifting for you - allowing your code to be neat and clean.
Here is the fiddle of this code:
http://jsfiddle.net/hbi99/q8xst/
Here is the code:
var data = [
{
"heading1": "heading1",
"heading2": "heading2",
"heading3": "heading3",
"heading4": "heading4",
"heading5": "heading5",
"heading6": "heading6"
},
{
"column1": 65536,
"column2": "school",
"column3": "testing purpose",
"column4": "DESKTOP",
"column5": "ACTIVE",
"column6": true,
"column7": "a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
},
{
"column1": 98305,
"column2": "Nikhil",
"column3": "Test",
"column4": "LAPTOP",
"column5": "ACTIVE",
"column6": true,
"column7": "a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
}
],
res = JSON.search( data, '//*[column4="DESKTOP"]' );
console.log( res[0].column2 );
// school

Ignoring undefined data / vars in an underscore template

Still learning backbone so bear with me;
I'm trying to add a new model with blank fields to a view, but the template I've created has a whole bunch of
<input value="<%= some_value %>" type="whatever" />
Works perfectly fine when fetching data, it populates it and all goes well. The trouble arises when I want to create a new (blank) rendered view, it gives me
Uncaught ReferenceError: some_value is not defined
I can set defaults (I do already for a few that have default values in the db) but that means typing out over 40 of them with blanks; is there a better way of handling this?
I'm fiddling around with the underscore template itself, trying something like <%= if(some_value != undefined){ some_value } %> but that also seems a bit cumbersome.
Pass the template data inside a wrapper object. Missing property access won't throw an error:
So, instead of:
var template = _.template('<%= foo %><%= bar %>');
var model = {foo:'foo'};
var result = template(model); //-> Error
Try:
var template = _.template('<%= model.foo %><%= model.bar %>');
var model = {foo:'foo'};
var result = template({model:model}); //-> "foo"
Actually, you can use arguments inside of your template:
<% if(!_.isUndefined(arguments[0].foo)) { %>
...
<% } %>
No,
There is no actual fix for this due to the way underscore templates are implemented.
See this discussion about it:
I'm afraid that this is simply the way that with(){} works in JS. If the variable isn't declared, it's a ReferenceError. There's nothing we can do about it, while preserving the rest of template behavior.
The only way you can accomplish what you're looking for is to either wrap the object with another object like the other answer suggested, or setting up defaults.
If you check source code for generated template function, you will see something like this:
with (obj||{}) {
...
// model property is used as variable name
...
}
What happens here: at first JS tries to find your property in "obj", which is model (more about with statement). This property is not found in "obj" scope, so JS traverses up and up until global scope and finally throws exception.
So, you can specify your scope directly to fix that:
<input value="<%= obj.some_value %>" type="whatever" />
At least it worked for me.
Actually, you can access vars like initial object properties.
If you'll activate debugger into template, you can find variable "obj", that contains all your data.
So instead of <%= title %> you should write <%= obj.title %>
lodash, an underscore replacement, provides a template function with a built-in solution. It has an option to wrap the data in another object to avoid the "with" statement that causes the error.
Sample usage from the API documentation:
// using the `variable` option to ensure a with-statement isn’t used in the compiled template
var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
compiled.source;
// → function(data) {
// var __t, __p = '';
// __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
// return __p;
// }
A very simple solution: you can ensure that your data collection is normalized, i.e. that all properties are present in each object (with a null value if they are unused). A function like this can help:
function normalizeCollection (collection, properties) {
properties = properties || [];
return _.map(collection, function (obj) {
return _.assign({}, _.zipObject(properties, _.fill(Array(properties.length), null)), obj);
});
}
(Note: _.zipObject and _.fill are available in recent versions of lodash but not underscore)
Use it like this:
var coll = [
{ id: 1, name: "Eisenstein"},
{ id: 2 }
];
var c = normalizeCollection(coll, ["id", "name", "age"]);
// Output =>
// [
// { age: null, id: 1, name: "Eisenstein" },
// { age: null, id: 2, name: null }
// ]
Of course, you don't have to transform your data permanently – just invoke the function on the fly as you call your template rendering function:
var compiled = _.template(""); // Your template string here
// var output = compiled(data); // Instead of this
var output = compiled(normalizeCollection(data)); // Do this
You can abstract #Dmitri's answer further by adding a function to your model and using it in your template.
For example:
Model :
new Model = Backbone.Model.extend({
defaults: {
has_prop: function(prop) {
return _.isUndefined(this[property]) ? false : true;
}
}
});
Template:
<% if(has_prop('property')) { %>
// Property is available
<% } %>
As the comment in his answer suggests this is more extendable.

Categories