Git Product home page Git Product logo

ftdomdelegate's Introduction

ftdomdelegate

FT's dom delegate library is a component for binding to events on all target elements matching the given selector, irrespective of whether anything exists in the DOM at registration time or not. This allows developers to implement the event delegation pattern.

Usage

Check out how to include Origami components in your project to get started with ftdomdelegate.

JavaScript

To import ftdomdelegate:

import Delegate from 'ftdomdelegate';
let myDel = new Delegate(document.body);

To instantiate Delegate on the body and listen to some events:

function handleButtonClicks(event) {
  // Do some things
}

function handleTouchMove(event) {
  // Do some other things
}

document.addEventListener('DOMContentLoaded', function() {
  var delegate = new Delegate(document.body);
  delegate.on('click', 'button', handleButtonClicks);

  // Listen to all touch move
  // events that reach the body
  delegate.on('touchmove', handleTouchMove);
});

A cool trick to handle images that fail to load:

function handleImageFail() {
  this.style.display = 'none';
}

document.addEventListener('DOMContentLoaded', function() {
  var delegate = new Delegate(document.body);
  delegate.on('error', 'img', handleImageFail);
});

.on(eventType[, selector], handler[, useCapture])

eventType (string)

The event to listen for e.g. mousedown, mouseup, mouseout, error, click, etc.

selector (string)

Any kind of valid CSS selector supported by matchesSelector. Some selectors, like #id or tag will use optimized functions internally that check for straight matches between the ID or tag name of elements.

null is also accepted and will match the root element set by root(). Passing a handler function into .on's second argument is equivalent to .on(eventType, null, handler).

handler (function)

Function that will handle the specified event on elements matching the given selector. The function will receive two arguments: the native event object and the target element, in that order.

useCapture (boolean)

Whether or not to listen during the capturing (pass in true) or bubbling phase (pass in false). If no value passed in, it will fallback to a 'sensible default', which is true for error, blur and focus events and false for all other types.

.off([eventType][, selector][, handler][, useCapture])

Calling off with no arguments will remove all registered listeners, effectively resetting the instance.

eventType (string)

Remove handlers for events matching this type considering the other parameters.

selector (string)

Only remove listeners registered with the given selector, among the other arguments.

If null passed listeners registered to the root element will be removed. Passing in a function into off's second parameter is equivalent to .off(eventType, null, handler[, useCapture]) (the third parameter will be ignored).

handler (function)

Only remove listeners registered with the given handler function, among the other arguments. If not provided, remove all handlers.

useCapture (boolean)

Only remove listeners with useCapture set to the value passed in. If not provided, remove listeners added with useCapture set to true and false.

.root([element])

element (Node)

Set the delegate's root node. If no element passed in the root node will be deleted and the event listeners will be removed.

.destroy()

Short hand for off() and root(), ie both with no parameters. Used to reset the delegate object.

Credits and collaboration

FT DOM Delegate was developed by FT Labs, part of the Financial Times. It's now maintained by the Origami Team. The developers of ftdomdelegate were Matthew Andrews and Matthew Caruana Galizia. Test engineering by Sam Giles. The API is influenced by jQuery Live.

Migration guide

State Major Version Last Minor Release Migration guide
✨ active 5 N/A migrate to v5
⚠ maintained 4 4.0.6 migrate to v4
⚠ maintained 3 3.1 migrate to v3
╳ deprecated 2 2.2 N/A
╳ deprecated 1 1.0 N/A

ftdomdelegate's People

Contributors

backflip avatar chee avatar dependabot-preview[bot] avatar dependabot[bot] avatar gvonkoss avatar i-like-robots avatar jakechampion avatar joshk avatar lazd avatar mattcg avatar matthew-andrews avatar notlee avatar onishiweb avatar orangemug avatar rowanbeentje avatar rowanmanning avatar samgiles avatar theoleanse avatar wheresrhys avatar wilsonpage 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

ftdomdelegate's Issues

Add ability to exclude events from `off` if prefixed with !

In my reworking of o-dialog I'm creating a single Delegate instance for each dialog instance. The show method attaches a load of listeners and hide removes them. But create also adds a listener to oLayers.destroyAll which I would like to preserve. At present there is no way for me to remove all listeners except this one unless I keep track of each event handler function, which adds undesirable complexity (every handler must be defined on the prototype, then .bind is used to modify it... or I could have multiple delegate instances per dialog )

