JS-Interpreter - changing “this” context - javascript

JS-Interpreter is a somewhat well-known JavaScript Interpreter. It has security advantages in that it can completely isolate your code from document and allows you to detect attacks such as infinite loops and memory bombs. This allows you to run externally defined code safely.
I have an object, say o like this:
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
I'd like to be able to run the code in process through JS-Interpreter:
for (let i = 0; i < o.process.length; i++)
interpretWithinContext(o, o.process[i]);
Where interpretWithinContext will create an interpreter using the first argument as the context, i.e. o becomes this, and the second argument is the line of code to run. After running the above code, I would expect o to be:
{
hidden: false,
regex: /^[a-z]+$/i,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: '^[a-z]+$'
}
That is, hidden and regex are now set.
Does anyone know if this is possible in JS-Interpreter?

I’ve spent a while messing around with the JS-Interpreter now, trying to figure out from the source how to place an object into the interpreter’s scope that can be both read and modified.
Unfortunately, the way this library is built, all the useful internal things are minified so we cannot really utilize the internal things and just put an object inside. Attempts to add a proxy object also failed failed since the object just wasn’t used in a “normal” way.
So my original approach to this was to just fall back to providing simple utility functions to access the outside object. This is fully supported by the library and probably the safest way of interacting with it. It does require you to change the process code though, in order to use those functions. But as a benefit, it does provide a very clean interface to communicate with “the outside world”. You can find the solution for this in the following hidden snippet:
function createInterpreter (dataObj) {
function initialize (intp, scope) {
intp.setProperty(scope, 'get', intp.createNativeFunction(function (prop) {
return intp.nativeToPseudo(dataObj[prop]);
}), intp.READONLY_DESCRIPTOR);
intp.setProperty(scope, 'set', intp.createNativeFunction(function (prop, value) {
dataObj[prop] = intp.pseudoToNative(value);
}), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"set('hidden', !get('visible'));",
"set('regex', new RegExp(get('validate'), 'i'));"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
However, after posting above solution, I just couldn’t stop thinking about this, so I dug deeper. As I learned, the methods getProperty and setProperty are not just used to set up the initial sandbox scope, but also as the code is being interpreted. So we can use this to create a proxy-like behavior for our object.
My solution here is based on code I found in an issue comment about doing this by modifying the Interpreter type. Unfortunately, the code is written in CoffeeScript and also based on some older versions, so we cannot use it exactly as it is. There’s also still the problem of the internals being minified, which we’ll get to in a moment.
The overall idea is to introduce a “connected object” into the scope which we will handle as a special case inside the getProperty and setProperty to map to our actual object.
But for that, we need to overwrite those two methods which is a problem because they are minified and received different internal names. Fortunately, the end of the source contains the following:
// Preserve top-level API functions from being pruned/renamed by JS compilers.
// …
Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty;
Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty;
So even if a minifier mangles the names on the right, it won’t touch the ones on the left. So that’s how the author made particular functions available for public use. But we want to overwrite them, so we cannot just overwrite the friendly names, we also need to replace the minified copies! But since we have a way to access the functions, we can also search for any other copy of them with a mangled name.
So that’s what I’m doing in my solution at the beginning in patchInterpreter: Define the new methods we’ll overwrite the existing ones with. Then, look for all the names (mangled or not) that refer to those functions, and replace them all with the new definition.
In the end, after patching the Interpreter, we just need to add a connected object into the scope. We cannot use the name this since that’s already used, but we can just choose something else, for example o:
function patchInterpreter (Interpreter) {
const originalGetProperty = Interpreter.prototype.getProperty;
const originalSetProperty = Interpreter.prototype.setProperty;
function newGetProperty(obj, name) {
if (obj == null || !obj._connected) {
return originalGetProperty.call(this, obj, name);
}
const value = obj._connected[name];
if (typeof value === 'object') {
// if the value is an object itself, create another connected object
return this.createConnectedObject(value);
}
return value;
}
function newSetProperty(obj, name, value, opt_descriptor) {
if (obj == null || !obj._connected) {
return originalSetProperty.call(this, obj, name, value, opt_descriptor);
}
obj._connected[name] = this.pseudoToNative(value);
}
let getKeys = [];
let setKeys = [];
for (const key of Object.keys(Interpreter.prototype)) {
if (Interpreter.prototype[key] === originalGetProperty) {
getKeys.push(key);
}
if (Interpreter.prototype[key] === originalSetProperty) {
setKeys.push(key);
}
}
for (const key of getKeys) {
Interpreter.prototype[key] = newGetProperty;
}
for (const key of setKeys) {
Interpreter.prototype[key] = newSetProperty;
}
Interpreter.prototype.createConnectedObject = function (obj) {
const connectedObject = this.createObject(this.OBJECT);
connectedObject._connected = obj;
return connectedObject;
};
}
patchInterpreter(Interpreter);
// actual application code
function createInterpreter (dataObj) {
function initialize (intp, scope) {
// add a connected object for `dataObj`
intp.setProperty(scope, 'o', intp.createConnectedObject(dataObj), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"o.hidden = !o.visible;",
"o.regex = new RegExp(o.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
And that’s it! Note that while that new implementation does already work with nested objects, it may not work with every type. So you should probably be careful what kind of objects you pass into the sandbox. It’s probably a good idea to create separate and explicitly safe objects with only basic or primitive types.

Have not tried JS-Interpreter. You can use new Function() and Function.prototype.call() to achieve requirement
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
for (let i = 0; i < o.process.length; i++)
console.log(new Function(`return ${o.process[i]}`).call(o));

Hi may be interpretWithinContext look like something like that ?
let interpretWithinContext = (function(o, p){
//in dunno for what you use p because all is on object o
o.hidden = (o.hidden === null) ? false : o.hidden;
o.regex = (o.regex === null) ? '/^[a-z]+$/i' : o.regex;
console.log(o);
return o;
});
https://codepen.io/anon/pen/oGwyra?editors=1111

Related

How to deep merge without overwriting exisiting vales

Within a game im currently developing, I keep all the game/save data in a big JS object.
for example:
const save = {
inventory: {
equips: {
weapon: {...},
chestplate: {...}
},
money: 50123
},
gameBooleans: {
isThisUnlocked: false,
isThatUnlocked: false
},
settings: {
version: '1.3.4'
}
}
Whenever I push out a new update, I check to see if the save.settings.version equals to the newest version. If it doesn't, I update the old save data to include the new values.
My problem right now is that as I develop the game, I add new fields to the save data. Eg. save.gameBooleans.isThisNewThingUnlocked: false.
Previously, what I have been doing is manually adding each new field to the object when the game detects an older save:
const currentVersion = '1.3.3'
if (save.settings.version === '1.3.2') {
save.gameBooleans.isThisNewThingUnlocked = false
save.gameBooleans.isThisOtherNewThingUnlocked = false
...etc
save.settings.version = currentVersion
}
This is becoming quite tedious and I sometimes miss a field which breaks the game and gets my thousands of players upset haha. I was wondering if there was a better approach.
I tried using lodash deepmerge but if I use it like this: _.mergeDeep(upToDateState, oldSave), it doesnt add in the new key/fields. And when used the other way: _.mergeDeep(oldSave, upToDateState), this adds in the new values but overwrites some existing values.
I dont want to write my own function to handle this but its starting to look like I have to. Does anyone have any ideas?
You can use the Object.keys() function to iterate through eack key of the new object and make a full copy in the save object including the new keys, withoud doing it manually. The function should be like this
function deepCopy(old_, new_) {
// Iterate through each key of the new object
Object.keys(new_).forEach(key => {
//If there is a nested object, recall the function
if (typeof new_[key] === 'object' && ! Array.isArray(new_[key]) && new_[key] !== null)
deepCopy(old_[key], new_[key])
// If there is a non-object value, just copy the value
else
old_[key] = new_[key];
});
}
Take into account that this function would make a reference copy of array values (which may be undesirable). However, you can make a new conditional to handle arrays and make a value copy using the spread operator newArray = ...oldArray.
Try it here
I created a new object called update which has 2 new keys in the gameBooleans value:
const save = {
gameBooleans: {
isThisUnlocked: false,
isThatUnlocked: false
},
settings: {
version: '1.3.4'
}
}
const update = {
gameBooleans: {
isThisUnlocked: true,
isThatUnlocked: true,
foo: true,
bar: true,
},
settings: {
version: '1.3.4'
}
}
function deepCopy(old_, new_) {
Object.keys(new_).forEach(key => {
if (typeof new_[key] === 'object' && ! Array.isArray(new_[key]) && new_[key] !== null)
deepCopy(old_[key], new_[key])
else
old_[key] = new_[key];
});
}
deepCopy(save, update)
console.log(save)
Is this enough for what you're trying to achieve?
const currentVersion = '1.3.3'
if (save.settings.version === '1.3.2') {
save = {
...save,
gameBooleans: {
...save.gameBooleans,
isThisNewThingUnlocked: false,
isThisOtherNewThingUnlocked: false
},
settings: {
...save.settings,
version: currentVersion
}
}
}
This way, you keep all the properties that you had on the previous object, adding new ones or overriding if the property already exists

Nested Defaults for constructor supplied with incomplete object as argument

Coming from an old-school way of handling my defaults, I am trying to wrap my head around how to allow default values within an object literal for a constructor, while calling the constructor with a partial object literal. Note: I am still not a fan of "class" syntax for constructors, but I intend to use it, so please indulge me while I learn!
Code speaks louder than words. Here was my first attempt:
class ProxyManager {
constructor(
proxy = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
) {
this.proxy = proxy;
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890});
foo.getProxy(); // returns {proxyID: 67890}
None of this is a surprise; you can see right from the syntax that as long as something is passed in as the first paramater, it becomes "proxy". So, while I wasn't expecting it to work, it was my starting point.
Out of familiarity, I fell back to an older-school way of doing it, which is something like this:
class ProxyManager {
constructor(proxy) {
this.proxy = proxy || {};
const defaults = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
// swap in more thorough sanity-check if needed
if (Object.prototype.toString.call(this.proxy) === '[object Object]') {
this.proxy = Object.assign(defaults, this.proxy)
}
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890, proxyPort: 16500});
foo.getProxy(); // returns full object with updated proxyID and proxyPort
It works, and I guess I could move on... but I am interested to see if there is a pattern I'm missing. I did some searching, and kept coming up short.
Based on comments, option #2 in the original question isn't such a bad solution. It's probably better than the following. But just to be thorough, here's what we arrived at:
class ProxyManager {
constructor(
{
proxyID = 12345,
proxyHost = '',
proxyPort = 8080,
proxySSL = false,
proxyPath = ''
} = {}
) {
this.proxy = {
proxyID,
proxyHost,
proxyPort,
proxySSL,
proxyPath
}
}
getProxy() {
return this.proxy;
}
}
It's possibly harder to grok for older-school JS users like me, and it is certainly needlessly repetitive for the end result. But it fits my original question criteria, so here it is as an answer. As a side benefit, you don't have to do a bunch of sanity-checks to ensure the defaults work. If the constructor is called with invalid paramaters (for example, passing in a simple string or int), the defaults just apply. On the other hand, that's also the drawback... there's no warning that you have used the constructor incorrectly.

How to include or detect the name of a new Object when it's created from a Constructor

I have a constructor that include a debug/log code and also a self destruct method
I tried to find info on internet about how to detect the new objects names in the process of creation, but the only recommendation that I found was pass the name as a property.
for example
var counter = {}
counter.a =new TimerFlex({debug: true, timerId:'counter.a'});
I found unnecessary to pass counter.a as a timerId:'counter.a' there should be a native way to detect the name from the Constructor or from the new object instance.
I am looking for something like ObjectProperties('name') that returns counter.a so I don't need to include it manually as a property.
Adding more info
#CertainPerformance What I need is to differentiate different objects running in parallel or nested, so I can see in the console.
counter.a data...
counter.b data...
counter.a data...
counter.c data... etc
also these objects have only a unique name, no reference as counter.a = counter.c
Another feature or TimerFlex is a method to self desruct
this.purgeCount = function(manualId) {
if (!this.timerId && manualId) {
this.timerId = manualId;
this.txtId = manualId;
}
if (this.timerId) {
clearTimeout(this.t);
this.timer_is_on = 0;
setTimeout ( ()=> { console.log(this.txtId + " Destructed" ) },500);
setTimeout ( this.timerId +".__proto__ = null", 1000);
setTimeout ( this.timerId +" = null",1100);
setTimeout ( "delete " + this.timerId, 1200);
} else {
if (this.debug) console.log("timerId is undefined, unable to purge automatically");
}
}
While I don't have a demo yet of this Constructor this is related to my previous question How to have the same Javascript Self Invoking Function Pattern running more that one time in paralel without overwriting values?
Objects don't have names - but constructors!
Javascript objects are memory references when accessed via a variables. The object is created in the memory and any number of variables can point to that address.
Look at the following example
var anObjectReference = new Object();
anObjectReference.name = 'My Object'
var anotherReference = anObjectReference;
console.log(anotherReference.name); //Expected output "My Object"
In this above scenario, it is illogical for the object to return anObjectReference or anotherReference when called the hypothetical method which would return the variable name.
Which one.... really?
In this context, if you want to condition the method execution based on the variable which accesses the object, have an argument passed to indicate the variable (or the scenario) to a method you call.
In JavaScript, you can access an object instance's properties through the same notation as a dictionary. For example: counter['a'].
If your intent is to use counter.a within your new TimerFlex instance, why not just pass counter?
counter.a = new TimerFlex({debug: true, timerId: counter});
// Somewhere within the logic of TimerFlex...
// var a = counter.a;
This is definitely possible but is a bit ugly for obvious reasons. Needless to say, you must try to avoid such code.
However, I think this can have some application in debugging. My solution makes use of the ability to get the line number for a code using Error object and then reading the source file to get the identifier.
let fs = require('fs');
class Foo {
constructor(bar, lineAndFile) {
this.bar = bar;
this.lineAndFile = lineAndFile;
}
toString() {
return `${this.bar} ${this.lineAndFile}`
}
}
let foo = new Foo(5, getLineAndFile());
console.log(foo.toString()); // 5 /Users/XXX/XXX/temp.js:11:22
readIdentifierFromFile(foo.lineAndFile); // let foo
function getErrorObject(){
try { throw Error('') } catch(err) { return err; }
}
function getLineAndFile() {
let err = getErrorObject();
let callerLine = err.stack.split("\n")[4];
let index = callerLine.indexOf("(");
return callerLine.slice(index+1, callerLine.length-1);
}
function readIdentifierFromFile(lineAndFile) {
let file = lineAndFile.split(':')[0];
let line = lineAndFile.split(':')[1];
fs.readFile(file, 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.split('\n')[parseInt(line)-1].split('=')[0].trim());
})
}
If you want to store the variable name with the Object reference, you can read the file synchronously once and then parse it to get the identifier from the required line number whenever required.

Why does flowtype think my variable is undefined?

I have a flowtype object declaration that looks like this:
type ProjectType = {
// removed for brevity
releases?: Array<ReleaseType>
}
There are times when releases is not included in the object, so when I want to use it, I first have a conditional checking for it. So here, later in my code, I access that array after a conditional like this:
if (selectedProject
&& selectedProject.releases
&& selectedProject.releases.length) {
majorReleasesSet = new Set(selectedProject.releases.map((release: ReleaseType): string => (
release.major)));
projectHasNoReleases = false;
projectLatestMajorRelease = selectedProject.releases.slice(-1)[0].major;
projectLatestMinorRelease = selectedProject.releases.slice(-1)[0].minor;
}
But flow does not like this, complaining:
Cannot call selectedProject.releases.slice because property slice is missing in undefined [1].
components/TimeEntries/EntryForm.jsx
123│ release.major)));
124│ projectHasNoReleases = false;
125│ projectLatestMajorRelease = selectedProject.releases.slice(-1)[0].major;
126│ projectLatestMinorRelease = selectedProject.releases.slice(-1)[0].minor;
127│ }
128│
129│ const projectLatestRelease = `${projectLatestMajorRelease}.${projectLatestMinorRelease}`;
What in the world am I missing? The I tried adding Array.isArray(selectedProject.releases) but flow still complained. Flow lists errors on both lines 125 and 126.
I would say that the the fact that you do some potentially effectful things (running a map function, making a set, etc) makes Flow concerned that the releases property might have changed on the object. Flow will discard basically any of your refinements after you run a function.
Here's an example of your code, as-is, throwing an error
The easiest way to get around this is to pull the releases off of your object into a separate value and perform the null check against that. That way, Flow is sure that it's still not null:
(Try)
// Mock this
type ReleaseType = {
major: string;
minor: string,
}
type ProjectType = {
// removed for brevity
releases?: Array<ReleaseType>
}
var selectedProject: ProjectType = {
major: "foo",
minor: "bar",
}
// Set up some variables for this example since I
// don't have any context on them
declare var majorReleasesSet: Set<any>;
declare var projectHasNoReleases: any;
declare var projectLatestMajorRelease: any;
declare var projectLatestMinorRelease: any;
// The `const {releases} = whateverObject` is the important part here
const {releases} = selectedProject || {};
if (releases && releases.length) {
majorReleasesSet = new Set(releases.map((release: ReleaseType): string => (release.major)));
projectHasNoReleases = false;
projectLatestMajorRelease = releases.slice(-1)[0].major;
projectLatestMinorRelease = releases.slice(-1)[0].minor;
}

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/

Categories

Resources