frend.co icon indicating copy to clipboard operation
frend.co copied to clipboard

Introduce event callback option for all components

Open adamduncan opened this issue 8 years ago • 9 comments

A lot of quality plugins have event emitter setups, but that might be a bit overkill for Frend bits.

Would be nice for this to be consistent amongst components. Is it as simple as adding an onToggle property to the options object that takes a function and passes the instance?

var myTabs = Frtabs({
  onToggle: function (tabs) {
    // do something
  }
});

As part of this, do we need to start storing things like the active element on each instance, so they can be referenced in these callbacks? I.e. tabs.activeTab

adamduncan avatar May 26 '16 15:05 adamduncan

Initial tester, any idea how we're going to return the instance?

thomasdigby avatar Jun 05 '16 13:06 thomasdigby

Has there been any more development on this? I'm specifically trying to use the off canvas component, but discovered there aren't any open and close events I can hook into, which is surprising.

Ambient-Impact avatar Apr 03 '18 18:04 Ambient-Impact

Hey @Ambient-Impact - thanks for enquiring on this. Unfortunately we lost a bit of steam on v2 with other obligations. In hindsight, exposing show/hide methods in the original API would have been super helpful.

Were you hoping to fire events when opening/closing the off-canvas panel? In which case, binding your own event handlers on the buttons will do the trick: https://codepen.io/adamduncan/pen/JLaoRM?editors=0010

Manually invoking open/close from elsewhere in your code would also be possible, but at this point would be a case of replicating the logic in _showPanel and _hidePanel I'm afraid.

Hope that helps.

adamduncan avatar Apr 04 '18 14:04 adamduncan

No worries. I started to replicate those event handlers, but then realized I also needed to account for the user hitting the escape key or clicking outside of the panel, and it started to get more complicated. I'm implementing an overlay (and using ally.js to trap focus in the off canvas panel), so I need a way to accurately know when the panel is opened and closed. After a bit of tinkering, I decided to use MutationObserver to watch for changes to aria-hidden on the panel. I'll probably just fall back to not using an overlay if it isn't supported. It still feels like a bit of a hack, but it's the next best thing.

Ambient-Impact avatar Apr 04 '18 16:04 Ambient-Impact

Sure, I see. Sorry about that. MutationObserver sounds like a nice, modern approach.

Other a11y packages (like Micromodal) have nice open/close event APIs. If you're implementing the off-canvas in conjunction with a modal overlay, I wonder whether the case could be made for treating the off-canvas as a modal-of-sorts as well 🤔

adamduncan avatar Apr 04 '18 16:04 adamduncan

@Ambient-Impact I've done something similar using

$(opts.contentSelector).on('transitionend', function()... )

which was better supported than MutationObserver when it was written (I'd rather use MutationObserve today)

results here: https://italia.github.io/design-web-toolkit/components/detail/offcanvas.html

gunzip avatar Apr 04 '18 16:04 gunzip

Nice one @gunzip!

adamduncan avatar Apr 04 '18 16:04 adamduncan

Hey @Ambient-Impact I am using MutationObserver, but it fires twice. Do you have the same problem?

I have the following script to activate a fadIn/fadeOut when the offCanvas opens/closes:

// Offcanvas
	var myOffcanvas = Froffcanvas();
	
	// Listen to offCanvas close/open
	var offCanvasObserver = new MutationObserver(function(mutations) {
	  mutations.forEach(function(mutation) {
	    if (mutation.type == "attributes") {
	      if (mutation.target.getAttribute('aria-hidden') == "true") {
	         $('.overlay-offcanvas').fadeOut( 60 );
	          console.log('closes');
	      } else {
	         $('.overlay-offcanvas').fadeIn( 60 );
	         console.log('open');
	      }      
	    }
	  });
	});

	var config = {
	  attributes: true, //configure it to listen to attribute changes
	  attributeFilter: ['aria-hidden'] // filter your attributes
	};

	// offCanvasObserver.observe(document.getElementById('offcanvas-sidebar'), config);
	offCanvasObserver.observe(document.getElementById('offcanvas-cart'), config);

`

There must be something firing it twice, but I can not find what it is. I have two offCanvas, and they don't work well together.

kevinmamaqi avatar May 04 '18 18:05 kevinmamaqi

@kevinmamaqi I've written my own complex wrapper around Froffcanvas, and it seems to work fine with more than one offcanvas on the page. With regards to your issue with MutationObserver, I think it might be due to the attribute changing more than once and the observer picking that up. There's no guarantee that it won't fire more than once, so what I've done is to instruct it to also keep the old value of aria-hidden, so I can compare them and only do something if it goes from true to false or vice versa. The below is a private function from my wrapper, hope it helps:

/**
 * Bind events so that we can provide open and close events.
 *
 * Froffcanvas doesn't currently offer any events, so we use a
 * MutationObserver watching for changes to the 'aria-hidden' attribute to
 * determine if the panel has been opened or closed. See link for
 * Froffcanvas issue. If MutationObserver is not supported by the browser,
 * no events will be fired.
 *
 * @param {jQuery} $panel
 *
 * @link https://github.com/frend/frend.co/issues/70
 * @link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
 */
function bindEvents($panel) {
	if (!('MutationObserver' in window)) {
		return;
	}

	var panel = $panel[0];

	panel.aiOffcanvas.mutationObserver =
		new MutationObserver(function(mutations) {
			var action;

			for (var i = 0; i < mutations.length; i++) {
				var mutatedPanel = mutations[i].target;

				if (
					mutations[i].oldValue === 'false' &&
					mutatedPanel.getAttribute('aria-hidden') === 'true'
				) {
					action = 'closed';
				} else if (
					mutations[i].oldValue === 'true' &&
					mutatedPanel.getAttribute('aria-hidden') === 'false'
				) {
					action = 'opened';
				}
			}

			switch (action) {
				case 'opened':
					openEvent($panel);

					break;

				case 'closed':
					closeEvent($panel);

					break;
			}
		});
	panel.aiOffcanvas.mutationObserver.observe(panel, {
		attributes:			true,
		attributeFilter:	['aria-hidden'],
		attributeOldValue:	true
	});
};

Ambient-Impact avatar May 07 '18 23:05 Ambient-Impact