Can I use getter/setters to do a similar task to an observer?
So for example if I assign an instance of a getter/setter to multiple objects, these will all be references, so if either object causes the getter to change the same code would run right?
I have tried the following code:
var obj = {
value: null,
get val() {
return this.value;
},
set val(x) {
console.log('set');
if (this.value !== x) {
console.log('value has been changed, do stuff!');
}
this.value = x;
}
}
var one = {
name: 'object 1',
value: obj /* Reference object */
}
var two = {
name: 'object 2',
value: obj /* Reference object */
}
var three = {
name: 'object 3',
value: obj /* Reference object */
}
Then run one.value.value = 2 which should fire the console log. However I just get 2 output in the console and no console.log.
Edit
Just saw where I was going wrong, I should be doing one.value.val = 2, this is starting to work, hang on.
Edit 2
Not too sure if this will function the way i'm expecting. I'm going to try and breakdown what i'm trying to do.
I have got an array of objects as follows:
var images = [
{ index: 0, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 1, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 2, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 3, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 4, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 5, object: HTMLElement, src: String, active: Object, alt: String },
{ index: 6, object: HTMLElement, src: String, active: Object, alt: String }
];
This object will get duplicated a couple of times throughout my script however the active state needs to remain the same throughout all instances.
How i'm duplicating the object:
var newList = [];
for (var i = 0; i < _.images.length; i++) {
// Create new instances of the images
var img = document.createElement('img');
img.src = images[i].object.src;
img.classList.add('responsive-img');
img.alt = images[i].alt;
var span = document.createElement('span');
span.appendChild(img);
if (i === current) img.parentNode.classList.add('active');
var newImage = {
object: img,
index: i,
src: images[i].src,
active: images[i].active, // Use reference to old list
alt: images[i].alt
};
newList.push(newImage);
// Add each image to the gallery DOM
_.gallery.main.appendChild(span);
}
Basically now what I need to happen, is that if the active value is changed in either reference, then code should execute and add/remove a class from the object within that instance.
Does this make sense?
So if the following is run
images[0].active.val = 1
Then newList[0].object.classList.Add('active'); and images[0].object.classList.Add('active'); executes.
There is more code that should execute however lets take it a step at a time. I was using a Polyfill for Observer before however it's too heavy weight for what i want to do and over the top, also having issues with it on Internet Explorer.
I think the best way to do this is maybe some kind of callback from the setter so I could run unique code for each instance that this object is within?
It feels a bit hackish but adding a getter to active to set the object you are referring should work :
var active = {
_value : null,
object : null,
set value(v) {
_value = v;
this.object.style.display = v;
}
}
var images = [
{img : document.querySelector('.div1'), _active : active, get active () {this._active.object = this.img; return this._active}
},
{img : document.querySelector('.div2'), _active : active, get active () {this._active.object = this.img; return this._active}
}
]
images[0].active.value = 'none';
images[1].active.value = 'block';
<div class = 'div1'>div 1</div>
<div class = 'div2' style = 'display:none'>div 2</div>
Related
This question already has answers here:
Javascript object members that are prototyped as arrays become shared by all class instances
(3 answers)
Closed 7 years ago.
I'm trying to create a builder pattern in JavaScript. But I'm wondering why if I call Object.create twice I got the same list as before.
Here's my code.
var filterBuilder = {
filters: [],
addFilter: function(options) {
var filter = {
'type': 'selector',
'dimension': options.dimension,
'value': options.value
}
this.filters.push(filter);
return this;
},
build: function() {
return this.filters;
}
};
If I run Object.create(filterBuilder).build() I get [] which is good. But when I start adding filter
Object.create(filterBuilder).addFilter({dimension: '1', value: 'v'}).build();
I get one filter which is good
But then if I do
Object.create(filterBuilder).addFilter({dimension: '1', value: 'v'}).addFilter({dimension: '1', value: 'v'}).build();
I will get three filters, the first one is from the previous call. Isn't Object.create supposed to create a new object for me?
Prototype properties are shared, so the filters array is the same for both objects you created. If you want each object to have its own filters you have to add it as an own property (a property that is owned by the object, not the prototype), ie:
var filterBuilder = {
addFilter: function(options) {
var filter = {
'type': 'selector',
'dimension': options.dimension,
'value': options.value
}
this.filters.push(filter);
return this;
},
build: function() {
return this.filters;
}
};
var a = Object.create(filterBuilder)
a.filters = [];
a.addFilter({dimension: '1', value: 'v'}).build();
var b = Object.create(filterBuilder)
b.filters = []
b.addFilter({dimension: '1', value: 'v'}).addFilter({dimension: '1', value: 'v'}).build();
console.log(a.filters.length, b.filters.length); // 1 2
You can do this with new as well:
function FilterContainer() {
this.filters = [];
}
FilterContainer.prototype.addFilter = function() {
...
};
FilterContainer.prototype.build = function() {
...
};
// Has its own `filters` array
new FilterContainer().addFilter(...).build();
When you call Object.create(filterBuilder) several times, you get two objects which hold references to the same filters array.
var b1 = Object.create(filterBuilder);
var b2 = Object.create(filterBuilder);
// now b1.filters is the _same_ object as b2.filters,
// so calling
b1.filters.push(...);
// will inevitably modify b2.filters as well.
Your best choice here is using classical functions and prototypes
function FilterBuilder() {
this.filters = [];
}
FilterBuilder.prototype.addFilter = function() { };
var builder = new FilterBuilder();
builder.addFilter();
Object.create() takes an optional second argument which can define properties that are not inherited from the prototype, so you can (re-)define the filters property of newly created object like this:
Object.create(filterBuilder, {
filters : { writable : true, enumerable: true, value : [] }
})
https://jsfiddle.net/1m7xx4ge/2/
// full code
var filterBuilder = {
filters: [],
addFilter: function(options) {
var filter = {
'type': 'selector',
'dimension': options.dimension,
'value': options.value
}
this.filters.push(filter);
return this;
},
build: function() {
return this.filters;
}
};
Object.create(filterBuilder, {
filters : { writable : true, enumerable : true, value : [] }
})
.addFilter({dimension: '1', value: 'v'}).build();
Object.create(filterBuilder, {
filters : { writable : true, enumerable : true, value : [] }
})
.addFilter({dimension: '1', value: 'v'})
.addFilter({dimension: '1', value: 'v'}).build();
I have a javascript code as below :
p.onRowSelectExtraListener = function(e)
{
var that = e.data.that;
that.actions.selectedRows = that.table.getSelectedRows();
var selected = [];
var disableoption = false;
$('#checkboxes input:checked').each(function() {
selected.push($(this).attr('statusid'));
});
$.each(selected, function(index, value) {
if (value == 27)
{
disableoption = true;
return false;
}
console.log(disableoption);
if(disableoption === true)
{
console.log("calling another function");
//call p.getRowActions and disable the 3rd menu item/button.option.
}
});
};
p.getRowActions = function()
{
var that = this;
var addToGroupOptions = this.data.group.slice(0);
console.log("in get row actions");
if (this.canUnlock == 0)
{
console.log("In Super-IF loop");
}
addToGroupOptions.unshift({id: 'new', name: 'new group'});
//TODO translate
return [
{
option: {id: '4', name: 'add to group'},
options: addToGroupOptions,
type: 'ajax',
func: function(selectedRows, group)
{
that.onAddToGroup(selectedRows, group);
}
},
{
option: {id: '2', name: 'change rights issue'},
type: 'ajax',
func: function(selectedRows, rights) {
that.onChangeRights(selectedRows, rights);
}
},
{
option: {id: '1', name: 'change status'},
options: this.data.status,
type: 'ajax',
func: function(selectedRows, status) {
that.onChangeStatus(selectedRows, status);
}
},
{
option: {id: '3', name: 'edit'},
type: 'ajax',
func: function(selectedRows, edit) {
that.onChangeEdit(selectedRows, edit);
}
}
];
};
Now out of these two functions, the second one is called/executed first when I load my page.
Hence I can see console.log("in get row actions"); printed as well as the 4 options under return[] statement present as buttons in my application.
Now my requirement is :
To disable the 3rd option(the option with id:1 name:change status whenever disableoption = true will be set in the 1st function.
How is that possible ?
How can I access and remove the property defined in diffrent function ?
Thanks in advance.
p.getRowActions returns an array, access the objects within the array and change the property of the object you want, once the object is modified you can reassemble the array with the new object (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) and then return this corrected array to the component that consumes its data to add the HTML objects to the DOM.
Another option is to insert a callback function at the end of the function that receives the array returned from p.getRowActions so you can edit the object in the DOM after it is created/rendered.
UPDATE (pseudo-code to illustrate solution):
function some_function_that_consumes_getRowActions_that_is_somewhere_else_that_I_dont_know() {
var arrayOfHTMLOptions = p.getRowActions();
//inserting options to DOM...
var theOptionYouWant = document.getElementById('1');
theOptionYouWant.disabled = true;
}
Another option (that might be more elegant depending on the potential upcoming use cases that could require a new change on this function) is to override the method of object "p" through inheritance and build your own p.getRowActions returning the options array you want, then you must update the function that receives this array so it can point to the new "p child object", by doing that you will have a more flexible way to keep changing p.getRowActions.
Here's some cool reading on Object Oriented Programming with Javascript:
http://marcelorjava.wordpress.com/2014/06/07/object-oriented-programming-with-java-and-javascript/
I have an array of objects that can be of any length and any depth. I need to be able to find an object by its id and then modify that object within the array. Is there an efficient way to do this with either lodash or pure js?
I thought I could create an array of indexes that led to the object but constructing the expression to access the object with these indexes seems overly complex / unnecessary
edit1; thanks for all yours replies I will try and be more specific. i am currently finding the location of the object I am trying to modify like so. parents is an array of ids for each parent the target object has. ancestors might be a better name for this array. costCenters is the array of objects that contains the object I want to modify. this function recurses and returns an array of indexes that lead to the object I want to modify
var findAncestorsIdxs = function(parents, costCenters, startingIdx, parentsIdxs) {
var idx = startingIdx ? startingIdx : 0;
var pidx = parentsIdxs ? parentsIdxs : [];
_.each(costCenters, function(cc, ccIdx) {
if(cc.id === parents[idx]) {
console.log(pidx);
idx = idx + 1;
pidx.push(ccIdx);
console.log(pidx);
pidx = findAncestorsIdx(parents, costCenters[ccIdx].children, idx, pidx);
}
});
return pidx;
};
Now with this array of indexes how do I target and modify the exact object I want? I have tried this where ancestors is the array of indexes, costCenters is the array with the object to be modified and parent is the new value to be assigned to the target object
var setParentThroughAncestors = function(ancestors, costCenters, parent) {
var ccs = costCenters;
var depth = ancestors.length;
var ancestor = costCenters[ancestors[0]];
for(i = 1; i < depth; i++) {
ancestor = ancestor.children[ancestors[i]];
}
ancestor = parent;
console.log(ccs);
return ccs;
};
this is obviously just returning the unmodified costCenters array so the only other way I can see to target that object is to construct the expression like myObjects[idx1].children[2].grandchildren[3].ggranchildren[4].something = newValue. is that the only way? if so what is the best way to do that?
You can use JSON.stringify for this. It provides a callback for each visited key/value pair (at any depth), with the ability to skip or replace.
The function below returns a function which searches for objects with the specified ID and invokes the specified transform callback on them:
function scan(id, transform) {
return function(obj) {
return JSON.parse(JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null && value.id === id) {
return transform(value);
} else {
return value;
}
}));
}
If as the problem is stated, you have an array of objects, and a parallel array of ids in each object whose containing objects are to be modified, and an array of transformation functions, then it's just a matter of wrapping the above as
for (i = 0; i < objects.length; i++) {
scan(ids[i], transforms[i])(objects[i]);
}
Due to restrictions on JSON.stringify, this approach will fail if there are circular references in the object, and omit functions, regexps, and symbol-keyed properties if you care.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter for more info.
As Felix Kling said, you can iterate recursively over all objects.
// Overly-complex array
var myArray = {
keyOne: {},
keyTwo: {
myId: {a: '3'}
}
};
var searchId = 'myId', // Your search key
foundValue, // Populated with the searched object
found = false; // Internal flag for iterate()
// Recursive function searching through array
function iterate(haystack) {
if (typeof haystack !== 'object' || haystack === null) return; // type-safety
if (typeof haystack[searchId] !== 'undefined') {
found = true;
foundValue = haystack[searchId];
return;
} else {
for (var i in haystack) {
// avoid circular reference infinite loop & skip inherited properties
if (haystack===haystack[i] || !haystack.hasOwnProperty(i)) continue;
iterate(haystack[i]);
if (found === true) return;
}
}
}
// USAGE / RESULT
iterate(myArray);
console.log(foundValue); // {a: '3'}
foundValue.b = 4; // Updating foundValue also updates myArray
console.log(myArray.keyTwo.myId); // {a: '3', b: 4}
All JS object assignations are passed as reference in JS. See this for a complete tutorial on objects :)
Edit: Thanks #torazaburo for suggestions for a better code.
If each object has property with the same name that stores other nested objects, you can use: https://github.com/dominik791/obj-traverse
findAndModifyFirst() method should solve your problem. The first parameter is a root object, not array, so you should create it at first:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
Then use findAndModifyFirst() method:
findAndModifyFirst(rootObj, 'children', { id: 1 }, replacementObject)
replacementObject is whatever object that should replace the object that has id equal to 1.
You can try it using demo app:
https://dominik791.github.io/obj-traverse-demo/
Here's an example that extensively uses lodash. It enables you to transform a deeply nested value based on its key or its value.
const _ = require("lodash")
const flattenKeys = (obj, path = []) => (!_.isObject(obj) ? { [path.join('.')]: obj } : _.reduce(obj, (cum, next, key) => _.merge(cum, flattenKeys(next, [...path, key])), {}));
const registrations = [{
key: "123",
responses:
{
category: 'first',
},
}]
function jsonTransform (json, conditionFn, modifyFn) {
// transform { responses: { category: 'first' } } to { 'responses.category': 'first' }
const flattenedKeys = Object.keys(flattenKeys(json));
// Easily iterate over the flat json
for(let i = 0; i < flattenedKeys.length; i++) {
const key = flattenedKeys[i];
const value = _.get(json, key)
// Did the condition match the one we passed?
if(conditionFn(key, value)) {
// Replace the value to the new one
_.set(json, key, modifyFn(key, value))
}
}
return json
}
// Let's transform all 'first' values to 'FIRST'
const modifiedCategory = jsonTransform(registrations, (key, value) => value === "first", (key, value) => value = value.toUpperCase())
console.log('modifiedCategory --', modifiedCategory)
// Outputs: modifiedCategory -- [ { key: '123', responses: { category: 'FIRST' } } ]
I needed to modify deeply nested objects too, and found no acceptable tool for that purpose. Then I've made this and pushed it to npm.
https://www.npmjs.com/package/find-and
This small [TypeScript-friendly] lib can help with modifying nested objects in a lodash manner. E.g.,
var findAnd = require("find-and");
const data = {
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Three',
},
],
};
findAnd.changeProps(data, { id: 2 }, { name: 'Foo' });
outputs
{
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Foo',
},
],
}
https://runkit.com/embed/bn2hpyfex60e
Hope this could help someone else.
I wrote this code recently to do exactly this, as my backend is rails and wants keys like:
first_name
and my front end is react, so keys are like:
firstName
And these keys are almost always deeply nested:
user: {
firstName: "Bob",
lastName: "Smith",
email: "bob#email.com"
}
Becomes:
user: {
first_name: "Bob",
last_name: "Smith",
email: "bob#email.com"
}
Here is the code
function snakeCase(camelCase) {
return camelCase.replace(/([A-Z])/g, "_$1").toLowerCase()
}
export function snakeCasedObj(obj) {
return Object.keys(obj).reduce(
(acc, key) => ({
...acc,
[snakeCase(key)]: typeof obj[key] === "object" ? snakeCasedObj(obj[key]) : obj[key],
}), {},
);
}
Feel free to change the transform to whatever makes sense for you!
I have a wrapper function where I use a variable dataObject. I have an action to trigger some outside functions within the wrapper function.
function wrapper() {
var dataObject;
var jsonPath = "dataObject[0]['Set1'][0]['Attribute1']";
eval('outsideFunction(dataObject, jsonPath)');
}
function outsideFunction(dataObject, jsonPath) {
dataObject[0]['Set1'][0]['Attribute1'] = 'asde'; //This sets the value to dataObject in the wapper
var attrVal = '123';
eval("jsonPath = attrVal"); //This doesn't set value to dataObject in the wrapper but in the local dataObject
}
Why is there a difference in the action of direct assign and assigning using eval?
According to your structure of data[0]['Set1'][0]['Attribute1'], which can be written as data[0].Set1[0].Attribute1, here is code, but I think you don't quite understand how many sets you were asking for.
var wrapper, outsideFunction;
wrapper = function(){
someOb = {};
var data = [
{
Set1: [
{
Attribute1: null, // we will change null to 'asdf' below
},
],
},
];
outsideFunction(data, someOb);
console.log( someOb.newProp, someOb.dumb );
// outputs 'hehehehe', undefined
};
outsideFunction = function(data, blah) {
data[0].Set1[0].Attribute1 = 'asdf';
//blah is a reference to someOb
// we can change a part of blah and it will look at the reference and change someOb
blah.newProp = 'hehehehe';
// but if we do `blah =`, then we reference a new object
// This won't affect someOb, blah will just be a different object talking about something else
blah = { dumb: false };
};
So, like I was saying, your data object is a numbered set, ( you have [0] ), then a named set (Set1), then a numbered set ( [0] ), I don't think you mean to nest so much.
numberedSet = [
{
name: 'dan',
likes: [
'coding', 'girls', 'food',
],
},
{
name: 'Sreekesh',
},
]
namedSet = {
dan: {
isPerson: true,
likes: [
'coding', 'girls', 'food',
],
},
Sreekesh: {
isPerson: true,
askedQuestion: function(){
return true;
},
}
};
numberedSet[0].name == dan; // true
numberedSet[0].likes[1] == 'girls'; // true
namedSet.dan.isPerson == true; // true
namedSet.Sreekesh.askedQuestion(); // true
I have an ObjectManager, which holds a reference to all objects that are created. The problem is that the ObjectManager is not referencing the object that was created, but instead it seems to be creating a new instance of it. Any help would be appreciated. Thanks!
var Fieldset = function (options) {
var fieldset = ($.extend(true, {
id: '',//Let's assume this has been overridden with 'MyFieldset' via the options param.
title: '',
accordion: '',
fields: [],
hidden: false,
Show: function () { $('#' + this.id).show() },
Hide: function () { $('#' + this.id).hide() }
}, options));
if (fieldset.id != null && fieldset.id != '')
ObjectManager.fieldsets[fieldset.id] = fieldset;//Save a reference to this object in the ObjectManager, so I can call ObjectManager.GetFieldset('MyFieldset'). A reference is only saved if an id is provided.
log(ObjectManager.GetFieldset(fieldset.id) == fieldset);//true
return fieldset;
}
Edit:
Sorry, I thought it was clear what I wanted this to do. There is nothing special about ObjectManger. It just has a property and Get method for each of my objects. For simplicity I only included the fieldsets property and Getter. I hope this clears up my intentions.
var ObjectManager =
{
fieldsets: {},
GetFieldset: function (id) { return this.fieldsets[id]; }
};
Edit2:
After some testing, I found something odd... Hopefully someone can explain to me why this is happening.
var myFieldset = new Fieldset({ id: 'MyFieldset' });
log(myFieldset == ObjectManager.GetFieldset('MyFieldset'));//I just found that it is true in this case...
//... However, this is not the normal way I create Fieldsets... This is:
var editForm = new Form({
dataStore: function () { return ClientsDS; },
id: 'ClientEditForm',
fieldSets: [
new Fieldset({
id: 'ClientDetailsFieldSet',
title: 'Details',
fields: [
new Field({ id: 'ClientID', name: 'ID', property: 'ID', fieldType: 'hidden', value: '0' })
]
})
]
});
log(editForm.fieldSets[0] == ObjectManager.GetFieldset('ClientDetailsFieldSet'));//false
On EDIT2:
Your objects are not equal, because they are not the same. The equality operator does not say these two objects have the same key/value pairs, they are equal when they are the same object.
For instance,
var a = b = {a: 1, b:2};
//This is b = {a: 1, b: 2}; a = b; In case you were wondering
a === b //true
var a = {a: 1, b: 2},
b = {a: 1, b: 2};
a === b //false
Hmm, your Fieldset constructor is returning an object. Perhaps try calling it as Fieldset({...}) instead of new Fieldset({...})?
I am assuming that your Form class looks something like your Fieldset class, i.e. that it $.extends (makes a deep copy) the options you give it with its internal "prototype". The object returned is the extended prototype not the options extended with the prototype object. Try changing the order of your $.extend arguments (put options second and the internal "prototype" third) and see if that changes anything. Alternatively, post your Form class :-)