If I could do this.delegate.off('!oLayers.destroyAll') (or even this.delegate.off('!oLayers.destroyAll !keyup') or this.delegate.off('!*.pattern')) this would make handling this scenario far easier.

Breaking change: swap 4th param of `on` (`eventData`) with more fine grained control over whether or not to `useCapture`

So that developers have more control over how their event listeners are bound. Also because (as far as I can see) we have never used eventData ever in either of our two main applications.

As this is a breaking change this would bring us to v1.0.0.

.on(eventType, selector, handler[, eventData])

To change to:

.on(eventType, selector, handler[, useCapture])

If useCapture is truthy or precisely false use that value when adding event listeners, otherwise use the fallback default provided by captureForType

Add corresponding fourth parameter to off - ie. going from:

.off([eventType][, selector][, handler])

To:

.off([eventType][, selector][, handler][, useCapture])

Again, if useCapture is truthy or precisely false use that value when removing event listeners.

Not sure what to do if useCapture undefined - should we remove all handlers - or just remove the handlers with the fallback useCapture?

(Perhaps if handler undefined then remove all events with useCapture either true or false)

@orangemug, @rowanbeentje, @georgecrawford, @wilsonpage thoughts?

Event listener execution order is backwards when useCapture = true

It seems event listeners fire in the wrong order when useCapture = true. See this Fiddle.

With this markup:

<div id="node0">
  <div id="node1">
    <div id="node2">
    </div>
  </div>
</div>

And with listeners added on the capture phase:

var events = new domDelegate.Delegate(document.querySelector('#node0'));

events.on('click', handler0, true);

events.on('click', '#node1', handler1, true);

events.on('click', '#node2', handler2, true);

For an event triggered on #node2:

document.querySelector('#node2').click();

One would expect the following call order, which matches that of native event listeners.

  • handler0
  • handler1
  • handler2

Instead, we get:

  • handler2
  • handler1
  • handler0

Cleaner syntax for attaching events to root

For consistency with fruitmachine's beautiful events library we could support 2 arguments being passed to on and off (where the last argument is the callback) to attach the root element.

Currently:

delegate.on('click', null, function() {
  alert("The root element got the click");
});

Propose:

delegate.on('click', function() {
  alert("The root element got the click");
});

@georgecrawford @wilsonpage do you guys have any thoughts?

Can we go to v0.3.0 and drop the old syntax too, or would you prefer we kept the old syntax (maybe with a console.warn) for now?

May be able to remove or we may have broken case-sensitive logic

@orangemug ... I just re-discovered this code:-
https://github.com/ftlabs/ftdomdelegate/blob/master/lib/delegate.js#L185

Did we break this when I merged: #45 ?

