Javascript class - how to make properties required and optional - javascript

I'm building a event tracking api and need to provide a JS class with a list of properties of which some of them will be required and some optional. Also, no new properties should be added.
Once the client instantiates and passes the object based on the class, I will be serializing the object and sending it as query string. I already have this part but not sure how to restrict the client from creating new properties.
How do I go about designing the class in JS?

There are a number of ways of doing the things you're wanting. Required/Optionals are pretty easy with utilities like underscorejs/jquery extend, along with some of underscore's utility methods (map, reduce,etc.).
To prevent an object from actually having additional properties though is a bit trickier. You could run an array of known required/optional properties as a check and do a delete on any property values that don't match your criteria.
Another option is to use something like seal. You read up more about this on mozilla's website here.
So I guess what I'd do is get the required properties working first by doing a sanity check against their existence. Your optionals could be provided and wrapped/unwrapped by using extend.
Finally (and maybe an even better route) would be to force the user to call getter/setter methods like setMyProperty( 'value' ) in order to populate any required/optional property value. This way, you aren't going to have to write a big hairy solution using the tools above.
JS is just funny like that. Neither solution is perfect, but they are both possible solutions. I'm sure there are others too. This is just a part of JS that can be a bit of a pain in the arsonal. Good luck.
>>> Edit <<<
This may not be perfect, but this is what I've done so far for the optional/required properties. I am obviously assuming they would just be passing in a set of properties in the constructor.
var _Required = {
requiredProperty1: null,
requiredProperty2: null,
requiredProperty3: null
};
var _Optionals = {
optionalProperty1: null,
optionalProperty2: null,
optionalProperty3: null
};
var EventTrackerAPI = function( settings ) {
var requiredProp, optionalProp;
this.required = {};
this.optional = {};
for( requiredProp in _Required ) {
if( !settings.hasOwnProperty( requiredProp ) ) {
throw new Error( 'FAILED to create an instance of EventTrackerAPI - Required Property (' + requiredProp + ') Missing!' )
}
this.required[requiredProp] = settings[requiredProp];
}
for( optionalProp in _Optionals ) {
if( settings.hasOwnProperty( optionalProp ) ) {
this.optional[optionalProp] = settings.hasOwnProperty( optionalProp );
} else {
this.optional[optionalProp] = null;
}
}
};
Anyways, I'd probably do something like the above. It isn't perfect yet (since I threw it together in about 10 minutes), but it should be a good start. I would also just not allow access to the private storage either.
When I was ready to actually extract the properties (like when you're ready to serialize), I'd do something similar as is being done in the constructor - just manually go through and grab the things you actually want - everything else would just be discarded this way. As a matter of fact, it may make sense to strip out the optional/required logic and make them methods on the object's prototype (EventTrackerAPI.prototype.extractRequired = function( required ) {.... } for example...)

Why not usign just class with constructor - you will have required properties then

Related

javascript referencing dynamic parent object

I have this object, a 3rd party tracking tool similar to google analytics. I want to extend it with my own "caching" function that saves the data from the previous tracking call so that I can reference stuff on the next tracking call if needed.
This is what I have so far, and it works:
// Current 3rd party tool, can't really mess with this.
// It is loaded from an external script
window.someTool={/* stuff */};
// my code
someTool._cache=someTool._cache||{};
someTool._cache._get=function(variabl) {
var length,index,variabl=(variabl||'').split('.'),
cache=someTool&&someTool._cache&&someTool._cache._dataLayer||{};
for (index=0,length=var.length;index<length;index++){
cache=cache[variabl[index]];
if (!cache) break;
}
return cache;
};
So then I have/do the following
// data layer output on initial page that gets wiped later
var dataLayer = {
'page' : {
'name' : 'foo',
'lang' : 'en'
},
'events' : {
'pageView' : true,
'search' : true
}
}
// I grab the initial data layer and save it here
someTool._cache._dataLayer = dataLayer;
This then allows me to do stuff like
someTool._cache._get('page'); // returns {'page':{'name':'foo','lang':'en'}
someTool._cache._get('page')['name']; // returns 'foo'
someTool._cache._get('page.lang'); // returns 'en'
So this works for me, but here comes the question/goal: I want to improve my _get function. Namely, I don't like that I have to hardcode someTool, or really even _cache, and if I can somehow swing it, _dataLayer.
Ideally, I'd like a reference of someTool._cache._dataLayer passed/exposed to _get (e.g. a parent type reference) so that if someTool,_cache, or _dataLayer were to change namespaces, I don't have to update _get. But I am not sure how to do that.
This is what I have so far:
(function(tool, cache, dataLayer) {
var tool = tool || {},
cache = cache || '_cache',
dataLayer = dataLayer || '_dataLayer';
dataLayer = tool[cache][dataLayer] || {};
tool[cache]._get = function(property) {
var length, index, property = (property || '').split('.');
for (index = 0, length = property.length; index < length; index++) {
dataLayer = dataLayer[property[index]];
if (!dataLayer) break;
}
return dataLayer;
};
})(someTool, '_cache', '_dataLayer');
This seems to work the first time I call it, e.g.
someTool._cache._get('page')['name']; // returns 'foo'
But after that, I get an error:
TypeError: someTool._cache._get(...) is undefined
I feel like it has something to do with dataLayer losing its reference or something, I dunno (though I'm not sure how it's working first time around..). Is what I am doing even possible, and if so, where am I going wrong? Or is what I originally have the best I can do?
I feel like it has something to do with dataLayer losing its reference or something, I dunno (though I'm not sure how it's working first time around..).
The reason this is happening is because you are using the same dataLayer you initialize in the closure of _get to:
store information, and
to use as a temporary loop variable
If you look at your code:
(function(tool, cache, dataLayer) {
// ...
// Here you are initializing (or looking up) the dataLayer
dataLayer = tool[cache][dataLayer] || {};
tool[cache]._get = function(property) {
// ...
for (index = 0, length = property.length; index < length; index++) {
// here you are overwriting the same dataLayer
dataLayer = dataLayer[property[index]];
if (!dataLayer) break;
}
return dataLayer;
};
})(someTool, '_cache', '_dataLayer');
You can see that your loop will overwrite dataLayer on each iteration which means every lookup after the first will most likely be wrong.
Eventually, dataLayer will be overwritten with undefined, and then any further lookups will now break the code.
What you can do is use another variable for the loop iteration:
var temp;
for (index = 0, length = property.length; index < length; index++) {
temp = dataLayer[property[index]];
if (!temp) break;
}
return temp;
This will leave your dataLayer object intact.
Although your code is so obsfucated (one-character variable names, abuse of the comma operator, etc.) that its hard to tell for sure, it seems that you need to fix a few things before moving on.
Properties prefixed with an underscore are meant to be private. They are subject to change, and by change I mean your app randomly breaking. Use the public API.
Parsing strings out by hand is a lot of work for seemingly little gain. Is the use case for get('page.id') over get('page').id really so compelling?
Your code is incomprehensible. This is the kind of output one would expect of a minifier: it makes it hard to understand what any of it does/is supposed to do.
Unless a third-party API is so integral to your application that replacing it would require a rewrite no matter what (e.g. google maps) or so well-known that it has umpteen clones (jquery), its is generally a good idea to wrap third-party library calls so you can change the library later.
I realize this does not answer your question, but its way too long for a comment and it would be remiss of me to not point out the bright red targets (plural) you've painted on your feet prior to polishing your firearm.
As for your actual question (post-edit), you're on the right track. But I'd make it a curried function so that you can dynamically access different properties. We're going to ignore for one minute the huge mistake that is accessing private properties just to get the point across:
function accessDataCache(cache) {
return function(dataLayer) {
return function(namespaceObj) {
return function(property) {
return namespaceObj[cache][dataLayer][property];
};
};
};
};
var getFn = accessDataCache('_cache')('_dataLayer')(someTool);
getFn('page');
You can now also mix and match if you need other stuff:
var getSomeOtherCachedThing = accessDataCache('_cache')('_someOtherThing')(someTool);
All of that is quite tedious to write out by hand, so I recommend using something like lodash or Ramda and .curry to achieve the effect:
var accessCacheData = R.curry(function(cache, dataLayer, namespaceObj, property) {
return namespaceObj[cache][dataLayer][property];
});

JSON.stringify and Object.keys produce different results on same object

I have an issue that isn't making any sense to me. I was wondering if any of you could help.
I have a data source object which I use to access REST data through. All the complex asynchronous stuff works fine, but I've come completely unstuck on what should be the very simple task of passing options into the configuration of the data sources.
At the moment, I have this function:
object.addSourceOption = function( model, optKey, optVal ){
if(!_.has(config.sources, model) ){ return this; }
else{
var options = config.sources[model]["options"];
options[optKey] = optVal;
console.log(options[optKey]);
//options = JSON.parse( JSON.stringify( ) );
console.log( "Source Option: " + optKey + ": " + optVal
+" added for model: " + model );
var debugString = JSON.stringify(options);
console.log(debugString);
console.log( Object.keys(options));
}
return this;
};
This function is being called and it's being called with good values as far as I can see. Here's an example of some debug output (in this case, the key value is "post_get" and the value is a function, which is printed):
function (element){
}
restData2.js:189 Source Option: post_get: function (element){
} added for model: Contacts
restData2.js:191 {}
restData2.js:192 ["post_get"]
I don't understand why JSON.stringify and Objects.keys produce different results. I don't understand which to trust to debug the code I'm using, or what could possibly be happening behind the scenes to make the two functions disagree about the code I've written.
Congratulations! You've found one of the subtler parts of JSON.stringify(...) :)
Here's a helpful part from MDN:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
While JSON is convenient because it looks like Javascript, JSON is really a subset of Javascript. Moreover, not all JS objects can be represented in JSON. The full details are available at http://json.org/.
But yeah, in your example, post_get is not showing up in the JSON because properties of type function are not legal in JSON.
Hope this helps!
Aside:
Also, keep in mind that sometimes a JS object can have properties that aren't returned by Object.keys(...) either, because Object.keys(...) only returns the properties that were not inherited from any prototypes (base objects) that the object extends.
the key value is "post_get" and the value is a function
JSON.stringify() won't include properties that refer to functions. Object.keys() will. It's as simple as that.

How to detect when a property is added to a JavaScript object?

var obj = {};
obj.a = 1; // fire event, property "a" added
This question is different from this one, where ways to detect when an already declared property is changed, being discussed.
this is possible, technically, but since all current JS implementations that I know of are single threaded it won't be very elegant. The only thing I can think of is a brute force interval:
var checkObj = (function(watchObj)
{
var initialMap = {},allProps = [],prop;
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//make tracer object: basically clone it
initialMap[prop] = watchObj[prop];
allProps.push(prop);//keep an array mapper
}
}
return function()
{
var currentProps = [];
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//iterate the object again, compare
if (watchObj[prop] !== initialMap[prop])
{//type andvalue check!
console.log(initialMap[prop] + ' => ' watchObj[prop]);
//diff found, deal with it whichever way you see fit
}
currentProps.push(prop);
}
}
//we're not done yet!
if (currentProps.length < allProps.length)
{
console.log('some prop was deleted');
//loop through arrays to find out which one
}
};
})(someObjectToTrack);
var watchInterval = setInterval(checkObj,100);//check every .1 seconds?
That allows you to track an object to some extent, but again, it's quite a lot of work to do this 10/sec. Who knows, maybe the object changes several times in between the intervals, too.All in all, I feel as though this is a less-then-ideal approach... perhaps it would be easier to compare the string constants of the JSON.stringify'ed object, but that does mean missing out on functions, and (though I filtered them out in this example) prototype properties.
I have considered doing something similar at one point, but ended up just using my event handlers that changed the object in question to check for any changes.
Alternatively, you could also try creating a DOMElement, and attach an onchange listener to that... sadly, again, functions/methods might prove tricky to track, but at least it won't slow your script down as much as the code above will.
You could count the properties on the object and see if has changed from when you last checked:
How to efficiently count the number of keys/properties of an object in JavaScript?
this is a crude workaround, to use in case you can't find a proper support for the feature in the language.
If performance matters and you are in control of the code that changes the objects, create a control class that modifies your objects for you, e.g.
var myObj = new ObjectController({});
myObj.set('field', {});
myObj.set('field.arr', [{hello: true}]);
myObj.set('field.arr.0.hello', false);
var obj = myObj.get('field'); // obj === {field: {arr: [{hello: false}]}}
In your set() method, you now have the ability to see where every change occurs in a pretty high-performance fashion, compared with setting an interval and doing regular scans to check for changes.
I do something similar but highly optimised in ForerunnerDB. When you do CRUD operations on the database, change events are fired for specific field paths, allowing data-bound views to be updated when their underlying data changes.

