Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@g2crowd/widget

Package Overview
Dependencies
Maintainers
6
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@g2crowd/widget - npm Package Compare versions

Comparing version 2.2.0 to 2.3.0

src/mutationObserver.js

2

package.json
{
"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);
});
}
};
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc