-
-
Notifications
You must be signed in to change notification settings - Fork 101
feat(BindableProperty): Reflect prop to attribute #560
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
eab77c9
bd63798
006b799
7f8770e
8c0d718
7629605
ab7dab4
746f194
47987e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,13 @@ export class BindableProperty { | |
| * Creates an instance of BindableProperty. | ||
| * @param nameOrConfig The name of the property or a cofiguration object. | ||
| */ | ||
| constructor(nameOrConfig: string | Object) { | ||
| constructor(nameOrConfig: string | { | ||
| defaultBindingMode?: number, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| reflect?: boolean | {(el: Element, name: string, newVal, oldVal): any}, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is bike-shedding but I'm not a fan of the name.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced by the API design of the function part. This is meant to just reflect a value in an attribute. The callback here really feels like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we restricted it to only choose the name, whjat would we do if it is not always 1:1 prop->attr ?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean like one binding updates 2 attributes? For example Personally I would say this is narrow use cases that can be filled today by using Making the API surface larger and more complex means a bigger Aurelia, more docs and more stuff to support and maintain in the future.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the case where value of property is an object, we dont want Another motivation, is that pretty much everything in Aurelia is intercept-able, So I would choose go with the rest on this. But we can limit it by accepting a function that will take value input an return a string, instead of passing in the whole 4 params like what I did. Can call it |
||
| name?: string, | ||
| attribute?: any, | ||
| changeHandler?: string | ||
| }) { | ||
| if (typeof nameOrConfig === 'string') { | ||
| this.name = nameOrConfig; | ||
| } else { | ||
|
|
@@ -58,17 +64,55 @@ export class BindableProperty { | |
| * @param descriptor The property descriptor for this property. | ||
| */ | ||
| registerWith(target: Function, behavior: HtmlBehaviorResource, descriptor?: Object): void { | ||
| let { reflect } = this; | ||
| behavior.properties.push(this); | ||
| behavior.attributes[this.attribute] = this; | ||
| this.owner = behavior; | ||
|
|
||
| if (reflect) { | ||
| behavior._registerReflection(this.name, typeof reflect === 'function' ? reflect : propToAttr); | ||
| this._configureReflection(target); | ||
| } | ||
|
|
||
| if (descriptor) { | ||
| this.descriptor = descriptor; | ||
| return this._configureDescriptor(descriptor); | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| _configureReflection(target) { | ||
| if (target.__reflectionConfigured__) return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aurelia includes a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some thing I couldn't think of. Can only 馃憤 |
||
|
|
||
| let method = 'propertyChanged'; | ||
| let onChanged = target.prototype[method]; | ||
| let hasHandler = !!onChanged; | ||
|
|
||
| let alteredHandler; | ||
| if (hasHandler) { // avoid ternary to make it consisten with the rest ? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about this part some more... Wouldn't it be more efficient and more clear to integrate |
||
| alteredHandler = function propertyChanged(name, newVal, oldVal) { | ||
| onChanged.call(this, name, newVal, oldVal); | ||
| let { __element__, __reflections__ } = this.__observers__; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, so just asking: is accessing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is really silly of mine, was too hasty to see this. 馃憤
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not see that If you want to maximize perfs and make sure
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I will move the whole block of reflection out of that function, to a separate fn |
||
| if (!__element__) return; | ||
| __reflections__[name].call(this, __element__, name, newVal, oldVal) | ||
| } | ||
| } else { | ||
| alteredHandler = function propertyChanged(name, newVal, oldVal) { | ||
| let { __element__, __reflections__ } = this.__observers__; | ||
| if (!__element__) return; | ||
| __reflections__[name].call(this, __element__, name, newVal, oldVal); | ||
| } | ||
| } | ||
|
|
||
| if (!Reflect.defineProperty(target.prototype, method, { | ||
| configurable: true, | ||
| value: alteredHandler | ||
| })) { | ||
| throw new Error(`Cannot setup property reflection on <${this.name}/> for ${target.name}`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we go for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is because my lack of knowledge about compiler result. I didn't know if |
||
| }; | ||
| target.__reflectionConfigured__ = true; | ||
| } | ||
|
|
||
| _configureDescriptor(descriptor: Object): Object { | ||
| let name = this.name; | ||
|
|
@@ -248,3 +292,18 @@ export class BindableProperty { | |
| observer.selfSubscriber = selfSubscriber; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @private | ||
| * Used for avoid creating mapping function multiple times | ||
| * @param {Element} element | ||
| * @param {string} propertyName | ||
| * @param {any} newValue | ||
| */ | ||
| function propToAttr(element, propertyName, newValue) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me this doesn't take into account the camelCase to kebab-case between properties and html attributes? Also in HTML a few weird properties do not map directly to same-name attribute. I think we have a kind of map somewhere in Aurelia that converts some of those so that it "just work" when binding HTML attributes, shouldn't we re-use it here so that it just works for those? Should this API interact with the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No camelCase to kebab-case is a bad mistake. It should be fixed. Code for the special pairs belong to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. This is for custom attributes, it has nothing to do with built-in HTML attributes.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the |
||
| if (newValue == null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this, but I think the rest of the team is linting for strict
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be fixed. Was trying to roll it out so a bit lazy for |
||
| element.removeAttribute(propertyName); | ||
| } else { | ||
| element.setAttribute(propertyName, newValue); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -330,6 +330,10 @@ export class HtmlBehaviorResource { | |
| let childBindings = this.childBindings; | ||
| let viewFactory; | ||
|
|
||
| if (element !== null) { | ||
| this._setupReflections(element, viewModel); | ||
| } | ||
|
|
||
| if (this.liftsContent) { | ||
| //template controller | ||
| au.controller = controller; | ||
|
|
@@ -401,6 +405,33 @@ export class HtmlBehaviorResource { | |
| return controller; | ||
| } | ||
|
|
||
| /** | ||
| * Allow a bindable property on custom element to register how to reflect prop value to attribute | ||
| * @param {string} propertyName | ||
| * @param {{(element: Element, name: string, newVal, oldVal) => any}} instruction A function with suitable parameters to react for setting attribute on the element | ||
| */ | ||
| _registerReflection(propertyName, instruction) { | ||
| let reflections = this.reflections || (this.reflections = {}); | ||
| if (propertyName in reflections) { | ||
| throw new Error(`Reflection for ${propertyName} was already registered`); | ||
| } | ||
| if (typeof instruction !== 'function') { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't parameter validation go first, before mutating Judging by the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be removed. |
||
| throw new Error('Invalid reflection instruction'); | ||
| } | ||
| reflections[propertyName] = instruction; | ||
| } | ||
|
|
||
| /** | ||
| * @param {Element} element | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why use Typescript-like type annotations in |
||
| * @param {object} viewModel | ||
| */ | ||
| _setupReflections(element, viewModel) { | ||
| if (!this.reflections) return; | ||
| let lookup = this.observerLocator.getOrCreateObserversLookup(viewModel); | ||
| lookup.__reflections__ = this.reflections; | ||
| lookup.__element__ = element; | ||
| } | ||
|
|
||
| _ensurePropertiesDefined(instance: Object, lookup: Object) { | ||
| let properties; | ||
| let i; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for typing that
Object!Should we have a named interface for that rather?
BindableOptions? That would make the code cleaner + give a place to comment on members... I know whatattributedoes but it's not totally obvious, especially now withreflectin the mix.