Accessing jQuery plugin properties - javascript

I'm creating a jQuery plugin and I'm calling it like the following
$('#element').helpify({
msg : 'This is my message',
closeButton: {
text: "Close this",
colour: "red"
}
});
Then in the plugin I set up some defaults and use extend to create an object with the parameters like this:
var settings = $.extend({
title: 'Default Title',
msg : 'Default message',
closeButton: {
text: "Close",
colour: "red",
btnClass: "pull-right"
}
}, options);
I know I can then access the title by writing settings.title, what I'm unsure of is how to access the properties within closeButton.
Any help much appreciated, thanks!
EDIT
I can access the properties when I pass some in as in the first code block above, however in the second code block which is setting up some defaults and then using the passed in 'options' object should there be some properties supplied - if I don't pass any properties in and rely on the defaults, the ones within closeButton do not work, but the others do, i.e. msg
EDIT 2
Here is a JS fiddle showing what I mean - http://jsfiddle.net/U5W5G/1/

Simply use settings.closeButton.text
JavaScript properties can be accessed using one of two methods:
Dot notation
The most common and basic way of accessing properties - however illegal variable names (except for reserved words - they are allowed as property names under ES5) will not work.
foo.bar; // OK
foo.class; // only in ES5 and up
foo.&^&%^&#&(#&&#; // SyntaxError: yeah, it doesn't work
Square bracket notation
When using the square bracket notation, it can take anything - however it will be converted to a string (all object properties in JavaScript are strings):
// both are the same
foo['bar'];
foo["bar"];
// this is fine
foo['&^&%^&#&(#&&#'];
// this is equivalent to foo["[object Object]"]
foo[{}];
Pick your fancy - but unless you need to, it's most likely easier to use dot notation to access JavaScript object properties.
EDIT: about your jsFiddle, here's why it doesn't work:
var options = {
// Passing these options in
msg: 'This is my message',
closeButton: {
text: "Close this",
colour: "red"
}
},
// These are the defaults if none are passed in
settings = $.extend({
title: 'Default Title',
msg: 'Default message',
closeButton: {
text: "Close",
colour: "red",
btnClass: "pull-right"
}
}, options);
console.log(settings.closeButton.text);
console.log(settings.closeButton.colour);
console.log(settings.closeButton.btnClass);
/*
settings.closeButton.text
settings.closeButton.colour
settings.closeButton.btnClass
*/
When you're calling $.extend(), any properties in the later arguments will replace those in the earlier ones. In this case, your closeButton property in your $.extend() call is being replaced by the one in options, since the arguments was given later.
Here's an example of this in action:
var a = { foo: 'bar' };
var b = { foo: 'baz' };
var c = $.extend(a, b);
var d = $.extend(b, a);
console.log(c.foo); // baz (b was last argument)
console.log(d.foo); // bar (a was given last)
To solve this issue, either swap the arguments, or (in this case acceptable) perform a deep copy, by prepending the arguments with true:
$.extend({ a: { b: 1, c: 2 } }, { a: { b: 3 } }).a; // { b: 3 }
$.extend(true, { a: { b: 1, c: 2 } }, { a: { b: 3 } }).a; // { b: 3, c: 2 }

Related

Turf JS does not create Object properly

I am using the helper function turf.point()
const feature = turfHelpers.point(coords, properties, { id: properties.id });
properties looks like this
properties = {
id: 1,
thisWorks: 'no problem'
foo: {
thisDoesntWork: 'this is a problem'
}
}
When I create feature with turfHelpers.point(), it messes with the object. The nested object is not an object anymore, but gets stringyfied...
So, features.properties is
{
id: 1,
thisWorks: 'no problem'
foo: "{
thisDoesntWork: 'this is a problem'
}"
}
Now, I cannot access. feature.properties.foo.thisDoesntWork anymore, because its a string...
Why is turf.js doing that?
Let's put the question in the runnable form.
const turfHelpers = turf.helpers;
const coords = [100, 14];
const properties = {
id: 1,
thisWorks: 'no problem',
foo: {
thisDoesntWork: 'this is a problem'
}
};
var feature1 = turfHelpers.point(coords, properties, {
id: properties.id
});
// Print out on console
console.log(feature1.properties); //look OK
console.log(feature1.properties.foo); //also OK
console.log(feature1.properties.foo.thisDoesntWork); //also OK
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/5.1.5/turf.min.js"></script>
Then, hopefully, it is helpful for discussion that leads to a solution.

Destructuring deep properties