Better Understanding Javascript by Examining jQuery Elements

Because jQuery is a widely used and mature collaborative effort, I can't help but to look at its source for guidance in writing better Javascript. I use the jQuery library all the time along with my PHP applications, but when I look under the hood of this rather sophisticated library I realize just how much I still don't understand about Javascript. Lo, I have a few questions for the SO community. First of all, consider the following code...
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
Now, is this to say that the attr() method was designed to accept EITHER an attribute name, an attribute name and a value, or a pair-value map? Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
Moving on, the whole library is wrapped in this business...
(function(window, undefined) { /* jQuery */ })(window);
I get that the wrapped parentheses cause a behavior similar to body onLoad="function();", but what is this practice called and is it any different than using the onLoad event handler? Also, I can't make heads or tails of the (window) bit there at the end. What exactly is happening with the window object here?
Am I wrong in the assessment that objects are no different than functions in Javascript? Please correct me if I'm wrong on this but $() is the all encompassing jQuery object, but it looks just like a method. Here's another quick question with a code example...
$('#element').attr('alt', 'Adopt a Phantom Cougar from Your Local ASPCA');
... Should look something like this on the inside (maybe I'm wrong about this)...
function $(var element = null) {
if (element != null) {
function attr(var attribute = null, var value = null) {
/* stuff that does things */
}
}
}
Is this the standing procedure for defining objects and their child methods and properties in Javascript? Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
I apologize for this being a bit lengthy, but answers to these questions will reveal a great deal to me about jQuery and Javascript in general. Thanks!
1. Method overloading
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
var attr = function (key, value) {
// is first argument an object / map ?
if (typeof key === "object") {
// for each key value pair
for (var k in key) {
// recursively call it.
attr(k, key[k]);
}
} else {
// do magic with key and value
}
}
2. Closures
(function(window, undefined) { /* jQuery */ })(window);
Is not used as an onload handler. It's simply creating new scope inside a function.
This means that var foo is a local variable rather then a global one. It's also creating a real undefined variable to use since Parameters that are not specified passes in undefined
This gaurds againts window.undefined = true which is valid / allowed.
the (window) bit there at the end. What exactly is happening with the window object here?
It's micro optimising window access by making it local. Local variable access is about 0.01% faster then global variable access
Am I wrong in the assessment that objects are no different than functions in Javascript?
Yes and no. All functions are objects. $() just returns a new jQuery object because internally it calls return new jQuery.fn.init();
3. Your snippet
function $(var element = null) {
Javascript does not support default parameter values or optional parameters. Standard practice to emulate this is as follows
function f(o) {
o != null || (o = "default");
}
Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
You can access properties on an object using foo.property or foo["property"] a property can be any type including functions / methods.
4. Miscellanous Questions hidden in your question
Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
An array is created using var a = [] it simply contains a list of key value pairs where all the keys are positive numbers. It also has all the Array methods. Arrays are also objects.
A map is just an object. An object is simply a bag of key value pairs. You assign some data under a key on the object. This data can be of any type.
For attr, if you give an object instead of a key value pair it will loop on each property.
Look for attr: in jQuery's code, then you'll see it use access. Then look for access: and you will see there is a check on the type of key if it is an object, start a loop.
The wrapping in a function, is to prevent all the code inside to be accessed from outside, and cause unwanted problems. The only parameters that are passed are window that allow to set globals and access the DOM. The undefined I guess it is to make the check on this special value quicker.
I read sometimes jQuery but I didn't start with it, may be you should get some good books to make you an idea first of what some advanced features Javascript has, and then apply your knowledge to the specifics of jQuery.
1 - Yes attr can accept a attribute name for getting a value, a name and a value for setting one value or a map of attribute names and values for settings more than one attribute
2 - A map is basically a JavaScript object e.g:
var map = {
'key1' : 'value1',
'key2' : 'value2'
};
3 - (function(window, undefined) { /* jQuery */ })(window); is something called an anonymous function as it doesn't have a name. In this case it also executes straight away.
A simple example would be:
function test(){
...
}
test();
//As an anonymous function it would be:
(function(){
...
}();
//And it you wanted to pass variables:
function test(abc){
...
}
test(abc);
//As an anonymous function it would be:
(function(abc){
...
}(abc);
this would make it different to the load event, as it is a function not an event.
4 - window is passed as a variable, as it is used internally within jQuery
5 - Objects and functions the same, as everything in JavaScript is an object. jQuery does something like this:
var obj = {
"init" : function(){
}
}
6 - Yes you can use . to retrieve a value on an object but you can also use [] e.g:
var map = {
"test" : 1
}
map.test //1
map["test"] //1
I hope this answers your many questions, let me know if I've missed anything out.
jQuery 1.6.1
The test is typeof key === "object"
if that is true, then you passed a { .... }
jQuery.fn.extend({
attr: function( name, value ) {
return jQuery.access( this, name, value, true, jQuery.attr );
},
// Mutifunctional method to get and set values to a collection
// The value/s can be optionally by executed if its a function
access: function( elems, key, value, exec, fn, pass ) {
var length = elems.length;
// Setting many attributes
if ( typeof key === "object" ) {
for ( var k in key ) {
jQuery.access( elems, k, key[k], exec, fn, value );
}
return elems;
}
// Setting one attribute
if ( value !== undefined ) {
// Optionally, function values get executed if exec is true
exec = !pass && exec && jQuery.isFunction(value);
for ( var i = 0; i < length; i++ ) {
fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
}
return elems;
}
// Getting an attribute
return length ? fn( elems[0], key ) : undefined;
},

Number of elements in a javascript object

Is there a way to get (from somewhere) the number of elements in a Javascript object?? (i.e. constant-time complexity).
I can't find a property or method that retrieves that information. So far I can only think of doing an iteration through the whole collection, but that's linear time.
It's strange there is no direct access to the size of the object, don't you think.
EDIT:
I'm talking about the Object object (not objects in general):
var obj = new Object ;
Although JS implementations might keep track of such a value internally, there's no standard way to get it.
In the past, Mozilla's Javascript variant exposed the non-standard __count__, but it has been removed with version 1.8.5.
For cross-browser scripting you're stuck with explicitly iterating over the properties and checking hasOwnProperty():
function countProperties(obj) {
var count = 0;
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
++count;
}
return count;
}
In case of ECMAScript 5 capable implementations, this can also be written as (Kudos to Avi Flax)
function countProperties(obj) {
return Object.keys(obj).length;
}
Keep in mind that you'll also miss properties which aren't enumerable (eg an array's length).
If you're using a framework like jQuery, Prototype, Mootools, $whatever-the-newest-hype, check if they come with their own collections API, which might be a better solution to your problem than using native JS objects.
To do this in any ES5-compatible environment
Object.keys(obj).length
(Browser support from here)
(Doc on Object.keys here, includes method you can add to non-ECMA5 browsers)
if you are already using jQuery in your build just do this:
$(yourObject).length
It works nicely for me on objects, and I already had jQuery as a dependancy.
function count(){
var c= 0;
for(var p in this) if(this.hasOwnProperty(p))++c;
return c;
}
var O={a: 1, b: 2, c: 3};
count.call(O);
AFAIK, there is no way to do this reliably, unless you switch to an array. Which honestly, doesn't seem strange - it's seems pretty straight forward to me that arrays are countable, and objects aren't.
Probably the closest you'll get is something like this
// Monkey patching on purpose to make a point
Object.prototype.length = function()
{
var i = 0;
for ( var p in this ) i++;
return i;
}
alert( {foo:"bar", bar: "baz"}.length() ); // alerts 3
But this creates problems, or at least questions. All user-created properties are counted, including the _length function itself! And while in this simple example you could avoid it by just using a normal function, that doesn't mean you can stop other scripts from doing this. so what do you do? Ignore function properties?
Object.prototype.length = function()
{
var i = 0;
for ( var p in this )
{
if ( 'function' == typeof this[p] ) continue;
i++;
}
return i;
}
alert( {foo:"bar", bar: "baz"}.length() ); // alerts 2
In the end, I think you should probably ditch the idea of making your objects countable and figure out another way to do whatever it is you're doing.
The concept of number/length/dimensionality doesn't really make sense for an Object, and needing it suggests you really want an Array to me.
Edit: Pointed out to me that you want an O(1) for this. To the best of my knowledge no such way exists I'm afraid.
With jquery :
$(parent)[0].childElementCount

Categories