Product
Introducing Enhanced Alert Actions and Triage Functionality
Socket now supports four distinct alert actions instead of the previous two, and alert triaging allows users to override the actions taken for all individual alerts.
@pelagiccreatures/sargasso
Advanced tools
Readme
@author Michael Rhodes (except where noted)
@license MIT
Made in Barbados 🇧🇧 Copyright © 2020-2021 Michael Rhodes
Sargasso Makes HTML elements aware of events such as Document (DOM) insertions and deletions, HIJAX Page load, Scrolling, Resizing, Orientation and messages Managed Web Workers and elements allowing them to efficiently implement any behavior they need to perform.
One of the core features of this framework is to implement an asynchronous page loading scheme which supports deep linking and lightning fast page loads where only dynamic content areas are merged between page loads leaving css, js, web workers and wrapper elements intact. Sargasso controller instances are automatically created as needed when their element appears in the DOM and destroyed when their element is removed so everything is cleanly destroyed and all the trash is collected. Performance is further enhanced with shared event listening services which are fully debounced during large updates. Services are also provided to schedule content changes using the browser's animation frame event loop and managed web workers for simplified offloading of computation heavy tasks to a dedicated thread resulting in highly performant pages.
This is a very lightweight (27kb), pure ES6 framework (with only few dependencies) which aims to use the most advanced stable features of modern browsers to maximum effect leaving the historical cruft, kludges and code barnacles infesting older web frameworks behind. The result is lean, highly performant and clean library that simplifies the complex technologies behind modern progressive web apps and web sites.
Other Sargasso modules that build on this framework:
API Stable
We are trying to keep this project as forward looking so as to not burden this framework with lots of obsolete junk and polyfills so while it will certainly not work on every ancient browser, it should work on any reasonably modern one. If you run into any problems, have questions, want to help or have any feedback let me know by opening a github issue.
Progressive Web Apps and modern websites need a HIJAX scheme to load pages that is integrated with and can manage element behavior. The big name frameworks out there at the moment are not a very good fit for the work I am doing so I decided to roll my own to investigate the current state of browser capabilities.
<!DOCTYPE html>
<head>
<title>Example Sargasso Element</title>
<body>
<h3>First Sargasso Element</h3>
<div data-sargasso-class="MyClass">Hello World</div>
<script src='https://cdn.jsdelivr.net/npm/@pelagiccreatures/sargasso/dist/sargasso.iife.js'></script>
<script defer>
// define MyClass as a subclass of Sargasso
class MyClass extends SargassoModule.Sargasso {
start() {
this.queueFrame(() => {
this.element.innerHTML += ' <strong>Started!</strong>'
})
super.start()
}
}
// Register MyClass to the Sargasso framework
SargassoModule.utils.registerSargassoClass('MyClass',MyClass)
// Start Sargasso
SargassoModule.utils.bootSargasso()
</script>
</body>
</html>
When you load the page the content of the Element will be "Hello World Started!"
When the object is instantiated, the framework supervisor will call the start()
method of the object. When removed from the DOM 'sleep()' will be called allowing you can cleanup any handlers. Beyond responding to scrolling, resize and other responsive events, you will probably want to interact with your element in some way. You should use the start hook to set up any element events you need to respond to such as clicking a button, responding to touch events or key presses, etc.
class MyButtonClass extends SargassoModule.Sargasso {
constructor (element, options = {}) {
options.watchViewport = true // tell me when I am visible
super(element, options) // important!
}
// listen for click
start () {
super.start() // important!
this.on('click', (e) => {
this.clicked()
})
}
// cleanup listener
sleep () {
this.off('click')
super.sleep() // important!
}
// use an animation frame to mutate the DOM
clicked () {
const frame = () => { // set up a DOM mutation
this.addClass('clicked')
this.element.textContent = 'Clicked!'
}
this.queueFrame(frame) // schedule it
}
enterViewport () {
// do some stuff such as modify element html or classes
const frame = () => {
this.element.textContent = 'Hello! Click me!'
}
this.queueFrame(frame)
}
}
SargassoModule.utils.registerSargassoClass('MyButtonClass', MyButtonClass)
Your Sargasso subclasses can subscribe to event feeds in order to be notified of events
Methods to override as needed: don't forget to call super.xxx() in your subclass
method | description |
---|---|
constructor(element, options = {}) | subscribe to services by setting options properties. All default to false so only set the ones you need watchDOM , watchScroll , watchResize , watchOrientation , watchViewport eg. {watchResize:true} |
start() | set up any interactions and event handlers |
sleep() | remove any event handlers defined in start() and cleanup references |
DOMChanged() | called when DOM changes if options 'watchDOM: true' was set in constructor |
didScroll() | called when scroll occurs if options 'watchScroll: true' was set in constructor |
didResize() | called when resize changes if options 'watchResize: true' was set in constructor |
enterViewport() | called when element is entering viewport if options 'watchViewport: true' was set in constructor |
exitViewport() | called when element is exiting viewport if options 'watchViewport: true' was set in constructor |
newPage(old, new) | on a new page |
didBreakpoint() | new screen width breakpoint |
elementEvent(e) | this.element received an 'sargasso' event |
workerOnMessage (id, data = {}) | id is the worker sending the message. Any payload from the worker postMessage is in data.xxx as defined by the worker |
enterFullscreen() | called if options 'watchOrientation: true' when user rotates phone or if setFullscreen is called |
exitFullscreen() | called on exit fullscreen |
Properties
property | description |
---|---|
this.element | the element we are controlling |
Utility Methods:
method | description |
---|---|
getMetaData | return sargasso metadata associated with element (weak map) |
setMetaData(key,value) | set a sargasso metadata property |
hasClass('classname') | returns true if this.element has cssclass |
addClass('classname') | add classname or array of classnames to this.element |
removeClass('classname') | remove classname or array of classnames to this.element |
setCSS({}) | set css pairs defined in object on this.element |
isVisible() | true if element is visible |
scrollTop(newTop) | get and set the current scroll position |
queueFrame(function) | queue a function to execute that changes the DOM |
workerStart(id, codeOrURL) | start a web worker with id. Ignored if worker id already installed (see https://github.com/PelagicCreatures/flyingfish for a shared worker example) |
workerPostMessage(id, data {}) | send the worker tagged with id a message. the message must be an object which can have any structure you want to pass to the worker |
on(container,fn) | attach undelegated event handler to container scoped to a css selector |
once(container,fn) | attach undelegated event handler to container scoped to a css selector that executes only once (automatically removes event handler on first call) |
off(container) | remove undelegated event handler to container scoped to css selector |
on(container,selector,fn) | attach delegated event handler to container scoped to a css selector |
once(container,selector,fn) | attach delegated event handler to container scoped to a css selector that executes only once (automatically removes event handler on first call) |
off(container,selector) | remove delegated event handler to container scoped to css selector |
Don't forget you need to let sargasso know about your class:
registerSargassoClass('MyClass', MyClass)
Many browsers support custom elements (current compatibility so the preferred (faster and cleaner) syntax for sargasso elements is to use a custom element tag. The class name is the kebab-case of your subclass name so MyClass becomes sargasso-my-class:
<sargasso-my-class>This works for MyClass in <em>most</em> browsers</sargasso-my-class>
Alternately, Sargasso watches the DOM for any elements tagged with the data-sargasso-class
attribute and instantiates the Sargasso class specified while hooking up the appropriate services. When the underlying element is removed from the DOM (loading a new page or something) it automatically destroys any dangling Sargasso objects.
<div data-sargasso-class="MyClass">This works for MyClass in all browsers</div>
You can also defer the instantiation using the lazy method by tagging it with data-lazy-sargasso-class
instead of data-sargasso-class
which will only start up the class when the element is visible in the viewport.
When HIJAX is enabled, Sargasso automatically captures <a href="..">
tags and calls the LoadPageHandler instead of letting the browser load pages. You can make a link be ignored by hijax by setting the <a href=".." data-no-hijax>
. Offsite links and links with targets are automatically ignored.
loadPageHandler(href)
is a utility function for programmatically loading a new page.
EG. instead of location.href= '/home'
, use LoadPageHandler('/home')
This can be called to reload the page as well (won't add to history if same url as current url)
import {Sargasso, utils, loadPageHandler} from '@pelagiccreatures/sargasso'
const preflightHandler = (url, cb) => {
if(url === '/handled-by-pre-flight') {
// special case page, we will handle it here
return cb(null, true)
}
cb(null, false)
}
let options = {
hijax: {
onError: (level, message) => { alert('Something went wrong. ' + message) }
},
preFlight: preflightHandler
}
utils.bootSargasso(options)
New pages are loaded via AJAX and are merged with the current page only replacing elements marked with data-hijax
from the new page.
<html>
<head>
</head>
<body>
static stuff
<div id="page-body" data-hijax>
<p>this changes from page to page</p>
<div>lots of html here</div>
</div>
more static stuff
<div id="some-element" data-hijax>
<p>this also changes from page to page</p>
</div>
more static stuff
</body>
<html>
Note that data-hijax elements must have and ID and contain well formed child html elements.
<div id="nope" data-hijax>I'm just text. No child elements. Won't work.</div>
<div id="yup" data-hijax><p>I'm html. This works.</p></div>
To avoid any chaotic repaints you should only make DOM changes inside animation frames - don't do any long processes in the responsive callbacks or things might bog down the browser UI.
You should offload compute heavy tasks to a new thread when possible.
Sargasso controllers have built in managed Web Workers that can be defined in external scripts or inline code blobs simplifying the management of running workers.
The worker code runs when it receives an onmessage event.
A web worker, once installed, could be used by many instances so sargasso sets e.data.uid to the id on the instance that is invoking the worker which we need to pass back in the postMessage so we know who is who.
class MySubClass extends Sargasso {
...
someMethod() {
/*
myWorker can be the url of a worker script or
inline code as in this example
*/
let pointlessMath = `onmessage = function (e) {
const baseNumber = e.data.power
let result = 0
for (var i = Math.pow(baseNumber, 7); i >= 0; i--) {
result += Math.atan(i) * Math.tan(i)
};
postMessage({
uid: e.data.uid, // Important! always pass this back in the message
result: 'Done doing pointless math: ' + result
})
}`
// create the worker to be managed by sargasso and give it an id
// the id can be unique to your task or shared by many sargasso
// controller
this.workerStart('pointlessMath', pointlessMath)
let data = { power: 12 }
this.workerPostMessage('pointlessMath', data) // send message to the worker
}
// listen for worker result
workerOnMessage (id, data) {
if (id === 'pointlessMath') {
const frame = () => {
this.element.innerHTML = data.result
}
this.queueFrame(frame)
}
super.workerOnMessage(id, data)
}
}
npm install @pelagiccreatures/sargasso --save-dev
You can use the .iife.js bundles in the /dist directory of the @PelagicCreatures modules by copying them to a public directory on your server and referencing them in script tags in your html.
node_modules/@PelagicCreatures/Sargasso/dist/sargasso.iife.js
-or-
You can also bundle sargasso modules with your own es6 code using rollup.
npm install npx -g
npm install rollup --save-dev
npm install @rollup/plugin-json --save-dev
npm install @rollup/plugin-commonjs --save-dev
npm install @rollup/plugin-node-resolve --save-dev
npm install rollup-plugin-terser --save-dev
app.js root browser javascript app for bundle
import { Sargasso, utils, loadPageHandler } from '@pelagiccreatures/sargasso'
const boot = () => {
utils.bootSargasso({})
}
export {
boot
}
html
<!DOCTYPE html>
<body>
<img data-jsclass="FlyingFish" data-src="/some-image.jpg">
<script src="public/dist/js/userapp.iife.js" defer></script>
<script defer>
window.onload= () => {
App.boot()
}
</script>
</body>
</html>
Set input and output ass needed.
rollup.config.js
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import json from '@rollup/plugin-json'
import {
terser
}
from 'rollup-plugin-terser'
export default {
input: './app.js', // <<< location of your es6 code
output: {
format: 'iife',
file: 'public/dist/js/userapp.iife.js', // <<< where to save the browser bundle
name: 'App', // <<< global variable where app.js exports are exposed
sourcemap: true,
compact: true
},
plugins: [
json(),
commonjs({}),
nodeResolve({
preferBuiltins: false,
dedupe: (dep) => {
return dep.match(/^(@pelagiccreatures|lodash|js-cookie)/)
}
}),
terser({
output: {
comments: false
}
})
]
}
Make the bundle
npx rollup --no-treeshake --no-freeze -c rollup.config.js
The hijax scheme does not work for file://xxx URIs so start a simple server on localhost:
python tests/localhost.py
Then run the tests:
npm test
-or-
point your browser to http://localhost:8000/tests/index.html to see it all in action
FAQs
Simple, Fast, Reactive, Supervised Javascript controllers for custom html elements.
The npm package @pelagiccreatures/sargasso receives a total of 10 weekly downloads. As such, @pelagiccreatures/sargasso popularity was classified as not popular.
We found that @pelagiccreatures/sargasso demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket now supports four distinct alert actions instead of the previous two, and alert triaging allows users to override the actions taken for all individual alerts.
Security News
Polyfill.io has been serving malware for months via its CDN, after the project's open source maintainer sold the service to a company based in China.
Security News
OpenSSF is warning open source maintainers to stay vigilant against reputation farming on GitHub, where users artificially inflate their status by manipulating interactions on closed issues and PRs.