Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/bindings for getter setter #1593

Merged
merged 3 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions lively.ide/debug/console.cp.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,18 @@ class LocalJSConsoleModel extends ViewModel {
static get properties () {
return {
logLimit: { defaultValue: 1000 },
bindings: {
get () {
return [
{ signal: 'keybindings', override: true, handler: 'keybindings' },
{ signal: 'commands', override: true, handler: 'commands' },
{ signal: 'menuItems', override: true, handler: 'menuItems' }
];
}
},
expose: {
get () {
return ['onWindowClose', 'clear', 'commands', 'menuItems', 'keybindings'];
return ['onWindowClose', 'clear'];
}
}
};
Expand Down Expand Up @@ -230,27 +239,25 @@ class LocalJSConsoleModel extends ViewModel {
if (!test) this.error('Assert failed: ' + msg);
}

get keybindings () {
const viewKeybindings = this.withoutExposedPropsDo(() => this.view.keybindings);
keybindings ({ get: viewKeybindings }) {
return [
{ keys: { mac: 'Meta-K', win: 'Ctrl-Alt-K' }, command: '[console] clear' },
...viewKeybindings
{ keys: { mac: 'Meta-Alt-K', win: 'Ctrl-Alt-K' }, command: '[console] clear' },
...viewKeybindings()
];
}

get commands () {
const viewCommands = this.withoutExposedPropsDo(() => this.view.commands);
commands ({ get: viewCommands }) {
return [
{
name: '[console] clear',
exec: () => { this.clear(); return true; }
},
...viewCommands
...viewCommands()
];
}

async menuItems () {
const viewItems = await this.withoutExposedPropsDo(() => this.view.menuItems());
async menuItems ($super) {
const viewItems = await $super();
return [
{ command: '[console] clear', target: this, alias: 'clear' },
{ isDivider: true },
Expand Down
18 changes: 14 additions & 4 deletions lively.lang/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,22 @@ function allPropertyDescriptors (obj) {
return descriptors;
}

function getPropertyDescriptor (obj, prop) {
let proto = obj;
let descriptor;
while (proto = proto.__proto__) {
descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (descriptor) break;
}
return descriptor;
}

/**
* For a given object only returns all the *property or function names*
* that are directly defined on the object itself. Here we *do not* consider
* what is defined on any of the prototypes in the prototype chain of the given object.
* If `predicate` is given, these can further be filtered by a custom condition.
* @param { Object } obj - The object to collect the property and function names for.
* @param { Object } obj - The object to collect the property and function names for.
* @param { function(*, string): boolean } [predicate] - The predicate to filter the properties by further.
* @returns { string[] } The names of all the local properties or functions.
*/
Expand All @@ -49,7 +59,7 @@ function allOwnPropertiesOrFunctions (obj, predicate) {
* For a given object only returns all the *property names*
* that are directly defined on the object itself. Here we *do not* consider
* what is defined on any of the prototypes in the prototype chain of the given object.
* @param { Object } object- The object to collect the property names for.
* @param { Object } object- The object to collect the property names for.
* @returns { string[] } The names of all the local properties.
*/
function own (object) {
Expand Down Expand Up @@ -84,7 +94,7 @@ function forEachOwn (object, func, context) {
/**
* For a given `object` return the name of the property that is equal to `value`.
* @param { Object } object - The object whose properties to check.
* @param { * } value - The value to scan the properties for.
* @param { * } value - The value to scan the properties for.
* @returns { string } The name of the property that stores the same `value`.
*/
function nameFor (object, value) {
Expand Down Expand Up @@ -122,7 +132,7 @@ function ownValues (obj) {
* For a given `obj` and `predicate` checks wether any property defined for `obj` satisfies the condition
* defined by `predicate`.
* @param { Object } obj - The object whose properties to check.
* @param { function(Object, string): boolean } predicate - The predicate to check the properties for.
* @param { function(Object, string): boolean } predicate - The predicate to check the properties for.
* @returns { boolean } Wether or not any of the properties of the object satisfies the predicate.
*/
function any (obj, predicate) {
Expand Down
50 changes: 32 additions & 18 deletions lively.morphic/components/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ export class ViewModel {
clearBindings () {
noUpdate(() => {
this.getBindingConnections().forEach(conn => conn.disconnect());
// also delete all overridden getter/setters
for (let { signal, override } of this.bindings) {
if (override && Object.hasOwnProperty(this.view, signal)) { delete this.view[signal]; }
}
});
}

Expand All @@ -397,6 +401,34 @@ export class ViewModel {
try {
const initConnection = (target) => {
if (!target) return;
let currentGetter = target.__lookupGetter__(signal);
let currentSetter = target.__lookupSetter__(signal);
if ((currentGetter || currentSetter) && override) {
// we are overriding a getter we need to redefine the getter instead of intercepting the method via a connection
let descr = Object.getOwnPropertyDescriptor(target.constructor.prototype, signal);

if (currentGetter) {
descr = {
...descr,
get: () => {
return this[handler]({ get: currentGetter.bind(target) });
}
};
}

if (currentSetter) {
descr = {
...descr,
set: (v) => {
return this[handler]({ set: currentSetter.bind(target) });
}
};
}

Object.defineProperty(target, signal, descr);

return;
}
if (obj.isFunction(handler)) {
epiConnect(target, signal, handler);
return;
Expand Down Expand Up @@ -492,24 +524,6 @@ export class ViewModel {
}
}

/**
* Invoke the given callback function without the viewModel exposing any props.
* In case the function is asynchronous, the bindings will be disabled as long as the function needs to terminate.
* @param { function } cb - The function to invoke while the bindings are disabled.
* @return { } - The value returned by `cb`.
*/
withoutExposedPropsDo (cb) {
this.clearExposedProps();
let res;
try {
res = cb();
} catch (err) {
console.error(err.message);
}
if (res && res.then) { return res.then(() => { this.reifyExposedProps(); return res; }); } else this.reifyExposedProps();
return res;
}

disableBindings () {
this.getBindingConnections().forEach(conn => conn.deactivate());
}
Expand Down
Loading