I'm a js developer and work in an environment where we do API calls and get some data returned. The structure of the data returned is HIGHLY inconsistent so therefor we can't assume anything about the data returned.
Picture the following scenario:
$.ajax({
success: function(data){
// Now I want to access a property 4 levels in
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
})
What's the correct way to ensure data.results.users.length is not undefined? Because of the inconsistencies from the API, each level of the returned object could be broken/undefined. Do I really have to do the following:
if (data && data.results && data.results.users && data.results.users.length){
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
Aren't there more elegant solutions?
You can create helper function like this.
Expect object with structure like this :
var someObj = {
some: {
other: {
third: 'bingo',
qwe: 'test'
}
}
};
Would be great to have something like
getPropByPath(someObj, 'some.other.qwe');
So the implementation of getPropByPath may looks like following:
function getPropByPath(obj, path){
var parts = path.split('.'),
root = obj;
for(var i=0; i<parts.length; i++) {
if(root[parts[i]] !== 'undefined') {
root = root[parts[i]]
} else {
return false;
}
}
return root;
}
If at all levels there may be something undefined, you should check all levels, something like:
var length = data &&
data.results &&
data.results.users &&
data.results.users.length || 0;
You can also use some helper function. Here's one:
function getPropValue ( path, defaultIfNotExisting ) {
defaultIfNotExisting = defaultIfNotExisting || 0;
path = path.split('.'), test = this;
while (path.length) {
test = test[path.shift()];
if (!test) {
return defaultIfNotExisting;
}
}
return test;
}
// usage in your case:
if ( getPropValue.call(data, 'results.users', []).length) { /* do stuff */}
I currently have added some methods to "localStorage".
/**
*
* MOVED TO: https://github.com/iFind/html5MultidimensionalStorage
*
* This methods extends the default HTML5 Storage object and add support
* to set and get multidimensional data
*
* #example Storage.setObj('users.albums.sexPistols',"blah");
* #example Storage.setObj('users.albums.sexPistols',{ sid : "My Way", nancy : "Bitch" });
* #example Storage.setObj('users.albums.sexPistols.sid',"Other songs");
*
* #example Storage.getObj('users');
* #example Storage.getObj('users.albums');
* #example Storage.getObj('users.albums.sexPistols');
* #example Storage.getObj('users.albums.sexPistols.sid');
* #example Storage.getObj('users.albums.sexPistols.nancy');
*
* This is just a prototype and is not recommended to use at production apps
* USE AT YOUR OWN RISK
*
* #author Klederson Bueno <klederson#klederson.com>
* #author Gabor Zsoter <helo#zsitro.com>
*/
//Add Storage support for objects
Storage.prototype.__walker = function(path,o) {
//Validate if path is an object otherwise returns false
if(typeof path !== "object")
return undefined;
if(path.length === 0){
return o;
}
for(var i in path){
var prop = path[i];
//Check if path step exists
if(o.hasOwnProperty(prop)){
var val = o[prop];
if(typeof val == 'object'){
path.splice(0,1);
return this.__walker(path,val);
} else {
return val;
}
}
}
};
Storage.prototype.setObj = function(key, value) {
var key = encodeURIComponent(key);
var path = key.split('.');
//First level is always the localStorage key pair item
var _key = path[0];
var os = this.getItem(_key) !== null ? JSON.parse(this.getItem(_key)) : null; //general storage key pair element
path.splice(0,1);
if(os === null) {
os = {};
this.setItem(_key,JSON.stringify(os));
}
var innerWalker = function(path,o) {
//Validate if path is an object otherwise returns false
if(typeof path !== "object")
return undefined;
if(path.length == 1) {
o[path[0]] = value;
return o;
} else if(path.length === 0) {
os = value;
return os;
}
var val = null;
for(var i in path){
var prop = path[i];
//Check if path step exists
if(o.hasOwnProperty(prop)) {
val = o[prop];
if(typeof val == 'object'){
path.splice(0,1);
return innerWalker(path,val);
}
} else {
//create depth
o[prop] = {};
val = o[prop];
path.splice(0,1);
return innerWalker(path,val);
}
}
};
innerWalker(path,os);
this.setItem(_key,JSON.stringify(os));
};
Storage.prototype.getObj = function(key) {
var key = encodeURIComponent(key);
key = key.split('.');
//First level is always the localStorage key pair item
var _key = key[0];
var o = this.getItem(_key) ? JSON.parse(this.getItem(_key)) : null;
if(o === null)
return undefined;
key.splice(0,1);
return this.__walker(key,o);
};
In another class, I do:
define(['jquery', '_Errors'], function($, Errors) {
[...]
localStorage.getObj('blabla');
[...]
});
Before I was migrating to RequireJS i simply had all my functions / prototypes in a file called functions.js - is this still possible? Or do I need to specify all the functions I am going to use in every file?
You have two general choices to load your functions.js file:
Load the file that modifies Storage outside of RequireJS. This means putting it in its own script element. I would load it before RequireJS loads so that everything loaded by RequireJS benefits from changes made by this file.
Have RequireJS load functions.js. You'd need a configuration like:
paths: {
functions: "path/to/functions.js"
},
shim: {
functions: {
// This should be something that only your file creates.
// In some circumstances it is used by RequireJS to check whether something
// has loaded.
exports: 'Storage.prototype.getObj'
}
}
But then every module you have that needs to use the functions added by functions need to list it among its dependencies:
define(['jquery', '_Errors', 'functions'], function ($, Errors) {
[...]
localStorage.getObj('blabla');
[...]
});
Whenever I load things that make modifications start modifying things like Element, Node, or Storage, I prefer to use the first option above. I want all of this to be loaded first, so that everything that comes after sees the same environment. So it is all loaded outside RequireJS. If you find that performance becomes an issue because you are loading a lot of these small files, you could always customize your optimization step to concatenate these files and add them at the start of the bundle produced by r.js.
I have three js controller file and 1 lib
app/lib/Client
function Client(id,name,blc){
this.id=id;
this.name=name;
this.blc=blc;
};
Client.prototype.getName = function(){
return this.id+' '+this.name+' '+this.blc;
};
Client.prototype.withdraw = function(amount){
if(amount<0) return -1;
if(this.blc<amount) return -1;
return this.blc-=amount;
};
Client.prototype.deposite = function(amount){
if(amount<0) return -1;
return this.blc+=amount;
};
module.exports = Client;
app/controller/addClient //this is where I want to add to global array
var args = arguments[0] || {};
var Client = require('Client');
function doClick(e) {
var user_id = $.id.getValue();
var user_name = $.name.getValue();
var user_blc = $.Balance.getValue();
if(user_id.length<=0 && user_name.length<=0 && user_blc.length<=0){
alert('you entred an invalid information');
}
else{
var c = new Client(user_id,user_name,user_blc);
alert(c.getName()+' is add successfly');
//note!!!!!
//add a global array to save the data
$.id.setValue("");
$.name.setValue("");
$.Balance.setValue("");
}
}
$.addClient.open();
app/controller/allClient
//this is where i want to use the data in Global app to make a listView
If you really want a "global array" use Alloy.Globals
If you want a collection of models, use Backbone
What Im doing wrong in the below code?
//File1.js
var arr = [];
function insertName {
var name = "josh";
arr.push(name);
return name;
};
function validName(key) {
var index = arr.indexOf(key);
if (index == -1) {
return false;
} else {
return true;
}
}
var result = insertname();
exports.arr = arr;
exports.validName = validName;
//File2.js
var file1 = require("./File1.js");
var name = "josh";
var verify = file1.validName(name);
if(verify) {
cosnole.log("Valid name");
}else {
console.log("Error");
}
node File1.js
node File2.js
When Im executing File2.js, Im gettin undefined for arr[]. Can someone help me what Im doing in the below code
Node.js modules retain the variables you declared at their top level, till the module is Garbage Collected or you manually delete them. If you look at your File1.js, you are exporting the array object, nothing else. So when you say
var file1 = require("./File1.js");
file1 is just a reference to a JavaScript object which has an arr property. You can check this by printing the file1. The functions you created in File1 are never exported. So, you can fix it like this*
exports = module.exports = {
validName: validName,
insertName: insertName
}
Now, you are exporting the functions and they can still access the arr variable. From File2, you can invoke insertName like this
file1.insertName();
if (file1.validName("josh")) {
console.log("Valid name");
} else {
console.log("Error");
}
* To know more about exports and module.exports, you can check my blog post about this
Your code contains mistakes change your code like below
File1.js
var arr = [];
function insertName() {
var name = "josh";
arr.push(name);
return name;
};
function validName(key) {
var index = arr.indexOf(key);
if (index == -1) {
return false;
} else {
return true;
}
}
var result = insertName();
exports.validName = validName;
File2.js
var file1 = require("./File1.js");
var name = "josh";
var verify = file1.validName(name);
if(verify) {
console.log("Valid name");
} else {
console.log("Error");
}
You can simply use global.(name) = (value)
Example :
main.js
global.foo = 1;
require('./mod.js').show();
mod.js
module.exports = {
show : function(){
console.log(global.foo); // which prints "1"
}
}
I am looking for an efficient way to translate my Ember object to a json string, to use it in a websocket message below
/*
* Model
*/
App.node = Ember.Object.extend({
name: 'theName',
type: 'theType',
value: 'theValue',
})
The websocket method:
App.io.emit('node', {node: hash});
hash should be the json representation of the node. {name: thename, type: theType, ..}
There must be a fast onliner to do this.. I dont want to do it manualy since i have many attributes and they are likely to change..
As stated you can take inspiration from the ember-runtime/lib/core.js#inspect function to get the keys of an object, see http://jsfiddle.net/pangratz666/UUusD/
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, ret = [];
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
} // ignore useless items
if (Ember.typeOf(v) === 'function') {
continue;
}
ret.push(key);
}
}
return this.getProperties.apply(this, ret);
}
});
Note, since commit 1124005 - which is available in ember-latest.js and in the next release - you can pass the ret array directly to getProperties, so the return statement of the getJson function looks like this:
return this.getProperties(ret);
You can get a plain JS object (or hash) from an Ember.Object instance by calling getProperties() with a list of keys.
If you want it as a string, you can use JSON.stringify().
For example:
var obj = Ember.Object.create({firstName: 'Erik', lastName: 'Bryn', login: 'ebryn'}),
hash = obj.getProperties('firstName', 'lastName'), // => {firstName: 'Erik', lastName: 'Bryn'}
stringHash = JSON.stringify(hash); // => '{"firstName": "Erik", "lastName": "Bryn"}'
I have also been struggling with this. As Mirko says, if you pass the ember object to JSON.stringify you will get circular reference error. However if you store the object inside one property and use stringify on that object, it works, even nested subproperties.
var node = Ember.Object.create({
data: {
name: 'theName',
type: 'theType',
value: 'theValue'
}
});
console.log(JSON.stringify(node.get('data')));
However, this only works in Chrome, Safari and Firefox. In IE8 I get a stack overflow so this isn't a viable solution.
I have resorted to creating JSON schemas over my object models and written a recursive function to iterate over the objects using the properties in the schemas and then construct pure Javascript objects which I can then stringify and send to my server. I also use the schemas for validation so this solution works pretty well for me but if you have very large and dynamic data models this isn't possible. I'm also interested in simpler ways to accomplish this.
I modifed #pangratz solution slightly to make it handle nested hierarchies of Jsonables:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
App.io.emit('node', {node: node.toJSON()});
Or if you have an ID property and want to include it:
App.io.emit('node', {node: node.toJSON({includeId: true})});
Will this work for you?
var json = JSON.stringify( Ember.getMeta( App.node, 'values') );
The false is optional, but would be more performant if you do not intend to modify any of the properties, which is the case according to your question. This works for me, but I am wary that Ember.meta is a private method and may work differently or not even be available in future releases. (Although, it isn't immediately clear to me if Ember.getMeta() is private). You can view it in its latest source form here:
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/utils.js
The values property contains only 'normal' properties. You can collect any cached, computed properties from Ember.meta( App.node, false ).cached. So, provided you use jQuery with your build, you can easily merge these two objects like so:
$.extend( {}, Ember.getMeta(App.node, 'values'), Ember.getMeta(App.node, 'cache') );
Sadly, I haven't found a way to get sub-structures like array properties in this manner.
I've written an extensive article on how you can convert ember models into native objects or JSON which may help you or others :)
http://pixelchild.com.au/post/44614363941/how-to-convert-ember-objects-to-json
http://byronsalau.com/blog/convert-ember-objects-to-json/
I modified #Kevin-pauli solution to make it works with arrays as well:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I also made some further modification to get the best of both worlds. With the following version I check if the Jsonable object has a specific property that informs me on which of its properties should be serialized:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, base, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
if (!Ember.isNone(this.get('jsonProperties'))) {
// the object has a selective list of properties to inspect
base = this.getProperties(this.get('jsonProperties'));
} else {
// no list given: let's use all the properties
base = this;
}
for (var key in base) {
if (base.hasOwnProperty(key)) {
v = base[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I am using this little tweak and I am happy with it. I hope it'll help others as well!
Thanks to #pangratz and #Kevin-Pauli for their solution!
Here I take #leo, #pangratz and #kevin-pauli solution a little step further. Now it iterates not only with arrays but also through has many relationships, it doesn't check if a value has the type Array but it calls the isArray function defined in Ember's API.
Coffeescript
App.Jsonable = Em.Mixin.create
getJson: ->
jsonValue = (attr) ->
return attr.map(jsonValue) if Em.isArray(attr)
return attr.getJson() if App.Jsonable.detect(attr)
attr
base =
if Em.isNone(#get('jsonProperties'))
# no list given: let's use all the properties
this
else
# the object has a selective list of properties to inspect
#getProperties(#get('jsonProperties'))
hash = {}
for own key, value of base
continue if value is 'toString' or Em.typeOf(value) is 'function'
json[key] = jsonValue(value)
json
Javascript
var hasProp = {}.hasOwnProperty;
App.Jsonable = Em.Mixin.create({
getJson: function() {
var base, hash, hashValue, key, value;
jsonValue = function(attr) {
if (Em.isArray(attr)) {
return attr.map(jsonValue);
}
if (App.Jsonable.detect(attr)) {
return attr.getJson();
}
return attr;
};
base = Em.isNone(this.get('jsonProperties')) ? this : this.getProperties(this.get('jsonProperties'));
json = {};
for (key in base) {
if (!hasProp.call(base, key)) continue;
value = base[key];
if (value === 'toString' || Em.typeOf(value) === 'function') {
continue;
}
json[key] = jsonValue(value);
}
return json;
}
});
Ember Data Model's object counts with a toJSON method which optionally receives an plain object with includeId property used to convert an Ember Data Model into a JSON with the properties of the model.
https://api.emberjs.com/ember-data/2.10/classes/DS.Model/methods/toJSON?anchor=toJSON
You can use it as follows:
const objects = models.map((model) => model.toJSON({ includeId: true }));
Hope it helps. Enjoy!
I have:
fixed and simplified code
added circular reference prevention
added use of get of value
removed all of the default properties of an empty component
//Modified by Shimon Doodkin
//Based on answers of: #leo, #pangratz, #kevin-pauli, #Klaus
//http://stackoverflow.com/questions/8669340
App.Jsonable = Em.Mixin.create({
getJson : function (keysToSkip, visited) {
//getJson() called with no arguments,
// they are to pass on values during recursion.
if (!keysToSkip)
keysToSkip = Object.keys(Ember.Component.create());
if (!visited)
visited = [];
visited.push(this);
var getIsFunction;
var jsonValue = function (attr, key, obj) {
if (Em.isArray(attr))
return attr.map(jsonValue);
if (App.Jsonable.detect(attr))
return attr.getJson(keysToSkip, visited);
return getIsFunction?obj.get(key):attr;
};
var base;
if (!Em.isNone(this.get('jsonProperties')))
base = this.getProperties(this.get('jsonProperties'));
else
base = this;
getIsFunction=Em.typeOf(base.get) === 'function';
var json = {};
var hasProp = Object.prototype.hasOwnProperty;
for (var key in base) {
if (!hasProp.call(base, key) || keysToSkip.indexOf(key) != -1)
continue;
var value = base[key];
// there are usual circular references
// on keys: ownerView, controller, context === base
if ( value === base ||
value === 'toString' ||
Em.typeOf(value) === 'function')
continue;
// optional, works also without this,
// the rule above if value === base covers the usual case
if (visited.indexOf(value) != -1)
continue;
json[key] = jsonValue(value, key, base);
}
visited.pop();
return json;
}
});
/*
example:
DeliveryInfoInput = Ember.Object.extend(App.Jsonable,{
jsonProperties: ["title","value","name"], //Optionally specify properties for json
title:"",
value:"",
input:false,
textarea:false,
size:22,
rows:"",
name:"",
hint:""
})
*/
Ember.js appears to have a JSON library available. I hopped into a console (Firebug) on one the Todos example and the following worked for me:
hash = { test:4 }
JSON.stringify(hash)
So you should be able to just change your line to
App.io.emit('node', { node:JSON.stringify(hash) })