Git Product home page Git Product logo

jsaction's Introduction

JsAction

JsAction is a tiny event delegation library that allows decoupling the DOM nodes on which the action occurs from the JavaScript code that handles the action.

The traditional way of adding an event handler is to obtain a reference to the node and add the event handler to it. JsAction allows us to map between events and names of handlers for these events via a custom HTML attribute called jsaction.

Separately, JavaScript code registers event handlers with given names which need not be exposed globally. When an event occurs the name of the action is mapped to the corresponding handler which is executed.

Finally, JsAction uncouples event handling from actual implementations. Thus one may late load the implementations, while the app is always able to respond to user actions marked up through JsAction. This can help in greatly reducing page load time, in particular for server side rendered apps.

Building

JsAction is built using the Closure Compiler. You can obtain a recent compiler from the site.

JsAction depends on the Closure Library. You can obtain a copy of the library from the GitHub repository.

The compiler is able to handle dependency ordering automatically with the --only_closure_dependencies flag. It needs to be provided with the sources and any entry points.

See the files dispatch_auto.js, eventcontract_auto.js, and eventcontract_example.js for typical entry points.

Here is a typical command line for building JsAction's dispatch_auto.js:

find path/to/closure-library path/to/jsaction -name "*.js" |
    xargs java -jar compiler.jar  \
    --output_wrapper="(function(){%output%})();" \
    --only_closure_dependencies \
    --closure_entry_point=jsaction.dispatcherAuto

Using drop-in scripts

If you would like to test out JsAction, you can link precompiled scripts into your page.

<script src="https://www.gstatic.com/jsaction/contract.js"></script>

...

<script src="https://www.gstatic.com/jsaction/dispatcher.js" async></script>

Usage

You can play around with JsAction already set up with the following directions at https://jsfiddle.net/q2eacgs7/.

In the DOM

Actions are indicated with the jsaction attribute. They are separated by ;, where each one takes the form:

[eventType:]<namespace>.<actionName>

If an eventType is not specified, JsAction will assume click.

<div id="container">
  <div id="foo"
       jsaction="leftNav.clickAction;dblclick:leftNav.doubleClickAction">
    some content here
  </div>
</div>

In JavaScript

Set up

const eventContract = new jsaction.EventContract();

// Events will be handled for all elements under this container.
eventContract.addContainer(document.getElementById('container'));

// Register the event types we care about.
eventContract.addEvent('click');
eventContract.addEvent('dblclick');

const dispatcher = new jsaction.Dispatcher();
eventContract.dispatchTo(dispatcher.dispatch.bind(dispatcher));

Register individual handlers

/**
 * Do stuff when actions happen.
 * @param {!jsaction.ActionFlow} flow Contains the data related to the action
 *     and more. See actionflow.js.
 */
const doStuff = function(flow) {
  // do stuff
  alert('doStuff called!');
};

dispatcher.registerHandlers(
    'leftNav',                       // the namespace
    null,                            // handler object
    {                                // action map
      'clickAction' : doStuff,
      'doubleClickAction' : doStuff
    });

Late loading the JsAction dispatcher and event handlers

JsAction splits the event contract and dispatcher into two separably loadable binaries. This allows applications to load the small event contract early on the page to capture events, and load the dispatcher and event handlers at a later time. Since captured events are queued until the dispatcher loads, this pattern can ensure that user events are not lost even if they happen before the primary event handlers load.

Visit http://jsfiddle.net/880m0tpd/4/ to try out a working example.

Load the contract early in the page

Just like in the regular example, in this example the event contract is loaded very early on the page, ideally in the head of the page.

<script id="contract" src="https://www.gstatic.com/jsaction/contract.js"></script>
<script>
  const eventContract = new jsaction.EventContract();

  // Events will be handled for all elements on the page.
  eventContract.addContainer(window.document.documentElement);

  // Register the event types handled by JsAction.
  eventContract.addEvent('click');
</script>

<button jsaction="button.handleEvent">
  click here to capture events
</button>

The event contract is configured to capture events for the entire page. Since the dispatcher and event handlers are not loaded yet, the event contract will just queue the events if the user tries to interact with the page. These events can then be replayed after the dispatcher and event handlers are loaded, which will be shown in this example next. This will ensure that no user interaction is lost, even if it happens before the code is loaded.

Loading the dispatcher and replaying events

At any point later in the page, the dispatcher and event handlers can be loaded and any queued events can be replayed.