It makes sense that SVG is case sensitive (it's XML based) but not modern HTML.

Actually do you know what, I don't think we should be supporting the following to be different:-

del.on('click', 'DIV', function() {
  console.log('i\'m a capital div');
});
del.on('click', 'div', function() {
  console.log('i\'m a lowercase div');
});

Do you think it would be sensible to just lowercase all the tags and remove this (now redundant) logic?

multiple event binding

Hi,
First of all, very appreciate to your good lib.
I need multiple event binding such as,

AS-IS

myDelegate.on('mousedown', '.icon', changeClass).on('mouseup', '.icon', changeClass);

TO-BE

myDelegate.on('mousedown mouseup', '.icon', changeClass);

Could you consider this?
Thanks.

Error on uglification

Did a fresh npm install, got a few deprecation warnings about fs-watch-tree and buffer-browserify. Then this error on grunt default:

domdelegate-error-on-uglify

Allow 'rootElement' to be specified as a selector

I want to be able to attach a handler which listens for clicks on the rootElement only. Currently, I can only specify an ID or CSS rule which will match the rootElement, rather than an explicit match for this element only.

Ideal interface:

myDelegate = new Delegate(rootEl)
    .on('click', null, _handleClicksOnRoot);

or:

myDelegate = new Delegate(rootEl)
    .on('click', Delegate.ROOT_ELEMENT, _handleClicksOnRoot);

Behavior is non-standard when handler returns false

With jQuery, returning false within a handler effectively calls event.preventDefault() and event.stopPropagation(). See the fiddle showing this behavior.

The same is true for Gator:

As an added convenience if you return false in your callback it is a shortcut for calling e.preventDefault() followed by e.stopPropagation().

However, it seems ftdomdelegate performs the equivalent of a preventDefault() and stopImmediatePropagation() when a handler returns false, which prevents other handlers at the same level in the DOM from executing. See the fiddle showing this behavior.

This seems non-standard given older libraries have established a consistent behavior, and I feel it should be changed to match.

Reference to original element in the event object

Given the following html

<body>
  <a href="#">
   <img src="https://avatars3.githubusercontent.com/u/235915?s=460">
  </a>
</body>

And the following javascript

var delegate = new domDelegate(document.body);
delegate.on('click', 'a', function(e) {
    console.log(e);
});

I want an item on the event object to give me the node which triggered the event, in the above example the <a>.

Example here shows there is no reference in the event object: http://jsfiddle.net/CV4Zk/

Add a getInstance method that creates/fetches an instance tied to body

Using browserify + debowerify if I want to share a delegate instance between all my commonjs modules then I have to undergo the somewhat artificial step of creating the following module:

var Delegate = require('ftdomdelegate');

module.exports = new Delegate(document.body);

... and then requiring that module wherever I need to use this shared delegator.

It'd be neat if there was a getInstance() method that creates and returns new Delegate(document.body) if it doesn't exist, but returns the existing one if it does exist. Then in each of my commonjs modules I can simply do

var delegate = require('ftdomdelegate').getInstance();

unexpected mouseover/mouseout behavior w/ capture

With a structure like this:

  <div class="my-delegator">
    <div class="my-button">
      <span class="my-button-text">I'm a button</span>
    </div>
  </div>

If I attach a listener on "my-delegator", like so:

  foo = document.querySelectorAll(".my-delegator")
  delegator = new ftdom.Delegator(foo)
  delegator.on("mouseover", ".my-button", someFunc, true)
  delegator.on("mouseout", ".my-button", someFunc, true)

I get the expected mouseover/mousout events when the cursor crosses the border of "my-button". But then I also get a "mouseout" event from "my-button" when the cursor goes from inside the button padding to inside the button text and, even weirder, I get a "mouseover" event from "my-button-text".

I'd expect to only get "mousover" and "mouseout" events when the cursor crosses the border of "my-button". At the very least, the selector passed to on should filter out the "mouseover" events coming from the inner node ("my-button-text"), but I'd also prefer not to get the "mouseout" event when the cursor moves deeper into the subtree.

Waiting for window load event in README examples

function handleImageFail() {
  this.style.display = 'none';
}

window.addEventListener('load', function() {
  var delegate = new Delegate(document.body);
  delegate.on('error', 'img', handleImageFail);
}, false);

Is there a reason to wait for the window load event here? Doesn't it mean that any image errors (from img's in the HTML) will have already been dispatched and missed before the delegate starts listening for them?

ftdomdelegate complete breaking checklist

Origami plan to release major new versions of low level components, which remove technical debt from recent additions such as component branding, and implement more recent proposals such as adding a primary Sass mixin and removing custom classes from many Sass interfaces.

steps

  • Add MIGRATION.md
  • Add a primary mixin, removing other mixins where possible.
  • If a mixin is required which does not output a selector, e.g. for component composition, create an oComponentContent mixin with options like the primary mixin (Content infers the mixin does not output selectors).
  • Replace methods to customise a component's style globally with a mixin oComponentCustomise e.g. so a whitelabel user can customise the style of a primary o-buttons button.
  • Replace mixins which add new variations of a component with an oComponentAddSomething mixin e.g. so a user may output a new o-buttons button, alongside primary buttons etc.
  • Remove custom classnames.
  • Use the $system-code variable for the source param in image service requests, error if not found, update obt to ^ 8.2.2 locally and in circleci config.
  • Remove existing semver ranges e.g. for o-brand and o-icons "o-brand": ">=2.2.0 <4",. We can't add new features to components used with a semver range without back-porting.
  • Use esm consistently over cjs module syntax.
  • Solve existing issues labeled "breaking" if possible.

Can't listen to events on window

I would expect the following code to work (see the Fiddle)

var events = new domDelegate.Delegate(window);

events.on('click', function() {
  console.log('Got a click!');
});

