Cross Communication Between Objects within an Object JavaScript - javascript

Forgive me if this has been answered before, but my searching has yielded no results. This is likely because I lack the terminology needed.
I have been working on a text based RPG as a side project and have been experimenting with closures in JavaScript. Below is a very basic example of what I am trying to accomplish:
app.js
var app = {};
This is where everything will be mounted to preserve namespace.
character.js
app.character = (function() {
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
return {
createCharacter: _createCharacter
};
})();
The character object is pretty straightforward, but what gets tricky is this next part.
items.js
app.items = {};
The items object will hold other objects; one for each item type because they all behave differently. The types will be: gear, consumable, quest. For simplicity I have only included gear.
gear.js
app.items.gear = (function() {
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
return {
createGear: _createGear
};
})();
So this object is two objects deep on the app object.
game.js
app.game = (function() {
var _player = {};
// closed functions in this area
function _giveWeapon() {
_player.weapon = this.item.gear.createGear();
}
function _newPlayer(name) {
_player = this.character.createCharacter(name);
_giveWeapon();
}
function getPlayer() {
return _player;
}
return {
getPlayer: _getPlayer,
newPlayer: _newPlayer
};
})();
This is the object that will pilot the game, i.e. call to other objects for functions as needed.
int.js
app.game.newPlayer('Ryan');
// should create { name: 'Ryan', stats: { health: 10, level: 1 }, weapon {} under game._player
This simply starts the game.
When ran it creates an error before anything even gets off the ground, because the game object can't see items.gear or character objects and returns them as undefined. What I am trying to get to is having them all able to communicate with one another inside the app object while remaining closures. Also, any advice on building single namespace complex JavaScript apps would be extremely helpful.

So I modified a few functional points, and a few aesthetic ones.
functional
- pass the app object into your game closure and use it instead of this
- change theApp.item.gear -> theApp.items.gear
aesthetic
- use function hoisting to create more skimmable code. Init your closure with the necessary variables, then return your api. After that you can declare your functions which only need to be referenced if detail is needed.
I can't help much with namespace principles since I avoided them in favor of bundlers and 'require' or more recently 'import' statements.
var app = {};
app.character = (function() {
return {
createCharacter: _createCharacter
};
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
})();
app.items = {};
app.items.gear = (function() {
return {
createGear: _createGear
};
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
})();
app.game = (function(theApp) {
var _player = {};
return {
getPlayer: _getPlayer,
createPlayer: _createPlayer
};
// closed functions in this area
function _giveWeapon() {
_player.weapon = theApp.items.gear.createGear();
}
function _createPlayer(name) {
_player = theApp.character.createCharacter(name);
_giveWeapon();
}
function _getPlayer() {
return _player;
}
})(app);
// should create { name: 'Ryan', stats: { health: 10, level: 1 }, weapon {} under game._player
app.game.createPlayer('Ryan');
var ryan = JSON.stringify(app.game.getPlayer(), null, 2);
document.getElementById('ryan').innerHTML = ryan;
<pre id="ryan"></pre>

Below code should get you going:
Basically, the reason why your code was not working earlier was that when you hit this line this.character.createCharacter(name);, the binding to this is with game but game object doesn't contain chatacter object.
Similar logic applis to the line this.item.gear.createGear();.
var app = {};
app.character = (function() {
// the character manipulation object
// this will be used to create character objects
function _createCharacter(name) {
return {
// Just an example
name: name,
stats: { health: 10, level: 1 },
weapon: {}
};
}
return {
createCharacter: _createCharacter
};
})();
app.items = {};
app.items.gear = (function() {
// the gear manipulation object
// this will be used to create gear objects
function _createGear() {
return {
name: 'Sword',
mods: { damage: 3, strength: 1 } // In reality it's random
};
}
return {
createGear: _createGear
};
})();
app.game = (function() {
var _player = {};
//this.character = character;
// closed functions in this area
function _giveWeapon() {
_player.weapon = app.items.gear.createGear();
}
function _newPlayer(name) {
//_player = this.character.createCharacter(name);
_player = app.character.createCharacter(name);
_giveWeapon();
}
function _getPlayer() {
return _player;
}
return {
getPlayer: _getPlayer,
newPlayer: _newPlayer
};
})();
app.game.newPlayer('Ryan');
console.log(app.game.getPlayer());

Related

