Open ramnathv opened 8 years ago
Here is an implementation of the idea that actually seems to work
import { extendObservable, observable, computed, autorun, toJSON } from 'mobx';
class AyxStore {
constructor(manager, dataItems){
dataItems.forEach(d => {
const item = manager.GetDataItemByDataName(d.dataName)
if (d.mode === 'read'){
this[d.obsName] = item.getValue()
item.BindUserDataChanged(v => { this[d.obsName] = v; })
} else {
this[d.obsName] = JSON.parse(item.getValue())
}
})
extendObservable(this, this)
dataItems
.filter(d => d.mode === 'write')
.forEach(d => {
autorun(() => {
console.log("I am autorunning...")
let item = manager.GetDataItemByDataName(d.dataName)
item.setValue(JSON.stringify(toJSON(this[d.obsName])))
})
})
}
}
export default AyxStore;
I was able to test this using the following code
Alteryx.Gui.AfterLoad = (manager) => {
const testStore = new AyxStore(manager, [
{obsName: 'editorValue', dataName: 'editorValue', mode: 'read'},
{obsName: 'dummy', dataName: 'dummy', mode: 'write'}
])
// setting the observable dummy on the store should update the value of the data item
testStore.dummy = {"x": 3, "y": 4}
console.log(manager.GetDataItemByDataName("dummy").value === '{"x": 3, "y": 4}')
// setting the data item editorValue should update the value of the observable in the store
manager.GetDataItemByDataName("editorValue").setValue("2x1 + 3x2")
console.log(testStore.editorValue = "2x1 + 3x2")
}
This should not create any circular logic, since the auto updates are only set one way.
read
mode, the value of the observable is always updated on change in value of the data item.write
mode, the value of the data item is always updated on change in value of the observable.In some cases (e.g. editorValue), we need both.
editorValue
data item.editorValue
data item from an observable value (e.g. constraint being edited)I was able to get true two-way syncing working. The only drawback seems to be that more events are getting triggered than should be because of the nature of the updates. For example, updating a dataitem value from the UI should ONLY trigger an autorun that updates the value of the observable. But due to the syncing mechanism this triggers an additional update of the dataitem which triggers another update of the observable. The recursion seems to stop there, so the fear of an infinite loop does not seem to be justified. I have some ideas on how to kill these extra events.
class AyxStore2 {
constructor(manager, dataItems){
dataItems.forEach(d => {
const item = manager.GetDataItemByDataName(d.dataName)
this[d.obsName] = item.getValue()
item.BindUserDataChanged(v => {
console.log("Bind User Data Changed...")
this[d.obsName] = v;
})
})
extendObservable(this, this)
dataItems.forEach(d => {
autorun(() => {
console.log("Autorun...")
let item = manager.GetDataItemByDataName(d.dataName);
item.setValue(this[d.obsName]);
})
})
}
}
@ramnathv here's the implementation I came up with based on your work here. This solves the recursion problem by diffing the values before triggering updates.
import { autorun, extendObservable } from 'mobx';
class NewStore {
constructor(ayx, dataItems) {
const { Gui: { manager, renderer } } = ayx;
this.manager = manager;
this.renderer = renderer;
dataItems.forEach(d => {
// Obtain a reference to the data item
const item = this.manager.GetDataItemByDataName(d);
// Assign the data item value as a property on the store
extendObservable(this, { [`${d}`]: item.getValue() });
// Bind an event listener to the data item that will diff the values and update accordingly
// Not needed for read-only attributes
item.BindUserDataChanged(v => {
if (this[d] !== v) {
this[d] = v;
}
});
// Set up an autorun on the observable property that will change the underlying data item only
// if the values differ
autorun(() => {
if (this[d] !== item.getValue()) {
item.setValue(this[d]);
}
});
});
}
}
export default NewStore;
Thanks @cafreeman. This is really cool!