events.on('customEvent', function() {
  console.log('Got a custom event!');
});

window.dispatchEvent(new CustomEvent('customEvent'));

None of the event handlers are called.

I'm using the prebuilt version from http://wzrd.in/standalone/dom-delegate@latest in Chrome 38.0.2125.104 on Mac OS X 10.9.5.

Bower is broken

It points to a file that assumes browserify/node-esque environment.

It would be better if it wasn't (but without having to commit built files)

Tests do not pass with fresh master

I did a fresh npm install, pulled master, and ran grunt buster. I get the following errors:

  TypeError: 'undefined' is not a function (evaluating 'Delegate.prototype.handle.bind(this)')
    at Delegate (./build/dom-delegate.js:9)  Exception should be thrown when no handler is specified in Delegate#on

  ReferenceError: Can't find variable: assert
    at ./test/tests/delegateTest.js:285

The first is due to the fact that PhantomJS does not support Function.prototype.bind().

Can't listen to window.scroll

I have

var del = new Delegate(window);
del.on('scroll', null , function () {}); // fails
del.on('scroll', document , function () {}); // succeeds
del.on('resize', null , function () {}); // succeeds

I'm not sure what I think should be done about this (if anything), but it's not nice behaviour

Inconsistently named installs with bower

I’m not sure how new or how big a problem this is, but as of yesterday I’ve noticed the following behaviour

$ bower install ftdomdelegate
bower cached        https://github.com/ftlabs/ftdomdelegate.git#2.0.3
bower validate      2.0.3 against https://github.com/ftlabs/ftdomdelegate.git#*
bower install       dom-delegate#2.0.3

dom-delegate#2.0.3 bower_components/dom-delegate

which means it gets installed in the bower_components/dom-delegate directory, and must be required as require('dom-delegate’). However, if ftdomdelegate is installed implicitly (e.g. o-share is installed, and ftdomdelegate is a subdependency) then it is installed in bower_components/ftdomdelegate.

a) Is this a bower bug?
b) To avoid the bug and to make the naming of this component more comprehensible should this repo be cloned to dom-delegate for backwards name compatibility, and bower.json in this repo be renamed ftdomdelegate

using [email protected]

TypeError: 'undefined' is not a function (evaluating 'Delegate.prototype.handle.bind(this)') in Konacha

Hey guys,

This library seems to work fine in the browser, but when I run automated tests in Konacha I'm getting this very confusing error:

TypeError: 'undefined' is not a function (evaluating 'Delegate.prototype.handle.bind(this)')

It's saying that Delegate.prototype.handle is not defined when it's called from the constructor. This might fall under the purview of someone else's problem (eg. Konacha, PhantomJS, etc...) but I wanted to run it by you guys here first in case there was some obvious answer staring me in the face.

FWIW, even though I understand why it's being done, I've never written (or seen) this done inside a constructor:
this.handle = Delegate.prototype.handle.bind(this);

So perhaps this is a naive question and my JS-fu is failing me here.

Thanks!

stopPropagation does not stop propagation to listeners added with ftdomdelegate

Basically, calling stopPropagation on a bubble phase listeners should prevent other delegated listeners further up the DOM from being called, but it doesn't.

See this Fiddle here for an example with bubble phase listeners for and this Fiddle for an example with capture phase listeners.

Furthermore, listeners added with addEventListener are still called because the actual call to stopPropagation happens at the level of the root element, stopping propagation above it, but not above the child element where the user called stopPropagation at, see this Fiddle for an example. This is non-trivial to solve, so it should at least be documented. Note that jQuery exhibits the same behavior (but does correctly stop propagation to other listeners added further up the DOM).

Add destroy() method

Apparently this has been discussed and rejected, but I think a destroy() method would be useful. Otherwise to ensure all DOM references and event listeners are correctly unbound, we have to run:

    // Destroy the internal reference to the root element
    myDelegate.root();

    // Remove the event listeners
    myDelegate.off();

    myDelegate = undefined;

We should encourage users to clean up when finished with a Delegate, so could we make it easier. I propose a simple destroy() which does the above.

Maybe we could include some examples and improve the usage section of README.md

Currently the usage section just says:

window.addEventListener('load', function() {
    new Delegate(document.body);
}, false);