After the dispatcher and event handler code loads, you will configure the dispatcher just like in the regular example:

// This is the actual event handler code.
function handleEvent() {
  alert('event handled!');
}

// Initialize the dispatcher, register the handlers, and then replay the queued events.
const dispatcher = new jsaction.Dispatcher();
eventContract.dispatchTo(dispatcher.dispatch.bind(dispatcher));
dispatcher.registerHandlers(
    'button',
    null,
    { 'handleEvent': handleEvent });

There is some new code to replay the queued events:

// This code replays the queued events. Applications can define custom replay
// strategies.
function replayEvents(events, jsActionDispatcher) {
  while (events.length) {
    jsActionDispatcher.dispatch(events.shift());
  }
}

// This will automatically trigger the event replayer to run if there are
// queued events.
dispatcher.setEventReplayer(replayEvents);

Now any events that happen during page load before the JS has loaded will be replayed when the primary JS does load, ensuring that user interactions are not lost.

Common events to use with JsAction

This is a list of common events to listen to when configuring JsAction, although it's not comprehensive.

contract.addEvent('click');
contract.addEvent('dblclick');
contract.addEvent('focus');
contract.addEvent('blur');
contract.addEvent('keydown');
contract.addEvent('keyup');
contract.addEvent('keypress');
contract.addEvent('load');
contract.addEvent('mouseover');
contract.addEvent('mousein');
contract.addEvent('mouseout');
contract.addEvent('mouseleave');
contract.addEvent('submit');
contract.addEvent('touchstart');
contract.addEvent('touchend');
contract.addEvent('touchmove');

jsaction's People

Contributors

blickly avatar concavelenz avatar mknichel avatar nanaze avatar ruidlopes avatar shicks avatar slaks avatar vrana avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsaction's Issues

Add compiled JS under dist or as tarballs in releases

There is currently no way to try out JsAction without installing/running Closure Compiler. This prevents, or at least discourages, many people from investigating the library.

If there was a way to easily add JsAction via a script tag, it could be used in CodePen/JS Bin/etc examples. For example, I believe JsAction could a great way to implement the Action Creators portion of the Flux architecture (map browser events to semantic events/functions) but I can't easily confirm or share examples with others.

image

Problems when jsNamespace and jsAction attributes share the same node.

Example markup:

<div id="timeline" jsnamespace="Timeline">
  <div id="tweet" jsnamespace="Tweet" jsaction="viewDetail">
    <div>...</div>
    <div>...</div>
    <div>...</div>
  </div>
</div>

Assume that (via data-jsaction-events attr or programatically) the container for each namespace is the node with the corresponding jsnamespace attr.

My goal:
Define "viewDetail" jsaction in the "Tweet" namespace (because the alternative is to add it to each of the three subnodes)

