aurelia / templating-binding

An implementation of the templating engine's Binding Language abstraction which uses a pluggable command syntax.
MIT License
32 stars 26 forks source link

[Discussion] Allow three bindings chain to support event self target #101

Closed bigopon closed 7 years ago

bigopon commented 7 years ago

I was thinking of an Aurelia way that make event handling on self target (event that originates from that target, not its descendants).

Syntax would look like this

<a click.delegate.self='only_when_click_me()'>A self listening anchor</a>
<!-- or can be flexible and this will also work -->
<a click.self.delegate='only_when_click_me()'>A self listening anchor</a>
  var acceptableDelegationStrategies = ['trigger', 'delegate', 'capture'];
  /**
   * @param {number[]} parts attribute parts
   * @returns {number} command part index in attribute parts
   */
  function findCommandPartIndex(parts) {
    var selfPartIdx = parts.indexOf('self');
    if (selfPartIdx < 1) return 0;
    if (selfPartIdx === 1 && acceptableDelegationStrategies.indexOf(parts[2]) !== -1) return 2;
    if (acceptableDelegationStrategies.indexOf(parts[1]) !== -1) return 1;
    return 0;
  }
  /**
   * @param {string[]} parts
   */
  function trimAttributeParts(parts) {
    for (var i = 0, ii = parts.length; i < ii; i++)
      parts[i] = parts[i].trim();
  }

class TemplatingBindingLanguage extends BindingLanguage {
// ....
inspecAttribute(resources, elementName, attrName, attrValue) {
    var parts = attrName.split('.');

    info.defaultBindingMode = null;

    if (parts.length === 3) {
      trimAttributeParts(parts);
      var commandPartIndex = findCommandPartIndex(parts);
      if (commandPartIndex) {
        info.attrName = parts[0] + '.self';
        info.attrValue = attrValue;
        info.command = parts[commandPartIndex];
      } else {
        info.command = null;
        console.warn('3 bindings chain only supports event.');
      }
    } else if (parts.length === 2) {
      trimAttributeParts(parts);
      // ...... the rest same
}
}
class EventManager {
  // ...
  addEventListener(target, targetEvent, callback, delegate) {
    let targetEventParts = targetEvent.split('.');
    let isSelfTarget = targetEventParts.length === 2;
    if (isSelfTarget) {
      targetEvent = targetEventParts[0];
      target.selfEvents = target.selfEvents || {};
      target.selfEvents[targetEvent] = true;
    }
    return (this.eventStrategyLookup[targetEvent] || this.defaultEventStrategy)
      .subscribe(target, targetEvent, callback, delegate);
  }
}
  target.addEventListener(targetEvent, callback, false);

  return function() {
      target.removeEventListener(targetEvent, callback);
  };

With:

    let handleTrigger = (event) => {
      let evtTarget = event.target;
      if (evtTarget.selfEvents && !evtTarget.selfEvents[event.type]) return;
      callback(event);
    };

    target.addEventListener(targetEvent, handleTrigger, false);

    return function() {
      target.removeEventListener(targetEvent, handleTrigger);
    };
function handleDelegatedEvent(event) {
  let interceptInstalled = false;
  event.propagationStopped = false;
  let target = findOriginalEventTarget(event);

  while (target && !event.propagationStopped) {
    if (target.delegatedCallbacks) {
      let evtType = event.type;
      if (target.selfEvents && !target.selfEvents[evtType]) return;
      let callback = target.delegatedCallbacks[evtType];
      // Original of 3 lines above:
      // let callback = target.delegatedCallbacks[event.type];
      if (callback) {
        if (!interceptInstalled) {
          interceptStopPropagation(event);
          interceptInstalled = true;
        }
        callback(event);
      }
    }

    target = target.parentNode;
  }
}
function handleCapturedEvent(event) {
  let interceptInstalled = false;
  event.propagationStopped = false;
  let target = findOriginalEventTarget(event);

  let orderedCallbacks = [];
  /**
   * During capturing phase, event 'bubbles' down from parent. Needs to reorder callback from root down to target
   */
  while (target) {
    if (target.capturedCallbacks) {
      let evtType = event.type;
      if (target.selfEvents && !target.selfEvents[evtType]) return;
      let callback = target.capturedCallbacks[evtType];
      // Original of 3 lines above:
      // let callback = target.delegatedCallbacks[event.type];
      if (callback) {
        if (!interceptInstalled) {
          interceptStopPropagation(event);
          interceptInstalled = true;
        }
        orderedCallbacks.push(callback);
      }
    }
    target = target.parentNode;
  }
  for (let i = orderedCallbacks.length - 1; i >= 0; i--) {
    let orderedCallback = orderedCallbacks[i];
    orderedCallback(event);
    if (event.propagationStopped) {
      break;
    }
  }
}

Does this look good ?

bigopon commented 7 years ago
jdanyow commented 7 years ago

I think you could do something like: foo.delegate="doSomething() & self" if you created a SelfBindingBehavior. Likewise, <input keydown.delegate="onSubmit() & keys:13"> would be possible.

What do you think?

bigopon commented 7 years ago

I like the 3 bindings syntax but binding behavior looks more Aurelia to me. Maybe consider making self binding behavior built in ?

EisenbergEffect commented 7 years ago

I prefer to go with the binding behavior since it leverages an existing capability and doesn't introduce new syntax.

bigopon commented 7 years ago

How would the syntax for keys be like ?

<input keydown.delegate="onSubmit() & keys:13:16:17 "/>
<input keydown.delegate="onSubmit() & keys:'enter':'shift':'ctrl' "/>
<input keydown.delegate="onSubmit() & keys:[13,16,17]" />
<input keydown.delegate="onSubmit() & keys:['enter', 'shift', 'ctrl']" />

<!-- or a mix? -->
<input keydown.delegate="onSubmit() & keys:13:'shift':'tab':'ctrl':18 " />
bigopon commented 7 years ago

@EisenbergEffect what do you think about the keys binding behavior ?

edit: I mean should it be built in. If so what should be the syntax ?

EisenbergEffect commented 7 years ago

I'm not sure if it should be built in yet. It would depend on how large it is perhaps and whether the implementation is generic enough to be used widely. I think the best way to handle this at first is via a plugin. Once it's built that way we can consider whether to build it in somewhere.

bigopon commented 7 years ago

Yeah that's what concerned me, it would add around at least 50 lines something. And doesn't seem to be that neccessary.