Javascript - defineProperty vs Prototype - javascript

I have just run into an unexpected problem whilst trying to create an array prototype for a function that emulates what is available in .net (linq) called SingleOrDefault().
Whilst the prototype function worked fine, I found that when iterating over a normal array using the following syntax for (var x in y)... my function name appeared as one of the properties and broke my logic.
As a result of some googling I have redefined the function (below).
My question is, is the code in the Current block the right way to extend the Array object without causing any unwanted side effects?
Previous
Array.prototype.singleOrDefault = function (predicate) {
var items = applyPredicate(this, predicate);
if (items.length > 1) {
throw new Error(errorOutput.multipleElements);
}
return (items) ? items[0] : null;
};
Current
Object.defineProperty(Array.prototype, 'singleOrDefault', {
value: function (predicate) {
var items = applyPredicate(this, predicate);
if (items.length > 1) {
throw new Error(errorOutput.multipleElements);
}
return (items) ? items[0] : null;
},
enumerable: false
});

Yes your 'current' code is correct, not as supported with older browsers as the previous code which is a downside if you need ie6/7 support (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
You could use the object.hasOwnProperty method with the 'previous' code to achieve the same effect when you are iterating:
for (var x in y) {
if (y.hasOwnProperty(y[x])) {
//would filter out SingleOrDefault in your examples above
}
}

Related

How to make a jQuery like select and action system(JavaScript)?

This is not for use in my project, Only for learning purposes.
In jQuery,
When we call $('h1'). it simply returns all the h1 elements from the document. Again when we make some action on an element like $('h1').hide(), it simply hides all the elements(cool ah?)
I want to learn this similar functionality, for example:
function app(elm){
const x = (typeof elm !== 'object') ? document.querySelectorAll(elm) : elm
return {
hide : function(){
x.forEach( target =>{
target.style.display = 'none';
});
}
}
}
This is a simple code here. So, If I call it like app('h1').hide(); it will hide all the h1 elements from the document. But if I call it like app('h1') it returns the object what I return that's normal.
In here I need all h1 elements from the document like jQuery. I mean It should work like this,
$('h1') === app('h1') //JQuery is equal to myCFunction (problem)
$('h1').hide === app('h1').hide() //jQuery is equal to myCFunction (solved)
[NOTE] Here is an article that is similar to my question but it's not my question answer.
Article Link
You can return x instead of a custom object, but before returning inject the hide function into x object's prototype like x.prototype.hide = function(){/*...*/}.
I think $("h1") does not return selected elements. It stores the selected elements. Instead we can have new function(getElement) to get select elements.Hope this code helps.
var App = function() {
var x ;
this.app = function (elem) {
x = document.querySelectorAll(elem);
return this;
}
this.hide = function(){
x.forEach(target => {
target.style.display = 'none';
});
return;
}
this.getElement = function(){
return x;
}
}
var $ = new App();
$.app("h1").hide();
console.log($.app("h1").getElement());
I've got a mostly working solution, but you still have to fix one small but annoying problem (see caveat 3). It's mostly done so I'll put it here anyway.
I think this is what you are looking for:
function app(selector) {
const retArr = document.querySelectorAll(selector); // The array to return
// Add proxies for all prototype methods of all elements
for (let e of retArr) {
let methods = getProtoMethods(e);
for (let mKey in methods) {
// Skip if the proxy method already exists in retArr
if (retArr[mKey] !== undefined) continue;
// Otherwise set proxy method
Object.defineProperty(retArr, mKey, {
value: function(...args) {
// Loop through all elements in selection
retArr.forEach(el => {
// Call method if it exists
if (el[mKey] !== undefined) el[mKey](...args);
});
}
});
}
}
return retArr;
// Gets all prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
return methods;
}
}
The idea is to put all the selected objects in an array, then define additional methods on the array. It should have all the method names of the selected objects, but those methods are actually proxies of those original methods. When one of these proxy methods is called, it calls the original method on all (see caveat 1) the selected objects in the array. But otherwise the returned object can just be used as a normal array (or more accurately, NodeList in this case).
However it's worth mentioning that there are several caveats with this particular implementation.
The list of proxy methods created is the union of the methods of all selected objects, not intersection. Suppose you selected two elements - A and B. A has method doA() and B has method doB(). Then the array returned by app() will have both doA() and doB() proxy methods. However when you call doA() for example, only A.doA() will be called because obviously B does not have a doA() method.
If the selected objects do not have the same definition for the same method name, the proxy method will use their individual definitions. This is usually desired behaviour in polymorphism but still it's something to bear in mind.
This implementation does not traverse the prototype chain, which is actually a major problem. It only looks at the prototypes of the selected elements, but not the prototypes of prototypes. Therefore this implementation does not work well with any inheritance. I did try to get this to work by making getProtoMethods() recursive, and it does work with normal JS objects, but doing that with DOM elements throws weird errors (TypeError: Illegal Invocation) (see here). If you can somehow fix this problem then this would be a fully working solution.
This is the problematic recursive code:
// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
// obj[pKey] throws error when obj is already a prototype object
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
// If obj's prototype has its own prototype then recurse.
if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
return methods;
} else {
return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
}
}
Sorry I cannot solve your problem 100%, but hopefully this at least somewhat helpful.