Setting this inside the functions Javascript [duplicate]

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 3 months ago.
I have the next code:
const obj = {
name: 'Hi',
first: () => {
return this
},
other: {
name: 'last',
sec: function() {
this.c = '2'
return function() {
this.s = '3'
return this; // expect {s:3}
}
}
}
}
const t = obj.other.sec()
console.log(t())
In console.log i expect {s:3} but there is not that object. Question: Why there is not {s:3}? Or function() should have its own context
It's not there because this in JS is dynamic and depends on the scope.
Arrow functions don't have their own scope, so obj.first() will not return you obj but instead returns the global context (in a browser, usually window) or undefined when running in strict mode!
Functions via function do have a dedicated scope. However, when you call obj.other.sec(), the context of this in the sec function points to obj.other, not obj.
The function returned by obj.other.sec() does (or rather: can) create a new scope, since it's called without a context. But since it's also called without new, this points to the global context (see point 1).
Depending on what you want to achieve, the simplest solution is to replace this with the correct context. For example, if you want all function to run in the context of obj, just replace every this with obj:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.c = '2'
return function() {
obj.s = '3'
return obj;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj` with a `c` and an `s` property added
Or maybe you want varying context's:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
obj.other.s = '3'
return obj.other;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj.other` with a `c` and an `s` property added
And in case obj.other.sec() should return a new object that is not related to obj or obj.other, then... well... just return a new object:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
return { s: 3 };
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs a new object with an `s` property
You can read more about the dynamic nature of this on MDN

Set same function to multiple objects in javascript

I have a list of objects like this
var obj = [
{ name: "user", per: { pu: [{ end: "foo" }], ge: [{ end: "bar" }] } },
{ name: "user2", per: { pu: [{ end: "foo2" }], ge: [{ end: "bar2" }] } }
];
I want to add a new property cond which is a function to the objects in pu and ge, but when i do this, the function set to the only last object.
I loop through them then set them like so obj[0].per[itm][0].cond = func and that set to the last object only, but when i try to convert function toString() it set to all, JSON.stringfy() works as func same behavior.
Have I clone or set it in another way?
You may need to post fuller code as it's difficult to see where your error is. However, considering it from scratch, I think some nested loops to match the nested arrays will get you there.
for (let o of obj) {
for (let puObj of o.per.pu) {
puObj.cond = func;
}
for (let geObj of o.per.ge) {
geObj.cond = func;
}
}
use the below function...
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
then you can pass in your new function or anything you need to add like this:
obj[index].per = updateObject(obj[index].per, {cond : function() {}} ); //use a loop to add the function to all objects in array
//check your current obj
console.log(obj);

Infinite functions calls like 'string'.replace().replace()

I'm not really sure how to explain so I will start with the output.
I need to return this:
{
replies:
[
{ type: 'text', content: 'one' }
{ type: 'text', content: 'two' }
{ type: 'text', content: 'three' }
],
conversation: {
memory
}
}
And I wanted to return that through in-line statement.
So I would like to call something like:
reply.addText('one').addText('two').addText('three').addConversation(memory)
Note that addText can be called infinite times while addConversation can be called only one time. Also conversation is optional, in that case, if conversation is absent the conversation object should not appear in the output.
To create a custom structured object use a constructor, say Reply.
To call instance methods on the return value of method calls, return the instance object from the method.
Choices to prevent multiple additions of conversation objects include throwing an error (as below) or perhaps logging a warning and simply not add additional objects after a first call to addConversation.
Write the code to implement the requirements.
For example using vanilla javascript:
function Reply() {
this.replies = [];
}
Reply.prototype.addText = function( content) {
this.replies.push( {type: "text", content: content});
return this;
}
Reply.prototype.addConversation = function( value) {
if( this.conversation) {
//throw new Error("Only one conversation allowed");
}
this.conversation = {conversation: value};
return this;
};
Reply.prototype.conversation = null;
// demo
var reply = new Reply();
reply.addText( "one").addText("two").addConversation("memory?");
console.log( JSON.stringify( reply, undefined," "));
(The console.log uses JSON stringify to avoid listing inherited methods)
A possible implementation is to create a builder as follows:
function create() {
const replies = []; // store all replies in this array
let conversation; // store the memory here
let hasAddConversationBeenCalled = false; // a state to check if addConversation was ever called
this.addText = function(content) {
// add a new reply to the array
replies.push({
type: 'text',
content
});
return this; // return the builder
};
this.addConversation = function(memory) {
if (!hasAddConversationBeenCalled) { // check if this was called before
// if not set the memory
conversation = {
memory
};
hasAddConversationBeenCalled = true; // set that the memory has been set
}
return this; // return the builder
}
this.build = function() {
const reply = {
replies
};
if (conversation) { // only if converstation was set
reply.conversation = conversation; // add it to the final reply object
}
return reply; // finally return the built respnse
}
return this; // return the new builder
}
You can then use it as follows:
const builder = create();
const reply = builder.addText('one').addText('two').addText('three').addConversation({}).build();
Here is a link to a codepen to play around with.
If you specifically want to add assemble this via multiple function calls, then the builder pattern is your best bet, as vader said in their comment.
However, if the goal is to simply create shorthand for concisely building these objects, it can be done using a function that takes the list of text as an array.
const buildObject = (textArray, memory) => {
return Object.assign(
{},
{
replies: textArray.map(x => {
return {
type: 'text',
value: x
}
})
},
memory ? {conversation: memory} : null
)
}
var memory = { };
//with memory
console.log(buildObject(['one', 'two', 'three'], memory ))
//without memory
console.log(buildObject(['one', 'two', 'three']));
Fiddle example: http://jsfiddle.net/ucxkd4g3/

Build a JSON object from absolute filepaths

I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}

