This commit is contained in:
Keith Cirkel 2020-02-13 13:16:39 +00:00
Родитель 27ea6d3cfb
Коммит 9d19ccd480
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E0736F11348DDD3A
10 изменённых файлов: 124 добавлений и 59 удалений

Просмотреть файл

@ -5,13 +5,18 @@
</head> </head>
<body> <body>
<hello-controller> <hello-controller>
<input data-target="HelloController.nameTarget" type="text" /> <input data-target="hello-controller.nameTarget" type="text" data-action="click:hello-controller#greet"/>
<button data-action="click->HelloController#greet"> <ajax-form>
<button data-action="click:hello-controller#greet">
Greet Greet
</button> </button>
<span data-target="HelloController.outputTarget"> </span> <span data-target="hello-controller.outputTarget"> </span>
</ajax-form>
</hello-controller> </hello-controller>
<script src="dist/index.js"></script> <script src="dist/index.js"></script>
</body> </body>

26
src/bind.ts Normal file
Просмотреть файл

@ -0,0 +1,26 @@
import {dasherize} from './dasherize'
import {wrap} from './wrap'
import {getMethods} from './getmethods'
/**
* Bind `[data-action]` elements from the DOM to their actions.
*/
export function bindEvents(classObject: any) {
const methods = getMethods(classObject.prototype)
const name = dasherize(classObject.name)
wrap(classObject.prototype, 'connectedCallback', function (this: HTMLElement) {
const selectors = methods.map(method => `[data-action*=":${name}#${method}"]`).join(',')
for(const el of this.querySelectorAll(selectors)) {
// Match the pattern of `eventName:constructor#method`.
for(const binding of (el.getAttribute('data-action')||'').split(' ')) {
const [rest, method] = binding.split('#')
const [eventName, handler] = rest.split(':')
if (handler === name) {
el.addEventListener(eventName, (event: Event) => {
if (event.target === el) (this as any)[method](event)
})
}
}
}
})
}

1
src/dasherize.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
export const dasherize = (str: string) => str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase()

11
src/getmethods.ts Normal file
Просмотреть файл

@ -0,0 +1,11 @@
export const getMethods = (obj: any) => {
let methods = []
for (const name of Object.getOwnPropertyNames(obj)) {
if (name === 'constructor') continue
const descriptor = Object.getOwnPropertyDescriptor(obj, name)
if (descriptor && descriptor.value && typeof descriptor.value === 'function') {
methods.push(name)
}
}
return methods
}

Просмотреть файл

@ -1,51 +1,3 @@
function *bindings(selector: string, callingCtor: string) { export {bindEvents} from './bind'
for(const binding of selector.split(' ')) { export {register} from './register'
const [_, eventName, ref, ctor, method] = binding.match(/(?:(\w+)((?:@)\w+)?->)(\w+)#(\w+)/) || [] export {target, targets, assertTargets} from './target'
if (ctor === callingCtor) yield [eventName, ref, method]
}
}
export function bind(classObject: any) {
const oldConnectedCallback = classObject.prototype.connectedCallback
classObject.prototype.connectedCallback = function () {
if (oldConnectedCallback) oldConnectedCallback.call(this)
for(const el of this.querySelectorAll('[data-action]')) {
for (const [eventName, ref, method] of bindings(el.getAttribute('data-action')||'', classObject.name)) {
let receiver = this
let delegate = el
if (ref === 'window') {
receiver = delegate = window
} else if (ref === 'document') {
receiver = delegate = document
}
receiver.addEventListener(eventName, (event: Event) => {
if (event.target === delegate) this[method](event)
})
}
}
}
}
export function register(classObject: any) {
const name = classObject.name.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase()
if (!window.customElements.get(name)) {
window[classObject.name] = classObject;
window.customElements.define(name, classObject);
}
}
export function target(proto: any, propertyKey: string) {
Object.defineProperty(proto, propertyKey, {
configurable: true,
get: function() {
const target = this.querySelector(
`[data-target=*"${this.constructor.name + "." + propertyKey}"]`
);
Object.defineProperty(this, propertyKey, {
value: target,
writable: true
});
return target;
},
});
}

Просмотреть файл

@ -1,12 +1,15 @@
import {bind, register, target} from './helpers' import {bindEvents, register, target, assertTargets} from './helpers'
@register @register
@bind @assertTargets
@bindEvents
class HelloController extends HTMLElement { class HelloController extends HTMLElement {
@target outputTarget!: HTMLElement; @target outputTarget!: HTMLElement;
@target nameTarget!: HTMLInputElement; @target nameTarget!: HTMLInputElement;
@target buttonTarget!: HTMLButtonElement;
greet() { greet() {
this.dataset.foo = 'foo'
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`; this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`;
} }
} }

15
src/register.ts Normal file
Просмотреть файл

@ -0,0 +1,15 @@
import {dasherize} from './dasherize'
/**
* Register the controller as a custom element.
*
* The classname is converted to a approriate tag name.
*
* Example: HelloController => hello-controller
*/
export function register(classObject: any) {
const name = dasherize(classObject.name)
if (!window.customElements.get(name)) {
window[classObject.name] = classObject;
window.customElements.define(name, classObject);
}
}

40
src/target.ts Normal file
Просмотреть файл

@ -0,0 +1,40 @@
import {dasherize} from './dasherize'
/**
* Create a property on the controller instance referencing the element with a
* `data-target` element of the same name.
*/
const createSelector = (receiver: Element, key: string) => `[data-target~="${dasherize(receiver.constructor.name) + "." + key}"]`
export function target(proto: object, key: string) {
Object.defineProperty(
proto,
key,
{
configurable: true,
get: function() {
return this.querySelector(createSelector(this, key))
},
}
);
}
export function targets(proto: object, key: string) {
Object.defineProperty(
proto,
key,
{
configurable: true,
get: function() {
return this.querySelectorAll(createSelector(this, key))
},
}
);
}
export function assertTargets(_: object) {
// This is an empty stub that does nothing at runtime, but can be used by
// compilers to generate code that asserts Targets are the Element types they
// declare themselves to be, otherwise throw an Invariant Error.
}

11
src/wrap.ts Normal file
Просмотреть файл

@ -0,0 +1,11 @@
export const wrap = (obj: any, name: string, fn: (...args: any[]) => any) => {
if (!obj[name]) {
obj[name] = fn
} else {
const oldFn = obj[name]
obj[name] = function () {
oldFn.call(this)
fn.call(this)
}
}
}

Просмотреть файл

@ -8,7 +8,8 @@
"allowJs": true, "allowJs": true,
"lib": [ "lib": [
"es6", "es6",
"dom" "dom",
"dom.iterable"
], ],
"target": "ES2020", "target": "ES2020",
"rootDir": "src", "rootDir": "src",