checking thruthness of overwritten valueOf not working

I want to create a similar construction in my code.
var inList = findItem(list, data);
if(!inList) {
var item = inList.item;
}
function findItem(list, data) {
var item = list.find("[data-day='"+data.day+"']")
// more code.
// conditional return
return {item: item, valueOf:function(){return false}};
}
But it doesn't work because overwriting valueOf doesn't play nicely with a simple truthfull check (in the way that I want it to work).
and having code like if(inList == false){} looks less clean imo. Is there a way to make this work?
Boolean checks don't invoke valueOf - all objects are considered truthy. If you want to circumvent that, you'll have to invoke it yourself explicitly:
if (!inList.valueOf()) …
You should not depend on code that uses valueOf,
if you wanted to do something where you are returning an object,
just add another property instead.
var findResult = findItem(list, data);
if(!findResult.found) {
var item = findResult.item;
}
function findItem(list, data) {
var item = list.find("[data-day='"+data.day+"']");
// more code.
// conditional return
return {item: item, found: false};
}
Then again, I forgot what I was doing 5 years ago.

How can I make Ember.js handlebars #each iterate over objects?

I'm trying to make the {{#each}} helper to iterate over an object, like in vanilla handlebars. Unfortunately if I use #each on an object, Ember.js version gives me this error:
Assertion failed: The value that #each loops over must be an Array. You passed [object Object]
I wrote this helper in attempt to remedy this:
Ember.Handlebars.helper('every', function (context, options) {
var oArray = [];
for (var k in context) {
oArray.push({
key : k,
value : context[k]
})
}
return Ember.Handlebars.helpers.each(oArray, options);
});
Now, when I attempt to use {{#every}}, I get the following error:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
This seems like a basic feature, and I know I'm probably missing something obvious. Can anyone help?
Edit:
Here's a fiddle: http://jsfiddle.net/CbV8X/
Use {{each-in}} helper. You can use it like like {{each}} helper.
Example:
{{#each-in modelWhichIsObject as |key value|}}
`{{key}}`:`{{value}}`
{{/each-in}}
JS Bin demo.
After fiddling with it for a few hours, I came up with this hacky way:
Ember.Handlebars.registerHelper('every', function(context, options) {
var oArray = [], actualData = this.get(context);
for (var k in actualData) {
oArray.push({
key: k,
value: actualData[k]
})
}
this.set(context, oArray);
return Ember.Handlebars.helpers.each.apply(this,
Array.prototype.slice.call(arguments));
});
I don't know what repercussions this.set has, but this seems to work!
Here's a fiddle: http://jsfiddle.net/CbV8X/1/
I've been after similar functionality, and since we're sharing our hacky ways, here's my fiddle for the impatient: http://jsfiddle.net/L6axcob8/1/
This fiddle is based on the one provided by #lxe, with updates by #Kingpin2k, and then myself.
Ember: 1.9.1, Handlebars: 2.0.0, jQuery 2.1.3
Here we are adding a helper called every which can iterate over objects and arrays.
For example this model:
model: function() {
return {
properties: {
foo: 'bar',
zoo: 'zar'
}
};
}
can be iterated with the following handlebars template:
<ul class="properties">
{{#every p in properties}}
<li>{{p.key}} : {{p.value}}</li>
{{/every}}
</ul>
every helper works by creating an array from the objects keys, and then coordinating changes to Ember by way of an ArrayController. Yeah, hacky. This does however, let us add/remove properties to/from an object provided that object supports observation of the [] property.
In my use case I have an Ember.Object derived class which notifies [] when properties are added/removed. I'd recommend looking at Ember.Set for this functionality, although I see that Set been recently deprecated. As this is slightly out of this questions scope I'll leave it as an exercise for the reader. Here's a tip: setUnknownProperty
To be notified of property changes we wrap non-object values in what I've called a DataValueObserver which sets up (currently one way) bindings. These bindings provide a bridge between the values held by our internal ArrayController and the object we are observing.
When dealing with objects; we wrap those in ObjectProxy's so that we can introduce a 'key' member without the need to modify the object itself. Why yes, this does imply that you could use #every recursively. Another exercise for the reader ;-)
I'd recommend having your model be based around Ember.Object to be consistent with the rest of Ember, allowing you to manipulate your model via its get & set handlers. Alternatively, as demonstrated in the fiddle, you can use Em.Get/Em.set to access models, as long as you are consistent in doing so. If you touch your model directly (no get/set), then every won't be notified of your change.
Em.set(model.properties, 'foo', 'asdfsdf');
For completeness here's my every helper:
var DataValueObserver = Ember.Object.extend({
init: function() {
this._super();
// one way binding (for now)
Em.addObserver(this.parent, this.key, this, 'valueChanged');
},
value: function() {
return Em.get(this.parent, this.key);
}.property(),
valueChanged: function() {
this.notifyPropertyChange('value');
}
});
Handlebars.registerHelper("every", function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
Ember.assert("Must be in the form #every foo in bar ", 3 == args.length && args[1] === "in");
options.hash.keyword = args[0];
var property = args[2];
// if we're dealing with an array we can just forward onto the collection helper directly
var p = this.get(property);
if (Ember.Array.detect(p)) {
options.hash.dataSource = p;
return Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
}
// create an array that we will manage with content
var array = Em.ArrayController.create();
options.hash.dataSource = array;
Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
//
var update_array = function(result) {
if (!result) {
array.clear();
return;
}
// check for proxy object
var result = (result.isProxy && result.content) ? result.content : result;
var items = result;
var keys = Ember.keys(items).sort();
// iterate through sorted array, inserting & removing any mismatches
var i = 0;
for ( ; i < keys.length; ++i) {
var key = keys[i];
var value = items[key];
while (true) {
var old_obj = array.objectAt(i);
if (old_obj) {
Ember.assert("Assume that all objects in our array have a key", undefined !== old_obj.key);
var c = key.localeCompare(old_obj.key);
if (0 === c) break; // already exists
if (c < 0) {
array.removeAt(i); // remove as no longer exists
continue;
}
}
// insert
if (typeof value === 'object') {
// wrap object so we can give it a key
value = Ember.ObjectProxy.create({
content: value,
isProxy: true,
key: key
});
array.insertAt(i, value);
} else {
// wrap raw value so we can give it a key and observe when it changes
value = DataValueObserver.create({
parent: result,
key: key,
});
array.insertAt(i, value);
}
break;
}
}
// remove any trailing items
while (array.objectAt(i)) array.removeAt(i);
};
var should_display = function() {
return true;
};
// use bind helper to call update_array if the contents of property changes
var child_properties = ["[]"];
var preserve_context = true;
return Ember.Handlebars.bind.call(context, property, options, preserve_context, should_display, update_array, child_properties);
});
Inspired by:
How can I make Ember.js handlebars #each iterate over objects?
http://mozmonkey.com/2014/03/ember-getting-the-index-in-each-loops/
https://github.com/emberjs/ember.js/issues/4365
https://gist.github.com/strathmeyer/1371586
Here's that fiddle again if you missed it:
http://jsfiddle.net/L6axcob8/1/

JavaScript dynamically defining properties

I'm working on a JS framework and came across something odd (or I'm missing completely obvious)
I define properties on objects using Object.defineProperty. However using this within a for loop will result in some funky values. Somehow the last property added will always be the assigned value. For example if I'd assign something to attribute one it gets assigned to attribute three. Here's an example (and here's a fiddle http://jsfiddle.net/5xLdC/)
var Test = function(){};
var props = ['one', 'two', 'three'];
for(var i = 0; i < props.length; i++) {
Object.defineProperty(Test.prototype, props[i], {
get: function() {
return this['_'+props[i]];
},
set: function(val) {
this['_'+props[i]] = val;
}
});
}
var test = new Test();
test.one = 'one';
console.log(test.three) // => 'one'
If I'd wrap this up in a forEach loop it works perfectly fine. What I'm guess at is that they all hold the same get/set functions due to scope errors (on my part?).
Could anyone explain as to why this happens?
EDIT:
Also possible to solve with a IIFE:
get:(function(y) {
return function() { return this['_'+props[y]]; }
})(i)
get and set are functions that all reference the same variable, i. By the time you call the function outside the loop, i is 3. When you use forEach, the function defining the properties gets the index or key as a parameter, which is a different entity in each call.

Userscript - Replace a variable's property with a function

A website has the following code:
var Items = {
drop: function (a, b, d) {
if (!(typeof a == "undefined" || typeof sockets[a.id] == "undefined")) {
SSocket.send(sockets[a.id], {
action: "item_drop",
data: {
id: d
}
});
Inventory.add(a, d)
}
},
give_to_player: function (a, b) {
Items.drop(a, void 0, b)
},
take_from_player: function (a, b) {
var d = clients[a];
Inventory.remove(d, b);
Player.send_inventory(d.id)
},
};
I am trying to replace the give_to_player property with my own function using a userscript. However, I am having zero luck doing so. I am familiar with javascript injection and the variable scope.
I have tried the following:
Object.defineProperty(window.Item, 'give_to_player', {
value:
function(a,b){
console.log('Change occured');
}
});
This does not generate any errors, however the change does not take hold and the console remains empty. I have tried Object.defineProperties as well with no luck.
Finally the following code failed to produce results either:
window.Item.give_to_player = function(a,b){ console.log('Change occured');};
Does anyone have any suggestions?
I am using Chrome to run my userscripts.
The second method would work if you change the name to Items with a s and drop the window in the method to just Items.give_to_player = function(a,b){ console.log('Change occured');};.
EDIT: the var in var Items makes the method not accessible thru the window scope. if the var was dropped this window.Items.give_to_player won't throw error but since its there you'll not need to use the window in front of Items.(if that makes sense)
JSFIDDLE
side note: your error
window.Items.give_to_player = function(a,b){ console.log('Change occured');};
// Uncaught TypeError: Cannot set property 'give_to_player' of undefined
I really don't know how the rest of code looks like (if that object is in some particular scope, deeply nested or what) but if Items object is in global scope you can define AFTER that object (and its properties definition) again that property and that should override the previous one:
Items.give_to_player: function () {
//write your own function
}
But I'm not sure if this will work as long as I have so little information.

Categories

Resources