I recently started using ES6's destructuring assignment syntax and started to get familiar with the concept. I was wondering if it's possible to extract a nested property using the same syntax.
For example, let's say I have the following code:
let cagingIt = {
foo: {
bar: 'Nick Cage'
}
};
I know I am able to access extract foo into a variable by doing:
// where foo = { bar: "Nick Cage" }
let { foo } = cagingIt;
However, is it possible to extract a deeply nested property, like bar. Perhaps something like this:
// where bar = "Nick Cage"
let { foo[bar] } = cagingIt;
I've tried finding documentation on the matter but to no avail. Any help would be greatly appreciated. Thank you!
There is a way to handle nested objects and arrays using this syntax. Given the problem described above, a solution would be the following:
let cagingIt = {
foo: {
bar: 'Nick Cage'
}
};
let { foo: {bar: name} } = cagingIt;
console.log(name); // "Nick Cage"
In this example, foo is referring to the property name "foo". Following the colon, we then use bar which refers to the property "bar". Finally, name acts as the variable storing the value.
As for array destructuring, you would handle it like so:
let cagingIt = {
foo: {
bar: 'Nick Cage',
counts: [1, 2, 3]
}
};
let { foo: {counts: [ ct1, ct2, ct3 ]} } = cagingIt;
console.log(ct2); // prints 2
It follows the same concept as the object, just you are able to use array destructuring and store those values as well.
You can destructure a property "as" something else:
const { foo: myFoo } = { foo: 'bar' } // myFoo == 'bar'
Here foo was destructured as myFoo. You can also destructure an object "as" a destructured object
const { foo: { bar } } = { foo: { bar: 'baz' } } // bar == 'baz'
Only one variable was defined in each situation, myFoo and bar, and you can see how they are in similar locations as well, except bar has the { } around it.
You can do this for as many layers of nesting as you like, but if you aren't careful going too many level deep you'll get the old "Cannot read properties of undefined(reading 'foo')".
// Here's an example that doesn't work:
const foo = { bar: { notBaz: 1 } };
const {
bar: {
baz: { // baz is undefined in foo, so by trying to destructure it we're trying to access a property of 'undefined'
qux
}
}
} = foo;
// throws Uncaught TypeError: Cannot read properties of undefined (reading 'baz')
// because baz is 'undefined'
// Won't run due to error above
console.log(qux);
In this case it should be obvious that we shouldn't be trying to destructure it because we can see the definition of foo on the previous line doesn't define the property baz. If the object is coming from an API, though, you aren't always guaranteed that every nested property of your expected result will be non-null or not undefined and you can't know beforehand.
You can set a default value for a destructured object by adding = {}:
const { foo: myFoo = 'bar' } = { baz: 'qux' }; // myFoo == 'bar'
const { bar: { baz } = {} } = { qux: 'quuz' } // baz == undefined
// baz is destructured from the object that was set as the default for foo, which is undefined
// this would throw without the default object, as were trying to destructure from 'undefined'
You can do this for deeply nested destructurings:
// Here's an example that works:
const foo = { bar: { notBaz: 1 } };
const {
bar: {
baz: {
qux // you can default the final item to anything you like as well including null or undefined, but it will be undefined in this case
} = {} // if 'baz' undefined, return {}
} = {} // if 'bar' undefined, return {}
} = foo;
console.log(qux); // logs 'undefined'
If any property is null or undefined along the way, it will cause a cascade of returning empty objects, whose properties to be destructured at the next level will just be undefined. This gets out of hand really quickly though with deeper objects, which can be many lines of code with this formatting. Here's another option that does the same exact thing.
const foo = { bar: { notBaz: 1 } };
const {qux} = foo?.bar?.baz ?? {}; // optional chaining and nullish coalescing
If at any point along the way foo, bar, or baz is null or undefined or null, it will return an empty object that you can destructure( the empty object after ??.
It doesn't make much sense to use destructuring on { qux } if you only need to extract one property, though, because this also requires us to add the nullish coalesced value ?? {}. Below is probably better.
const foo = { bar: { notBaz: 1 } };
const { qux, quux, quuz } = foo?.bar?.baz ?? {}; // this method is better for multiple properties
const quxFromBaz = foo?.bar?.baz?.qux; // this method is better for single properties
For me personally, I think it looks a little messy to include all the optional chaining question marks, but it's better than the alternative with nested destructuring and default values at every level.
And it works with arrays
const foo = {
bar: [
{ a: 1 },
{ b: 2 }
]
}
const c = foo?.bar?.[2]?.c // c == undefined
// bar[2] will be undefined, so trying to access property 'c' would normally throw an error
If you have lodash installed, you can use one of the following:
_.get
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
or if you need multiple keys.
_.at
var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
_.at(object, ['a[0].b.c', 'a[1]']);
// => [3, 4]
You can also safely pair _.at() up with with Array destructuring. Handy for json responses.
[title, artist, release, artwork] = _.at(object, [
'items[0].recording.title',
'items[0].recording.artists[0].name',
'items[0].recording.releases[0].title',
'items[0].recording.releases[0].artwork[0].url'
]);
Three Levels Deep
In case this helps anyone, here's a bit of code that shows how to destructure three levels deep. In this case, I'm using the find() method on an array.
const id = someId
array.find(({ data: { document: { docId }, }, }) => docId == id)
Above, the array data is structured like this (each obj in the array is the same shape):
[{
isSuccess: true,
isLoading: false,
data: {
foo: bar,
...,
document: {
docId: '123',
...
},
}}]

How to inherit and chain String without polluting String.prototype?

What I want to be able to do is something like this:
var where = new Where();
where('a'); // returns a string 'WHERE a' that I can chain against
where('a').andWhere('b'); // reuturns 'WHERE a AND b' that is also chainable
where('a').andWhere('b').orWhere('c'); // 'WHERE a AND b OR c', and so on ...
The where methods should return what is for all intents and purposes a string, with all string like methods, but with the two custom andWhere and orWhere methods.
When I tried inheriting from Sting.prototype, my where methods returned an object, not a string. Of course, if I returned a string directly from the methods, they didn't have the andWhere and orWhere methods, so chaining broke.
The code below does what I want, but it does it by polluting the String.prototype. Is there a way to get the same behavior, but encapsulated in a custom object?
Object.defineProperty(String.prototype, "andWhere", {
value: function _andWhere(clause) {
return [this, 'AND', clause].join(' ');
},
configurable: true,
enumerable: false,
writeable: true
});
Object.defineProperty(String.prototype, "orWhere", {
value: function _orWhere(clause) {
return [this, 'OR', clause].join(' ');
},
configurable: true,
enumerable: false,
writeable: true
});
function where(clause){
return ['WHERE', clause].join(' ');
}
where('a').andWhere('b').orWhere('c');
// => 'WHERE a AND b OR c'
Edit
I still want to have access to all the string methods off the object directly. In other words the returned object acts just like a string, but with a couple more methods. For example:
var whereStr = where('a').andWhere('b').orWhere('c');
whereStr.length; // => 18
whereStr.concat(' and so on'); // => 'WHERE a AND b OR c and so on'
If it makes any difference, this is primarily for Node, but ideally would work for any recent (ES5) javascript implementation. Again, this works perfectly if I'm bad and use String.prototype, I'm hoping there's a way to do a drop in replacement.
UPDATED Added in an example of creating the length property as a "getter".
function Where(conditional) {
var thisObj = this;
//Setup the length property's "getter"
this.__defineGetter__( "length", function() {
return thisObj.clause.length;
});
this.start( conditional );
}
Where.prototype = {
AND_STR: " AND ",
OR_STR: " OR ",
add: function(conditional, prefix) {
this.clause += prefix + conditional;
},
and: function(conditional) {
this.add( conditional, this.AND_STR );
return this;
},
or: function(conditional) {
this.add( conditional, this.OR_STR );
return this;
},
start: function(conditional) {
this.clause = "WHERE " + conditional;
},
toString: function() {
return this.clause;
}
}
//Use it like this (this shows the length of the where statement):
alert( new Where( "a" ).and( "b" ).or( "c" ).length );

Chai.js: Object contains/includes

Chai has an include method. I want to test to see if an object contains another object. For example:
var origin = {
name: "John",
otherObj: {
title: "Example"
}
}
I want to use Chai to test if this object contains the following (which it does)
var match = {
otherObj: {
title: "Example"
}
}
Doing this does not appear to work:
origin.should.include(match)
Hei, just published chai-subset. Check this out: https://www.npmjs.org/package/chai-subset
This should work for you)
var chai = require('chai');
var chaiSubset = require('chai-subset');
chai.use(chaiSubset);
var obj = {
a: 'b',
c: 'd',
e: {
foo: 'bar',
baz: {
qux: 'quux'
}
}
};
expect(obj).to.containSubset({
e: {
foo: 'bar',
baz: {
qux: 'quux'
}
}
});
The include and contain assertions can be used as either property based language chains or as methods to assert the inclusion of an object in an array or a substring in a string. When used as language chains, they toggle the contain flag for the keys assertion. [emphasis mine]
So if you're invoking include on an object (not an array or a string), then it only serves to toggle the contain flag for the keys assertion. By the looks of your example, testing for deep equality would make more sense, possibly checking for the key first.
origins.should.include.keys("otherObj");
origins.otherObj.should.deep.equal(match.otherObj);
Actually, now I browse the other examples, you would probably be happiest with this :
origins.should.have.deep.property("otherObj", match.otherObj)
In chai 4.2.0, for example you can use deep include
chaijs doc examples:
// Target array deeply (but not strictly) includes `{a: 1}`
expect([{a: 1}]).to.deep.include({a: 1});
expect([{a: 1}]).to.not.include({a: 1});
// Target object deeply (but not strictly) includes `x: {a: 1}`
expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
expect({x: {a: 1}}).to.not.include({x: {a: 1}});
If you know the level of the subobject you can simply use:
expect(origin.otherObj).to.include(match.otherObj);
https://www.chaijs.com/api/bdd/
In Chai 1.5.0 you will find handy method
includeDeepMembers
http://chaijs.com/releases/

Object Not Being Passed as Reference

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 :-)

Categories