Which whilst accurate isn't really reflective of how you might actually use dom-delegate.

Suggest that changes to something like:

function handleButtonClicks(event) {
  // do some things
}

window.addEventListener('load', function() {
  var del = new Delegate(document.body);
  del.on('click', 'button', handleButtonClicks);
}, false);

Browser compatible bower build

Given that bower is used for front-end package management. What are your thoughts on making a browserified build and have the bower.json set the main to that file?

I'm currently trying to use this with bower_rails which when using ftdomdelegate, errors out due to the module.

My only options currently is to download the build code, or try and mangle browserify into the ftdomdelegate in the bower rails loading.

If this is something you're interested in, I'll totally do the labor to make it happen.

Is it necessary for us to be able to support multiple eventType in a single #on?

There are two main inconsistencies:-

  • In the .on(eventType, selector, handler[, eventData]) method the eventType supports multiple types and is space separated whereas multiple selector's are comma separated.
  • .on supports multiple eventTypes, .off doesn't.

Given these inconsistencies could we discuss whether it is necessary or useful for us to be able to support multiple eventType's in a single #on? Is that even useful? Can we drop the support for multiple, space separated eventType's?

I know this is a breaking API change (can we get away with just upping the major version number to v1.0.0?) but I think the less this module does, the better. @mattcg may have some ideas on how / if we should proceed...

Update - it looks like off does support multiple eventType's but this functionality is undocumented

IE11 SCRIPT5007: Unable to get property 'call' of undefined or null reference

Bug report

What

On IE11 every time I click anywhere on the page I get
SCRIPT5007: Unable to get property 'call' of undefined or null reference

Details

  • how to reproduce it: click anywhere on the page
  • describe the environment you are having this problem in
    • Browser: IE11 (11.329.19041.0)
    • Device: Desktop
    • provide screenshots to illustrate your problem:
      Screen Shot 2020-10-15 at 12 59 17 PM

Additional information

This happens here:

line 363:

else if (listener.matcher.call(target, listener.matcherParam, target)) {
          toFire.push([event, target, listener]);
 } 

Docs from Microsoft: https://docs.microsoft.com/en-us/scripting/javascript/misc/object-expected

Why are there two names for this module?

I've got both ftdomdelegate and dom-delegate installed in my bower_components when using Origami modules.

Why are there two different names? Which is the correct one to use?

Complete IE8 Support

Creating this issue from an email thread @richard-still-ft, @triblondon, @roland-vachter - looping in @wheresrhys.

The question was:-

Thanks Roland. @matthew-andrews we need IE8 support in ftdomdelegate. What is the best way to go about adding it? Should we do the dev and create a pull request? Or would you rather add it?

My answer is:-

I think Rhys has done much of the work for IE8 support in this module... #43 (I've just merged this and am in the process of making a release)

Although I understand support isn't perfect (I think it only works for events that bubble natively - so no support for error, blur, focus, scroll, resize, etc events)

We have the following options:-

  • Decide the existing support is sufficient and the documentation clear enough
  • Improve the documentation in origami to say that ftdomdelegate is on A-List but only for non-bubbling events. (Or at least experience shoudn't explicitly rely on non-bubbling events to function)
  • Improve documentation in ftdomdelegate in the same way as above.
  • Find a way to offer complete support for IE8.
  • Remove ftdomdelegate from the A-List (:disappointed:)

IE8 Support

In #42 @wheresrhys said:

I'm finding it's not the only place where ftdomdelegate doesn't cut our ie8 mustard (e.g. its use of Node). To be able to use it we'll need to either

  1. Introduce some regressive compatibility changes
  2. Drop ie8 support, as @commuterjoy is very keen to do
  3. Properly fork it as opposed to wrapping it for use in
    Financial-Times/ft-origami#189
  4. Have a compatibility version alongside the main one
    Thoughts? @triblondon

My personal feeling is we should try to explore option 1 first.

Can't listen to window.scroll

I have

var del = new Delegate(window);
del.on('scroll', null , function () {}); // fails
del.on('scroll', 'html' , function () {}); // fails
del.on('resize', null , function () {}); // succeeds

I'm not sure what I think should be done about this, but it's not nice behaviour. I'm gonna need to set up an additional delegate instance to cope with it

var del = new Delegate(document);
del.on('scroll', null , function () {}); // will succeed

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.