Related
Given the following javascript object:
var commands = {
back:{
command: "b",
aliases: ["back","go back","backwards"],
action: function(){
return this.key; //I want this to return "back" (the prop name)
},
desc: "goes back"
}
}
How can i access the Property Name which is "back" from within the action()?
I think it should be pretty simple, but if it isn't something simple than I'll add more details.
NOTE: aliases[0] is holding the name by chance, and it is not promised to hold it in the future or in other commands.
EDIT:
Sometimes we get to complicated while we can solve the problem pretty fast.
In this case i can just go ahead and return the string "back"
I'll leave the question and accept the answer that solves my question if there is such a solution.
Returning the string as you mentioned is definitely the easiest way. But I could see cases where someone might want to be able to get similar functionality with a dynamically created object in which the keys are not known until run-time.
A solution that would work in that case is exposing the commands object to the sub objects, so they can look themselves up:
var commands = {
back:{
command: "b",
aliases: ["back","go back","backwards"],
action: function(){
var commandKeys = Object.keys(commands);
for(var i=0; i < commandKeys.length; i++){
if(commands[commandKeys[i]] === this){
return commandKeys[i];
}
}
},
desc: "goes back"
}
};
In this case it may also make more sense to share the function across all those action objects:
var commands = {
back:{
command: "b",
aliases: ["back","go back","backwards"],
action: getAction,
desc: "goes back"
},
forward: {
//...
action: getAction,
//...
}
};
function getAction() {
var commandKeys = Object.keys(commands);
for(var i=0; i < commandKeys.length; i++){
if(commands[commandKeys[i]] === this){
return commandKeys[i];
}
}
}
Unless you need to perform some specific logic for each sub object.
EDIT: To improve efficiency, we can make it where the getAction function is not executed every call and add a property that will store the name. That way the lookup only occurs the first time.
var commands = {
back:{
command: "b",
aliases: ["back","go back","backwards"],
action: getAction,
desc: "goes back"
},
forward: {
//...
action: getAction,
//...
}
};
// Only needs to getKey the first time called.
function getAction() {
if(!this.key) this.key = getKey(this);
return this.key;
}
function getKey(obj) {
var commandKeys = Object.keys(commands);
for(var i=0; i < commandKeys.length; i++){
if(commands[commandKeys[i]] === obj){
return commandKeys[i];
}
}
}
When you call action as the following:
commands.back.action();
the scope of action is back. Sadly, the creation of the object that gets assigned to commands.back does not know that this inside of action is called "back". From my understanding, this is done because we could assign the object assigned to commands.back to another object with another name. As in:
var foo = { f: function(){console.log(this) } };
var bar = foo;
bar.f();
Or closer to what you have...
var foo = {
bar: {
f:function(){console.log(this)}
}
};
var other = { another: (foo.bar) };
The only way I know of where the object knows the name of what it was created within are functions. So, we can create a temp function that has the name back that will create an object as desired.
var commands = {
back:(new function back(){
// I prefer to assign to a variable to assist with the readability as to what "this" is:)
var self = this;
self.command = "b";
self.aliases = ["back","go back","backwards"];
self.action = function(){
// Can leave as "this" or change to "self".
return this.key;
};
self.desc = "goes back";
self.key = self.prototype.constructor.name;
})
}
Simplest Solution
But at that point might as well just add a property that already has the name. I would recommend doing a property called key or name rather than placing the name directly into the action function to make it easier to have multiple places where the name is used. Also, allows there to be a single place to change the name within the object if need be.
var commands = {
back:{
command: "b",
aliases: ["back","go back","backwards"],
action: function(){
return this.key;
},
desc: "goes back",
key: "back"
}
}
EDIT: Added this edit as another way to do this, but I would still do the previous way. We can utilize Object.keys to get the name of the property since back is being added as an enumerable property of commands.
var i = 0,
commands = { back: {
key: (function(id){return function(){return Object.keys(commands)[id]}})(i++)
}}
Then can get the key by the following:
commands.back.key();
Or within the action function as:
this.key();
Can add key to back as a get which would look like:
var i = 0,
commands = { back: {
id: (i++),
get key() {return Object.keys(commands)[this.id]}
}}
This will allow you to access the property as commands.back.key and within the action function as this.key.
Can also pre-define everything then can do the following:
var i = 0, commands = { back: undefined };
commands.back = { key: Object.keys(commands)[i++] };
You can add and also advisable to add a toString method for your every object like this.
var commands = {
back:{
command: "b",
name : "back",
aliases: ["back","go back","backwards"],
action: function(){
return this.toString();
},
desc: "goes back",
toString : function(){
return this.name;
}
}
}
console.log(commands.back.action()); // back
console.log(commands.back.toString()); // back
What you are having here, is a nested object, held on the property of an object.
You can not get that property by hand - unless you are doing some strange metaprogramming stuff, such as getting the AST parent node and trying to determine the property the object is held etc. The easiest way, is to hold the property name using a string i.e.: "back".
In simple terms, it is like holding the object to a var
var obj = {/*....*/};
And you are trying to get the var name from within the object.
Remember though that in JavaScript, you can access an object property, using both string and index notation, so commands.back can also be called using commands['back']. If I am guessing right, you are trying to make a sort of dispatching, so this notation can be useful for you.
If you've got that object (literal object) you can't use this keyword. You have two solutions:
return commands.back.aliases[0]
Or otherwise,you can construct the object as a prototype object and not literal object:
var commands = function() {
this.back = function() {
this.command = "b";
this.aliases = ["back","go back","backwards"];
this.action = function() {
return this.aliases[0];
};
this.desc = "goes back";
};
};
And initialize like this
var instance = new commands();
instance.action(); // it returns "back" string
I'm working on a java challenge that reads
//In the function below you'll be passed a user object. Loop through the user object checking to make sure that each value is truthy. If it's not truthy, remove it from the object. Then return the object. hint: 'delete'.
function truthyObjLoop(user) {
//code here
I came up with....
var user = {};
user.name = {};
user.age = {};
if (user !== false)
{
return user;
} else {
delete user;
}
}
However whenever I try it it comes back with the error Function returned
{"name":{},"age":{}}
instead of
{"name":"ernest","age":50}
when passed
{"name":"ernest","age":50,"funky":false}....
Can anyone help me understand why this is happening or if I'm using the wrong symbols here? Thank you.
var user = {};
user.name = {};
user.age = {};
The user name and age should be the values, not Objects. curly braces mentions objects, you are wrong.try my following code please
var user = {};
user.name = "john";
user.age = 12;
Also read this tutorial please :
http://www.w3schools.com/json/
The text says "you'll be passed a user object". That means the user is being defined outside of the function, and passed in. Here's what you're looking for:
function truthyObjLoop(user) {
for (var k in user) {
if (!user[k]) delete user[k]
}
console.log(user);
}
You then should pass in a "user" with all sorts of different attributes to demonstrate how the test works:
var person = {
age: 29,
gender: "male",
pets: [
{
type: "cat",
name: "smelly"
}
],
children: 0,
married: false
};
truthyObjLoop(person);
Here's a jsfiddle:
https://jsfiddle.net/mckinleymedia/tb823pyp/
Notice it removes the attributes with a value of 'false' or '0'.
I have a constructor where name is a string and details should be an array containing animal type, age and colour.:
function animal(name, details) {
this.animal_name = name;
this.animal_details = details;
}
I want to create a new object from this prototype:
var myPet = new animal("Olly", " /* what goes here? */ ");
How do I declare the array in this case (is it like the usual array declaration) How do I use this when creating a new object myPet?
NB: This is my previous way of doing this without using an array:
function meal (starter, main, side, dessert, drink) {
this.starter = starter;
this.main = main;
this.side = side;
this.dessert = dessert;
this.drink = drink;
}
var myMeal = new meal("soup", "chicken", "garlic bread", "cake", "lemonade");
Arrays can be declared inline using square brackets:
var a = [1, 2, 3];
In your case, you can use this directly in your call to new animal:
var myPet = new animal("Olly", ["dog", 3, "brown"]);
An alternative approach, which if you want to store all the details together in this way is the one I'd take, is to pass in an object:
var myPet = new animal("Olly", {type: "dog", age: 3, colour: "brown"});
This means you can then access the details by name:
console.log(myPet.animal_details.type); //dog
You can declare an array like this:
var myPet = new animal("Olly", ['cat','white','big']);
//myPet.animal_details[0] === 'cat';
but I think an object will fit better with your solution
var myPet = new animal("Olly", {type: 'cat',color: 'white', size: 'big'});
//myPet.animal_details.type === 'cat';
//myPet.animal_details.color === 'white';
//myPet.animal_details.size === 'big';
You may find it easier to use object instead of an array because it would be easier to define default properties if they are not present. An object's structure is a little better for handling this type of data too, imo:
function Animal(details) {
this.name = details.name || 'Dave';
this.age = details.age || 10;
this.location = details.location || 'Dorset';
}
var cat = new Animal({ name: 'Simon', location: 'London' });
// Object { name: "Simon", age: 10, location: "London" }
PS. Always captitalise your constructor names.
Of course, if you're not worried about default values and just want the items specified in your object to be added to the instance, this is a simple method to use:
function Animal(details) {
for (var p in details) {
this[p] = details[p];
}
}
var cat = new Animal({ name: 'Simon', location: 'London' });
// Object { name: "Simon", location: "London" }
You can use this approach, to dynamically add details for every instance:
//Base Animal Class
function Animal(name,options) {
//Localise Class Variable
var me = this;
//Assign name to class variable
me.name = name;
//Assign details dynamically, depends on every instance
for(var index in options) {
me[index] = options[index];
}
}
//Create Animal Instance
var PetInstance = new Animal("Max",{
age:11,
kind:"Pudel"
});
console.log(PetInstance);
//Create Another Animal With Different Details
var PetInstance2 = new Animal("Foo",{
age:22,
kind:"Doberman"
});
console.log(PetInstance2);
See this JSFiddle Example:
http://jsfiddle.net/shako92/bretpu5c/
You could use instant-array notation: ['Furry', 'Sharp nails', 'Black']. Although you might wanna go with an object so you aren't depending on "which index did I use to put the skin-type?" So something like:
{
'skin':'Furry',
'paw':'Sharp nails',
'color':'Black'
}
(If this looks like JSON to you, it's because it is, JSON = JavaScript Object Notation)
Alternatively, you could leverage the built-in arguments array to extract (part of) the arguments given as an array and assign it:
function animal(name) {
this.animal_name = name;
this.animal_details = arguments.slice(1); // get a "slice" of the arguments array starting at index 1 (skipping the name argument)
}
var myPet = new animal("Olly", 'Furry', 'Sharp nails', 'Black');
you should try use the 'arguments' var inside your function, like this:
function a (){
for(arg in arguments){
console.log(arg);
}
}
a(1,2,3,4,5,6,7);
The result will be:
1
2
3 [...]
This way you let your code simple and useful.
Let's say I have a list of knockout bindings placed in a nested/namespaced object, resembling this:
var bindings = {
event: {
eventid: ko.observable(),
office: ko.observable(),
employee: {
name: ko.observable(),
group: ko.observable()
}
},
...
}
Now let's say there are a number of different sets of data that might be loaded into this - so one does an ajax query and gets a JSON result like this:
{
"defaults": {
"event": {
"eventid": 1234,
"employee": {
"name": "John Smith"
}
},
...
}
}
Note that not every binding has a default value - but all defaults are mapped to a binding. What I want to do is read the defaults into whatever knockout binding they correspond to.
There are definitely ways to traverse a nested object and read its values. Adding an extra argument to that example, I can keep track of the default's full key (eg event.employee.name). Where I'm getting stumped is taking the default's key and using it to target the associated knockout binding. Obviously, even if i have key = "event.employee.name", bindings.key doesn't reference what I want. I can only think of using eval(), and that leaves me with a bad taste in my mouth.
How would one go about using a key to reference the same location in a different object? Perhaps knockout provides a way to auto-map an object to its bindings, and I've just overlooked it? Any insight would be helpful. Thanks in advance!
I would suggest you have a look at the Knockout Mapping Plugin which will do most of what you want to do. If that doesn't workout then you can turn your bindings object into a series of constructor functions that accepts a data parameter. Something like
var Employee = function (data){
var self = this;
self.name = ko.observbale(data.name || '');
self.group = ko.observable(data.group);
};
var Event = function(data){
var self = this;
self.eventid = ko.observable(data.id || 0);
self.office = ko.observable(data.office || '');
self.employee = ko.observable(new Employee(data.employee));
};
var bindings = function(data){
var self = this;
self.event = ko.observable(new Event(data));
}
I'll be putting Nathan Fisher's solution into a future update, but I wanted to share the fix I found for now as well. Each time the defaults object recurses, I simply pass the corresponding bindings object instead of tracking the entire keypath.
var setToDefaults = function(data){
loopDefaults(data.defaults, bindings);
};
var loopDefaults = function(defaults, targ){
for(var d in defaults){
if(defaults.hasOwnProperty(d) && defaults[d]!==null){
if(typeof(defaults[d])=="object"){
loopDefaults(defaults[d], targ[d]);
}else{
// defaults[d] is a value - set to corresponding knockout binding
targ[d](defaults[d]);
}
}
}
};
I'm working on a wizard that uses javascript to change the page in an iframe. I'd like to create an object for each page of the wizard with references to a next & previous page.
Edit: The code posted below does not work. actionOne.nextAction is equal to {} after execution.
var actionOne = {};
var actionTwo = {};
actionOne = {
url: 'actionOneUrl.htm',
prevAction: null,
nextAction: actionTwo,
doDisplay: function(){
$('.label').html('Action One');
}
}
actionTwo = {
url: 'actionTwoUrl.htm',
prevAction: actionOne,
nextAction: null,
doDisplay: function(){
$('.label').html('Action Two');
}
}
The problem is that I can't figure out how to properly set up the next and previous references. There is likely a relatively simple solution, but I'm not sure what to search for. I am able to set the references after creating all the pages, but it feels very clunky to do so. Is there a way to do it while creating the objects?
For what you're trying to do, you're going to need to use an Object Oriented approach in JavaScript. This will allow you to assign a reference to new instances of your object. For example this works:
http://jsfiddle.net/Gq7vQ/
function Action(url, name){
this.url = url;
this.prevAction = null;
this.nextAction = null;
this.name = name;
}
Action.prototype.doDisplay = function(){
$(".label").html(this.name);
}
var actionOne = new Action('actionOneUrl.html', 'Action One');
var actionTwo = new Action('actionTwoUrl.html', 'Action Two');
actionOne.nextAction = actionTwo;
actionTwo.prevAction = actionOne;
console.log(actionOne.nextAction);
EDIT: So the OP asked for an implementation that automatically sets up these links between newly added actions. So here is a doubly-linked list implementation:
http://jsfiddle.net/wXC9B/1/
function ActionList(){
this.head = null;
this.tail = null;
}
ActionList.prototype.doDisplay = function(index){
var node = this.getNode(index);
console.log(node.name);
}
ActionList.prototype.getNode = function(index){
var current = this.head,
c = 0;
while(c < index && current !== null){
current = current.nextAction;
c++;
}
return current;
}
ActionList.prototype.add = function(url, name){
var node = {
url: url,
name: name,
nextAction: null,
prevAction: null
};
if(this.head === null){
this.head = node;
this.tail = node;
}
else{
this.tail.nextAction = node;
node.prevAction = this.tail;
//move tail to new node
this.tail = node;
}
}
var actionList = new ActionList();
//Each add automatically sets up links between the two
actionList.add('actionOneUrl.html', 'Action One');
actionList.add('actionTwoUrl.html', 'Action Two');
console.log(actionList.getNode(1));
actionList.doDisplay(1);
This is a very simplified example, but something like the following structure would prevent the need to manually reference your next/prev actions...let the application logic go find what to do based on the user's inputs.
UnderscoreJS's where function http://underscorejs.org/#where would be useful here
var dataFromServer = [
{id:"1", name: "First Page", nextId:"2"},
{id:"2", name: "Second Page", nextId:"3", prevId: "1"},
.....];
var actions = [];
var Action = function(data) {
this.doNextURL = function() {
//find action with id= data.nextId;
var actionToDo = _.where(actions, {id: data.nextId})[0];
window.location.href = actionToDo.url; //or something... a callback parameter, or returning the action rather than doing the 'ui logic' here would be better real world
}
}
for(var i = 0; i < dataFromServer.length; i+=1){
actions.push(new Action(dataFromServer[i]));
}
When you do
actionTwo = {
// ...
}
you are assigning a new value to actionTwo. It does not refer to the object anymore you assigned in var actionTwo = {}; and hence does not refer to the object you used in
actionOne = {
// ...
nextAction: actionTwo,
// ...
}
The easiest way would be to just initialise both objects and then assign them to the correct properties later on:
var actionOne = {
url: 'actionOneUrl.htm',
prevAction: null,
nextAction: null,
doDisplay: function(){
$('.label').html('Action One');
}
};
var actionTwo = {
url: 'actionTwoUrl.htm',
prevAction: null,
nextAction: null,
doDisplay: function(){
$('.label').html('Action Two');
}
};
actionOne.nextAction = actionTwo;
actionTwo.prevAction = actionOne;
If you want to do this for multiple actions, you should consider using constructor functions, so as joeltine shows in his answer.
To learn more about objects, have a look at MDN - Working with Objects.
When you use the {} object literal to define an object you are creating a new object instance with the Object constructor.
The following creates two new object instances from the Object constructor:
var actionOne = {}; // object instance 1
var actionTwo = {}; // object instance 2
This next part creates another new object instance (the third object instance) from the Object constructor and adds several properties. actionOne.nextAction points to the object instance of actionTwo (which doesn't have any of its own properties).
actionOne = {
url: 'actionOneUrl.htm',
prevAction: null,
nextAction: actionTwo,
doDisplay: function(){
$('.label').html('Action One');
}
} // object instance 3
So now when you declare actionTwo = {....} it creates a fourth object instance with a bunch of new properties. actionOne.prevAction still points to the second object instance you created (but are are no longer referencing with the the global variable actionTwo).
The key to remember is that the object literal {} creates new object instances with the Object constructor and the properties you create reference the object instance at the time they are declared.
Try this: don't create NEW objects for actionOne, actionTwo, instead leave your code as is - but assign to object properties of the already existing objects (which the first two lines create).
var actionOne, actionTwo;
actionOne = {
url: 'actionOneUrl.htm',
doDisplay: function(){
$('.label').html('Action One');
}
};
actionTwo = {
url: 'actionTwoUrl.htm',
doDisplay: function(){
$('.label').html('Action Two');
}
};
actionOne.prevAction = null; //could also be set above
actionOne.nextAction = actionTwo;
actionTwo.prevAction = actionOne;
actionTwo.nextAction = null; //could also be set above
Your question was a very good one - don't let anyone tell otherwise :) It is NOT obvious, even with quite a bit of JS background, that the object properties point to the objects the variables pointed to at the time the (literal) object creation statement was executed, rather than to the variable itself (in which case your example would have worked).
And please ignore the MVC pattern thing, even if it was even upvoted. Nothing wrong with MVC (sometimes), but this is a much, much MUCH more basic Javascript question, those pattern things come into play on a whole different (higher) level than your little interesting issue.
Some background: Deep inside the bowels of the Javascript execution engine variables that have an object as value are pointers (C/C++ background knowledge is good for understanding Javascript, because JS engines are written in it). So, when you assign the value of such a variable to an object property it will not point to the variable, but instead it will receive the pointer the variable has at value at the time. This means if the variable gets a new object assigned, pointing to another place in memory, the object property keeps pointing to the old object. If it pointed to the variable instead it would there find a pointer to the new object. As you can see, answering your question leads us deep inside how Javascript engines actually work on a very low level :)
All the other answers sure also solve your immediate issue, but I believe knowing this bit of background is much more fertile, in the end. Instead of trying to just give an answer that works it's sometimes worth investigating what's really going on... :)
Primitive types are stored in the variable directly, variables for objects are actually pointers. new String("foo") is an object (String), "foo" is a primitive type (string).
The exact same issue is important to keep in mind when calling functions in Javascript! It is call by value always, technically - but when the variable is a pointer to an object the value IS the pointer, which one must consider when assigning to variables the function gets as parameter.
Everyone seems to really be overcomplicating this.
function Wizard(o) {
return { url:o.url, doDisplay:function() { $('.label').html(o.html); } };
}
var wizards = [{url: 'actionOneUrl.html', html:'Action One'},
{url: 'actionTwoUrl.html', html:'Action Two'}].map(Wizard);
// wizards now contains an array of all your wizard objects
wizards.reduce(function(p,c) { c.prevAction = p; return p.nextAction = c; });
// prevAction and nextAction now point to the right places
No you can't, but you can keep the empty objects and just fill them:
var actionOne = {};
var actionTwo = {};
actionOne.url = 'actionOneUrl.htm';
actionOne.prevAction = null;
actionOne.nextAction = actionTwo;
...
But that's rather ugly. I would recommend filling in the links between them by using a function like this:
function chain() {
var i, prev, curr;
for(i = 0; i < arguments.length; i++) {
curr = arguments[i];
if(prev) {
prev.nextAction = curr;
curr.prevAction = prev;
}
else {
curr.prevAction = null;
}
prev = curr;
}
if(curr) curr.nextAction = null;
}
var actionOne, actionTwo;
actionOne = {
url: 'actionOneUrl.htm',
doDisplay: function(){
$('.label').html('Action One');
}
}
actionTwo = {
url: 'actionTwoUrl.htm',
doDisplay: function(){
$('.label').html('Action Two');
}
}
chain(actionOne, actionTwo);