Using constants as indices for JavaScript associative arrays

I'm looking to create an associative array in JavaScript, but use constants defined as part of the class as indices.
The reason I want this is so that users of the class can use the constants (which define events) to trigger actions.
Some code to illustrate:
STATE_NORMAL = 0;
STATE_NEW_TASK_ADDED = 0;
this.curr_state = STATE_NEW_TASK_ADDED;
this.state_machine = {
/* Prototype:
STATE_NAME: {
EVENT_NAME: {
"next_state": new_state_name,
"action": func
}
}
*/
STATE_NEW_TASK_ADDED : { // I'd like this to be a constant
this.EVENT_NEW_TASK_ADDED_AJAX : {
"next_state": STATE_NEW_TASK_ADDED,
"action" : function() {console.log("new task added");},
}
}
}
// Public data members.
// These define the various events that can happen.
this.EVENT_NEW_TASK_ADDED_AJAX = 0;
this.EVENT_NEW_TASK_ADDED_AJAX = 1;
I'm having trouble getting this to work. I'm not too great with JavaScript, but it looks like no matter what I do, the array gets defined with strings and not constants. Is there a way to force the array to use the constants?
In ECMAScript 6 you can use computed values for object keys:
var CONSTANT_A = 0, CONSTANT_B = 1
var state_machine = {
[CONSTANT_A]: function () {
return 'a'
},
[CONSTANT_B]: function () {
return 'b'
}
};
console.log(state_machine)
This does not work in Internet Explorer 11 nor in Safari browsers:
https://kangax.github.io/compat-table/es6/#test-object_literal_extensions_computed_properties
See Kristian's answer re: ECMAScript 6/modern JavaScript, which has new syntax to make this possible.
The below is my original answer, from the pre-modern age.
The problem here, actually, is that you can't use a value for the key part when you're defining an object literally.
That is to say, this uses the constant values as expected:
var CONSTANT_A = 0, CONSTANT_B = 1;
var state_machine = {};
state_machine[CONSTANT_A] = "A";
state_machine[CONSTANT_B] = "B";
console.log(state_machine[0]); // => A
console.log(state_machine[1]); // => B
But this won't work as expected, instead using the string CONSTANT_A as key:
var CONSTANT_A = 0, CONSTANT_B = 1;
var state_machine = {
CONSTANT_A: "A",
CONSTANT_B: "B",
};
console.log(state_machine[0]); // => undefined
console.log(state_machine["CONSTANT_A"]); // => A
console.log(state_machine.CONSTANT_A); // => A
JavaScript has a shorthand to define object literals where you can omit the double-quotes around keys. Expressions can't be used, so CONSTANT_A won't be evaluated.
Let's say you have the following constants:
const COMPANIES = "companies";
const BRANCHES = "branches";
const QUEUES = "queues";
const LOGOUT = "logout";
If you declare the dictionary this way:
var itemsToState = {
COMPANIES: true,
BRANCHES: false,
QUEUES: false,
LOGOUT: false,
}
// You will get:
// { COMPANIES: true, BRANCHES: false, QUEUES: false, LOGOUT: false }
Note the keys are uppercase ^ because it is not using the constant's value.
If you want to use the constant's value as key, you need to do this:
var itemsToState = {
[COMPANIES]: true,
[BRANCHES]: false,
[QUEUES]: false,
[LOGOUT]: false,
}
// You will get:
// { companies: true, branches: false, queues: false, logout: false }
Note the keys are lowercase ^ because it is using the constant's value.

Categories

Resources