Alternate/better ways to initialize JavaScript object that needs multiple static values? - javascript

I have a JavaScript object with some static attribute values, dynamic attribute values and methods. Each time I need one of these objects, I will need 10 of them. Each of the 10 objects gets initialized by a dedicated object literal. That happens under 3 different contexts of a user doing something on a data entry form. User actions can cause the contexts to happen in any order, any number of times, but the same 10 objects will always be created in each context. By "same" I mean the static values for a "no_matl" object will be identical each time a "no_matl" object is created ... only a few dynamic attribute values (field value, previous value, date/time, context ID) are different for each context.
Is there a smarter way to do the initialization currently done with the const object literal? Originally I passed a bunch of params to the constructor and initialized the static attributes from those. The object literal approach seemed cleaner. Maybe there's a better way?
// object literals used to initialize a each of the 10
// different type objects.
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
const FIELD_CPCAT = { ... same attributes, different values ...};
const FIELD_HCN = { ... same attributes, different values ...};
// ... 7 more like this ...
// context 1
var no_matl = new MyField(FIELD_NOMATERIAL),
cpcap = new MyField(FIELD_CPCAT),
hcn = new MyField(FIELD_HCN) .... 7 more like this
// object definition
function MyField() {
if (arguments.length == 1 && typeof(arguments[0]) === 'object' ) {
this.DispName = arguments[0].DispName ;
this.DbName = arguments[0].DbName ;
// .... etc for rest of static attributes ...
}
}

Sounds like what you want is a copy of the original object that can change values without changing the original. Try this:
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
function getFreshCopy(original) {
return Object.assign({}, original);
}
var no_matl = getFreshCopy(FIELD_NOMATERIAL);
Using Object.assign({}, obj) will create a new copy that can be changed without the original values changing. no_matl can be adjusted and FIELD_NOMATERIAL remains in its original state.
Note that const means the variable cannot be assigned a new value. It does not mean that the contents of the object cannot be changed. That means the following is true:
const noChange = { a: 7 };
noChange.a = 8; // this is fine because 'a' is allowed to change
noChange = "hello"; // this gives TypeError: Assignment to constant variable.

Related

for loop gives only last value in javascript

I have created variable outside to get values from inside but I only get last value in both variables.
var categoryProductid;
var PPProducttitle;
for (var key in UpdatedCategory) {
const actual = UpdatedCategory[key]
var categoryProductid = actual.id;
var PPProducttitle = actual.title;
console.log(PPProducttitle)
console.log(categoryProductid)
}
console.log(PPProducttitle)
console.log(categoryProductid)
when I do console.logs() from inside and outside of for loop I get two different values.
Any help will be appreciated thank you!!!!!
I'm going to assume that UpdatedCategory is an object (if it's an array, please see my answer here for what to use instead of for-in to loop through it).
The basic problem with that code is that you're assigning to the same variable over and over. The reason it's the same variable is that var doesn't have block scope, so the var part of var categoryProductid = actual.id; is completely ignored (and similar for var PPProducttitle = actual.title;). Instead, you're reusing the variables you declared prior to the for loop.
A variable can only hold one value, so when you assign a new value to it, it no longer holds the old value.
If you want to hold multiple values with a variable, you can assign a container to it that can hold multiple values, such as an array, an object, a Map, or a Set.
You haven't said what end result you want, but here's an example that creates two arrays and fills them with the id and title of the products from UpdatedCategory:
// Again, I'm assuming `UpdatedCategory` is an object:
const UpdatedCategory = {
a: {
id: 1,
title: "a",
},
b: {
id: 2,
title: "b",
},
c: {
id: 3,
title: "c",
},
};
// `[]` creates a new empty array.
// We can use `const` to declare these because we never change their
// value (they only ever refer to a single array), but you could use
// `let` if you preferred. Don't use `var` in new code, it's deprecated.
// Note: I've renamed these slightly to:
// 1. Stick to standard naming conventions
// * Initial capitals are used primarily for constructor functions
// * Variables referring to arrays are generally plurals
// 2. Be consistent in capitalization
const categoryProductIds = [];
const ppProductTitles = [];
for (var key in UpdatedCategory) {
// Get this property value
const actual = UpdatedCategory[key];
// Push the `id` and `title` into the relevant arrays
categoryProductIds.push(actual.id);
ppProductTitles.push(actual.title);
}
// Show the contents of the arrays
console.log(ppProductTitles);
console.log(categoryProductIds);

