@g2crowd/widget
Advanced tools
Comparing version 2.2.0 to 2.3.0
{ | ||
"name": "@g2crowd/widget", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"description": "Rails-friendly plugin wrapper", | ||
@@ -5,0 +5,0 @@ "repository": { |
@@ -23,3 +23,3 @@ # vvidget | ||
const widget = widgetSetup({ attr: 'ue', data: 'ue-widget' }, 'page-refreshed', 'page-refreshed'); | ||
const widget = widgetSetup({ attr: 'ue', data: 'ue-widget' }, 'page-refreshed', 'fragment-refreshed'); | ||
// DOM elements with selector of `ue`, or `data-ue-widget` will activate widgets created with this | ||
@@ -41,4 +41,8 @@ // config | ||
```javascript | ||
widget('widget-name', function(opts) { | ||
widget('widget-name', function(opts, ready) { | ||
$el = this; | ||
ready(() => { | ||
// teardown | ||
}); | ||
}, { | ||
@@ -67,17 +71,38 @@ defaults: {}, | ||
### Page refreshes | ||
### Page refresh events | ||
Widgets will listen for a custom event of your choosing, and initialize themselves once per DOM node. This allows | ||
you to trigger the event repeatedly without unintended side effects. The event can be triggered on HTML fragments or | ||
on the entire document. | ||
There is a legacy event-driven mode, that requires triggering events to initialize widgets. | ||
This is useful for PJAX and other page-refreshing systems. | ||
```javascript | ||
widget('remote-resource', function(opts) { | ||
widget('remote-resource', function(opts, ready) { | ||
fetch('/example').then((htmlFragment) => { | ||
this.append(htmlFragment); | ||
this.trigger('page-refreshed'); | ||
this.trigger('fragment-refreshed'); | ||
// any widgets nested within the HTML fragment will be activated | ||
}); | ||
this.addEventListener('click', fetchData); | ||
ready(() => { | ||
this.removeEventListener('click', fetchData); | ||
}); | ||
}) | ||
``` | ||
### DOM Mutation Observer | ||
Instead of manually triggering events, you can also choose to opt into using a MutationObserver to detect when | ||
new elements are added to the DOM. | ||
When you use this mode, fragment and page-load events will be ignored. Setup and teardown should work as expected | ||
without any manual intervention. | ||
```javascript | ||
import widgetSetup from 'widget'; | ||
const widget = widgetSetup({ attr: 'ue', data: 'ue-widget' }, 'page-refreshed', 'fragment-refreshed'); | ||
widget.startWatchingDOM(); | ||
``` |
@@ -48,8 +48,6 @@ // @format | ||
import initWidgets from './initWidgets'; | ||
import selectorBuilder from './selectorBuilder'; | ||
import { widgetInitiator } from './initWidgets'; | ||
import { strategies } from './strategies'; | ||
import { widgetTracker } from './widgetTracker'; | ||
import camelize from './camelize'; | ||
import { mutationObserver } from './mutationObserver'; | ||
@@ -67,5 +65,3 @@ class AlreadyRegisteredError extends Error { | ||
const registered = {}; | ||
const teardowns = widgetTracker(); | ||
const initiatedWidgets = widgetTracker(); | ||
const initWidgets = widgetInitiator({ attr, data, registered, teardowns, initiatedWidgets }); | ||
const init = widgetInitiator({ attr, data, registered }); | ||
@@ -82,3 +78,3 @@ const register = function (name, plugin, settings = {}) { | ||
function handleLoadEvents() { | ||
initWidgets(document.body.querySelectorAll(selector)); | ||
init.initWidgets(document.body.querySelectorAll(selector)); | ||
} | ||
@@ -93,6 +89,8 @@ | ||
if (targetElement.getAttribute(attr) || targetElement.getAttribute(`data-${data}`)) { | ||
initWidgets([targetElement]); | ||
init.initWidgets([targetElement]); | ||
} | ||
initWidgets(elements); | ||
init.initWidgets(elements); | ||
} else { | ||
init.initWidgets(document.body.querySelectorAll(selector)); | ||
} | ||
@@ -103,40 +101,35 @@ | ||
function teardownWidget(element, widgetName) { | ||
const teardown = teardowns.get(widgetName, element); | ||
const initiated = initiatedWidgets.get(widgetName, element); | ||
function handleTeardownEvents(e) { | ||
const element = e.target; | ||
const elements = element.querySelectorAll(selector); | ||
const widgetName = e.detail && e.detail.widgetName; | ||
if (teardown && typeof teardown === 'function') { | ||
teardown(); | ||
teardowns.set(widgetName, element, undefined); | ||
if (element !== document) { | ||
init.teardownWidgets([element], widgetName); | ||
} | ||
if (initiated) { | ||
initiatedWidgets.set(widgetName, element, undefined); | ||
delete element.dataset[`vvidget_${camelize(widgetName)}`]; | ||
} | ||
init.teardownWidgets(elements, widgetName); | ||
} | ||
function handleTeardownEvents(e) { | ||
const element = e.target; | ||
const widgetName = e.detail && e.detail.widgetName; | ||
register.strategies = strategies; | ||
if (widgetName) { | ||
teardownWidget(element, widgetName); | ||
} else { | ||
const names = `${element.dataset[camelize(data)] || ''} ${element.getAttribute(attr) || ''}`; | ||
let observer; | ||
register.startWatchingDOM = () => { | ||
register.shutdown(); | ||
observer = | ||
observer || | ||
mutationObserver(selector, { | ||
onAdd: (el) => init.initWidgets([el]), | ||
onRemove: (el) => init.teardownWidgets([el]) | ||
}); | ||
observer.observe(); | ||
}; | ||
names | ||
.split(' ') | ||
.filter((i) => i) | ||
.forEach((name) => teardownWidget(element, name)); | ||
register.stopWatchingDOM = () => { | ||
if (observer) { | ||
observer.disconnect(); | ||
} | ||
} | ||
}; | ||
document.addEventListener(loadEvents, handleLoadEvents); | ||
document.addEventListener(fragmentLoadEvents, handleFragmentLoadEvents); | ||
document.addEventListener('vvidget:teardown', handleTeardownEvents); | ||
register.strategies = strategies; | ||
register.initAllWidgets = () => initWidgets(document.querySelectorAll(selector)); | ||
register.initAllWidgets = () => init.initWidgets(document.querySelectorAll(selector)); | ||
register.teardownAllWidgets = () => { | ||
@@ -146,2 +139,3 @@ const event = new CustomEvent('vvidget:teardown', { bubbles: true }); | ||
}; | ||
register.restartAllWidgets = () => { | ||
@@ -151,4 +145,10 @@ register.teardownAllWidgets(); | ||
}; | ||
register.startup = () => { | ||
document.addEventListener(loadEvents, handleLoadEvents); | ||
document.addEventListener(fragmentLoadEvents, handleFragmentLoadEvents); | ||
document.addEventListener('vvidget:teardown', handleTeardownEvents); | ||
}; | ||
register.shutdown = () => { | ||
register.teardownAllWidgets(); | ||
document.removeEventListener(loadEvents, handleLoadEvents); | ||
@@ -159,2 +159,4 @@ document.removeEventListener(fragmentLoadEvents, handleFragmentLoadEvents); | ||
register.startup(); | ||
return register; | ||
@@ -161,0 +163,0 @@ }; |
@@ -29,20 +29,9 @@ import { extractOptions } from '@g2crowd/extract-options'; | ||
export const widgetInitiator = function ({ attr, data, registered, teardowns, initiatedWidgets }, fn) { | ||
export const widgetInitiator = function ({ attr, data, registered, initiatedWidgets }) { | ||
const availableWidgets = registered || {}; | ||
initiatedWidgets = initiatedWidgets || widgetTracker(); | ||
const wrapTeardown = function wrapTeardown(name, teardownFn, element) { | ||
return function () { | ||
if (typeof teardownFn === 'function') { | ||
teardownFn(); | ||
} | ||
initiatedWidgets.set(name, element, false); | ||
delete element.dataset[`vvidget_${camelize(name)}`]; | ||
}; | ||
}; | ||
const wrapPlugin = function wrapPlugin(name, pluginFn, element) { | ||
const wrapPlugin = function wrapPlugin(name, pluginFn, element, resolve) { | ||
const ready = function ready(teardown) { | ||
teardowns.set(name, element, teardown); | ||
resolve(teardown); | ||
emit(element, 'vvidget:initialized'); | ||
@@ -62,7 +51,12 @@ }; | ||
async function startWidget(name, pluginFn, element) { | ||
const strategy = strategies.get(pluginFn.init); | ||
const wrapped = wrapPlugin(name, pluginFn, element); | ||
function startWidget(name, pluginFn, element) { | ||
const widgetPromise = new Promise(function (resolve) { | ||
const strategy = strategies.get(pluginFn.init); | ||
const wrapped = wrapPlugin(name, pluginFn, element, resolve); | ||
strategy(wrapped, element); | ||
strategy(wrapped, element); | ||
}); | ||
initiatedWidgets.set(name, element, widgetPromise); | ||
return widgetPromise; | ||
} | ||
@@ -84,18 +78,42 @@ | ||
emit(element, 'vvidget:load', { name }); | ||
initiatedWidgets.set(name, element, true); | ||
element.dataset[`vvidget_${camelize(name)}`] = true; | ||
}; | ||
fn = fn || loadWidget; | ||
function parseWidgetNames(element) { | ||
const str = `${element.dataset[camelize(data)] || ''} ${element.getAttribute(attr) || ''}`; | ||
return str.split(' ').filter((i) => i); | ||
} | ||
return function initWidgets(elements) { | ||
elements.forEach(function (element) { | ||
const names = `${element.dataset[camelize(data)] || ''} ${element.getAttribute(attr) || ''}`; | ||
function teardownWidget(element, names) { | ||
names.forEach((name) => { | ||
const widgetPromise = initiatedWidgets.get(name, element); | ||
names | ||
.split(' ') | ||
.filter((i) => i) | ||
.forEach((name) => fn(element, name)); | ||
if (widgetPromise) { | ||
widgetPromise.then((teardown) => { | ||
if (teardown && typeof teardown === 'function') { | ||
teardown(); | ||
} | ||
}); | ||
initiatedWidgets.set(name, element, undefined); | ||
delete element.dataset[`vvidget_${camelize(name)}`]; | ||
} | ||
}); | ||
} | ||
return { | ||
initWidgets: (elements) => { | ||
elements.forEach(function (element) { | ||
parseWidgetNames(element).forEach((name) => loadWidget(element, name)); | ||
}); | ||
}, | ||
teardownWidgets: (elements, widgetName) => { | ||
elements.forEach((element) => { | ||
const names = widgetName ? [widgetName] : parseWidgetNames(element); | ||
teardownWidget(element, names); | ||
}); | ||
} | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
13330
11
314
106