What happens:

  1. a browser event fires at or within the tweet node.
  2. for each container that handles that type of browser event..
    a) bubble up to the first jsaction attr, but stop if the namespace container is reached first (https://github.com/google/jsaction/blob/master/eventcontract.js#L480)
    b) if the namespace is not specified in the jsaction, resume DOM walk, starting from the found jsAction node. (https://github.com/google/jsaction/blob/master/eventcontract.js#L721)

For the Tweet container, the DOM walk at 2a will quit before it reaches that container so the viewDetail action will be ignored.

Summary:
jsActions defined in the same node as jsNamespace are ignored.

Notes/Fixes/Workarounds

  1. The DOM walks in 2a and 2b could be made consistent by having 2a quit on the container node, instead of before it:
    Change https://github.com/google/jsaction/blob/master/eventcontract.js#L480 to...
    for (var node = target; node && node != container.parentNode;
    (For completeness, the alternate condition at https://github.com/google/jsaction/blob/master/eventcontract.js#L457 above should probably be changed in a similar way)
  2. A workaround is to set the namespace container to a parent node (or document.body).
  3. Another workaround is to move the jsAction to each of the three child nodes (but feels wrong)
<div id="timeline" jsnamespace="Timeline">
  <div id="tweet" jsnamespace="Tweet">
    <div jsaction="viewDetail">...</div>
    <div jsaction="viewDetail">...</div>
    <div jsaction="viewDetail">...</div>
  </div>
</div>
  1. Pre-qualifying the jsaction with the desired namespace (i.e. "Tweet.viewDetail") has no effect, because the Tweet container DOM walk still quits before it reaches the jsAction.
  2. Even if we wanted the viewDetail action to be associated with the Timeline namespace, that fails with the above markup because the qualifying namespace code at 2b will always find the Tweetnamespace attr first (unlike 2a it looks at the container node). We could fix this by prequalifying the jsAction as "Timeline.viewDetail" but this may be more information than we want the child component markup to know. For the Timeline container, that DOM walk will reach the viewDetail action. But then 2b kicks in to find the qualifying namespace. Because 2b's DOM walk starts at the jsaction node, it will immediately see the Tweet jsnamespace attribute and assign it to the action. Given that the container is for Timeline, assigning the jsAction with the Tweet namespace is counter-intuitive

Questions:

  1. Is the inability to have a jsAction share a node with its jsNamespace by design?
  2. Any advice re. pros and cons of having container node differ from the node with that jsNamespace attribute (when there are several sibling nodes with the same jsNamespace attr, I'm assuming it makes sense for the container to be on a parent node - but then is there an issue with the DOM walk in 2b finding the wrong qualifying namespace?)

Dispatch browser events

Hello,
When I try to dispatch an event in this way:

document.querySelector('input').dispatchEvent(new KeyboardEvent('keyup', {key: "a", code: "KeyA", keyCode: 65}))

nothing happens. Why?
How to dispatch browser events on an element?

Why the software history was not kept?

Hi there,

I'm a researcher studying software evolution. As part of my current research, I'm studying the implications of open-sourcing a proprietary software, for instance, if the project succeed in attracting newcomers. However, I observed that some projects, like jsaction, deleted their software history.

b9c20d5

Knowing that software history is indispensable for developers (e.g., developers need to refer to history several times a day), I would like to ask jsaction developers the following four brief questions:

  1. Why did you decide to not keep the software history?
  2. Do the core developers faced any kind of problems, when trying to refer to the old history? If so, how did they solve these problems?
  3. Do the newcomers faced any kind of problems, when trying to refer to the old history? If so, how did they solve these problems?
  4. How does the lack of history impacted on software evolution? Does it placed any burden in understanding and evolving the software?

Thanks in advance for your collaboration,

Gustavo Pinto, PhD
http://www.gustavopinto.org

[question] How is the app functional before implementations have loaded?

The README says:

JsAction uncouples event handling from actual implementations. Thus one may late load the implementations, while the app is always able to respond to user actions marked up through JsAction.

But what handles responses to user actions before the implementations have loaded? I get that the dispatcher could observe the actions but then what does it do with them?

jsaction can work against touch adjstument behavior of Chrome

One problem with event delegation in general is that it can defeat efforts from the browser to try to guess what elements are activateable. Eg. Chrome has a 'touch adjustment' heuristic which is necessary to get good touch targeting on websites not specifically designed for touch. Without any event handler, :active/:hover effect or other indication that an element is tappable, Chrome will slightly bias taps towards other elements under the contact area of the finger. Safari and IE do something like this too, but details aren't available (and they probably do a better job at avoiding issues than us!).

Here is an example where using jsaction in conjunction with Chrome's touch adjustment behavior triggered confusion / problems.

Perhaps jsaction should do something to continue to give the browser a signal that such elements are really tappable? Eg. Maybe it should register dummy event handlers on the elements? I'm happy to make changes to Blink here if we can agree on some protocol, but it should be based around standard APIs (not library-level conventions). Eg. we don't currently take ARIA role attributes into account in this heuristic, but perhaps we should? Longer term, maybe we should try to standardize an API like Element.supportsActivation which you could explicitly set to true when our heuristics don't guess correctly?

Thoughts?

Open source the rest of Wiz

jsaction is a part of the Wiz framework which Google famously uses for most of its consumer web apps.

It seems like Wiz was decided to not be made open source, at the time, so as not to add to the war that was going on between React and Angular. Well, the war has passed and in these quieter times, I think Wiz would prove to be a fresh alternative to these and other JS heavy frameworks by being a more conservative middle ground.

CC: @cramforce

jsaction.createKeyboardEvent does not work in Firefox 57.0+

Reproduce: run tests in replay_test.js

Firefox has implemented KeyboardEvent.initKeyboardEvent, but it does not correctly set the keyCode or charCode.

Based on https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent, it looks like the preferred way to create these events is with the KeyboardEvent constructor (both initKeyEvent and initKeyboardEvent are deprecated). The most surprising part of this to me, is that MDN also says Gecko will not implement initKeyboardEvent, but it appears that something has changed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.