Design pattern to check if a JavaScript object has changed

I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).

Generic 2D hash in JavaScript?

In other languages it is possible to create a generic 2D hash. I know creating 2d hashes is possible in javascript as well as explained here, but I can't seem to find a generic way to achieve this.
As an example of what I am looking for. In Ruby you can do this:
2dhash = Hash.new{|h, k| h[k] = Hash.new }
puts 2dhash["test"]["yes"]
#=> nil
2dhash[1][2] = "hello"
puts 2dhash[1][2]
#=> "hello"
Notice that I have not initialized the second level of hash, it happens automatically.
Is it possible to somehow achieve the same in javascript? Specifically, a way to make a 2d hash without initializing the first level of hash (or hard-coding it to be even more specific). The 2dhash will be used dynamically, so I have no clue what the first level will be.
Looks like a nice data structure excercise, let me try :D
function Hash() {
this.hash = {};
}
Hash.prototype.set = function(val) {
var paths = Array.prototype.slice.call(arguments, 1) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
if (!(hashed instanceof Hash)) {
hashed = this.hash[path] = new Hash()
}
Hash.prototype.set.apply(hashed, [val].concat(paths))
} else {
// last level
this.hash[path] = val
}
}
Hash.prototype.get = function() {
var paths = Array.prototype.slice.call(arguments, 0) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
return Hash.prototype.get.apply(hashed, paths)
} else {
// last level
return hashed
}
}
Now, let's see if it works:
var trytry = new Hash()
trytry.set('the value to store', 'key1', 'key2')
trytry.get('key1') // Hash{key2: 'the value to store'}
trytry.get('key1', 'key2') // 'the value to store'
Hooray it works!
It also works for even deeper levels:
trytry.set('the value to store', 'key1', 'key2','key3', 'key4')
trytry.get('key1', 'key2','key3') // Hash{key4: 'the value to store'}
However, a disadvantage of this approach is that you have to use instance methods get and set, rather than native object literal getter/setter.
It's still incomplete. For production environment, we need to do more, e.g. methods and properties like contains, size, etc.
If you initialize the first level of the hash with objects, then you can reference the second level without typeErrors, even if the data was not defined before.
Example:
var _2dhash = {a: {}, b: {}, c:{}}
//Note you cannot start variable names with numbers in js
_2dhash['a']['missingElement'];
// > undefined
It works because you're accessing undefined properties of defined objects. If you try to access through a missing top-level object, ie.
_2dhash['d']['whatever'];
You will get a TypeError, because _2dhash.d was not defined, and the second lookup fails, trying to read the 'whatever' property of undefined.

Accessing plugin prototype function using array square [] brackets

I am very new to JS and I was just going through the syntax of modal.js. Basically I have a small difficulty, a lot of classical JS plugins use the below skeleton code for the plugin:
var Modal = function(element , options){
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.isShown = null
this.$backdrop =
this.scrollbarWidth = 0
}
Modal.prototype.toggle = function (_relatedTarget) {
// do something
}
Modal.prototype.show = function (_relatedTarget) {
// do something
}
var data = new Modal(somthing , radnom);
// now if we assume that option is "show",
//the show function in Modal will be executed
// but my question is data is not an array, so how can we use
// [] square brackets to access the properties of Modal/data ??
data[option](_relatedtarget);
Now my question is about accessing the properties of a plugin, see how a function is being called using the following syntax:
data[option](_relatedtarget);
See my comment in the code. How can we access the properties of data using []; it's not an array, right?
[] are not just for arrays
You can use [] to access properties on an object too.
You can use
data["show"] to access the show method
OR
data.show which is the same thing
One advantage of the [] is that you can use a variable within the brackets
var option = "show";
data[option](something); // call the `show` method on `data`
If you know the method you want to call, using the . is much nicer looking in the code
data.show(something); // much quicker (to type), and prettier
JavaScript has arrays:
var anArray = [ 1, 2, 3, 4 ];
and associative arrays (also known as maps):
var anAssociativeArray = { first: "No. 1", second: 2, somethingElse: "Other" };
both of these data structures can be accessed via []:
anArray[3] // will get the element of the array in position 3
// (starting counting frrom 0).
anAssociativeArray['first'] // will get the element of the associative array with the
// key 'first'.
Associative arrays can also be accessed via the .key notation:
anAssociativeArray.first // will also get the property with key 'first'.
The . notation can be used if you know the key you want to access but if you want to dynamically select which key then you need to use the [] notation.
var whichOptionToPick = 'somethingElse';
var value = anAssociativeArray[ whichOptionToPick ]; // will get the value "Other".

How Do I Create Variable Object Keys Of Object Keys Of Objects?

Okay, so I have a search results array of objects where one of the object properties value (show value) matches a search. The structure of this array is as follows and may contain any number of different objects:
results = [
{
day: value,
time: value,
show: value,
sid: value,
network: value,
title: value,
ep: value,
link: value,
}
];
I am trying to consolidate all the results into one large object, merging any days or times that have the same value. However, I cannot simply look at each day or time value independently. For example, I need to retain 9:00 pm on Monday if there is a 9:00 pm on Tuesday as well.
To do this I am trying to create a new object structure like so:
for ( var i=0; i<results.length; i++ ) {
var uniqtime = results[i]["time"];
var uniqshow = results[i].show;
uniqresults[results[i].day] = {
uniqtime: {
uniqshow: {
sid: results[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
}
}
};
}
but obviously this won't work since the variable sub-object key names are treated as strings.
If I instead try to create the variable sub-objects/keys like so:
for ( var i=0; i<obj.length; i++ ) {
uniqresults[results[i].day] = {};
uniqresults[results[i].day][results[i]["time"]] = {};
uniqresults[results[i].day][results[i]["time"]][results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
I can indeed create the proper key names but I am forced to declare an empty object to define each key (uniqresults[obj[i].day] = {} & uniqresults[obj[i].day][obj[i]["time"]] = {}). If I don't declare like this it won't let me insert the other sub-keys/values that I need to. However, declaring like this doesn't allow me to merge my results arrays correctly since I am essentially emptying out the sub-key names each time I read a new result object!
Maybe I am making this more complicated than it should be. Maybe there is an easier way or a way underscore or jquery could simplify my task at hand. Regardless, I am quite desperate for a proper solution at the moment.
It seems like you could use a conditional check when redifining those objects.
var day, time;
for ( var i=0; i<obj.length; i++ ) {
// Instantiate the day reference if it doesn't exist
day = uniqresults[results[i].day] = uniqresults[results[i].day] || {};
// Instantiate the time reference if it doesn't exist
time = day[results[i].time] = day[results[i].time] || {};
time[results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
Cheers!
I don't know whether there's a neater solution to the wider problem, but the issue of overwriting the sub-objects each time can be solved by checking if they already exist before creating them.
A relatively compact and idiomatic way of doing this in JS is using the || operator, which unlike most languages returns the argument which evaluated to true, not simply a boolean true:
uniqresults[results[i].day] = uniqresults[results[i].day] || {};
The first time through, uniqresults[results[i].day] will be undefined, so evaluate to false, so {} will be assigned; subsequently, however, it will be an object, which evaluates to true, so will simply assign the variable to itself, leaving it unchanged.
Create an empty object only if the object's key does not exist:
if(!(results[i].day in uniqresults)){
uniqresults[results[i].day] = {};
}
And for sub keys so on.

Categories

Resources