scrawl-canvas
Version:
Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun
278 lines (195 loc) • 7.75 kB
JavaScript
// # Element factory
// The Scrawl-canvas Stack/Element system is an attempt to supplement DOM elements with Scrawl-canvas entity [positioning and dimensioning](../mixin/position.html) functionality.
//
// During initialization Scrawl-canvas will search the DOM tree and automatically create Stack wrappers for any element which has been given a `data-scrawl-stack` attribute which resolves to true. Every direct (first level) child inside the stack element will have Element wrappers created for them (except for <canvas> elements).
// #### Imports
import { constructors } from '../core/library.js';
import { doCreate, isa_dom, removeItem, Ωempty } from '../helper/utilities.js';
import { uiSubscribedElements } from '../core/user-interaction.js';
import { makeCanvas } from './canvas.js';
import baseMix from '../mixin/base.js';
import domMix from '../mixin/dom.js';
// Shared constants
import { ABSOLUTE, CANVAS, CORNER_SELECTOR, ELEMENT, FORBIDDEN_ELEMENTS, MIMIC } from '../helper/shared-vars.js';
// Local constants
const T_ELEMENT = 'Element';
// #### Element constructor
const Element = function (items = Ωempty) {
const el = items.domElement;
// Restrict what can become an Element
// + The last check is to exclude Web Components
if (el && !FORBIDDEN_ELEMENTS.includes(el.tagName) && !el.tagName.includes('-')) {
this.makeName(items.name);
this.register();
if (el) {
// Scrawl-canvas does not retain an Element's textContent or innerHTML values internally. However these can be set on initialization, and subsequently, by using the attributes `text` (for textContent, which automatically escapes all HTML-related tags and entities) and `content` (which should respect HTML tags and entities)
if (items.text) el.textContent = items.text;
else if (items.content) el.innerHTML = items.content;
}
this.initializePositions();
this.dimensions[0] = this.dimensions[1] = 100;
this.pathCorners = [];
this.css = {};
this.here = {};
this.initializeDomLayout(items);
this.set(this.defs);
this.initializeAccessibility();
this.mimic = null;
this.pivot = null;
this.dirtyContent = true;
this.dirtyCss = true;
this.dirtyStampOrder = true;
this.localMouseListener = null;
this.canvas = null;
this.elementComputedStyles = null;
this.set(items);
const myEl = this.domElement;
if (myEl) myEl.id = this.name;
return this;
}
return null;
};
// #### Element prototype
const P = Element.prototype = doCreate();
P.type = T_ELEMENT;
P.lib = ELEMENT;
P.isArtefact = true;
P.isAsset = false;
// #### Mixins
baseMix(P);
domMix(P);
// #### Element attributes
// No additional attributes required beyond those supplied by the mixins
// #### Packet management
// No additional packet functionality required
// #### Clone management
// No additional clone functionality required
// #### Kill management
P.factoryKill = function (removeElement = true) {
removeItem(uiSubscribedElements, this.name);
if (removeElement) this.domElement.remove();
};
// #### Get, Set, deltaSet
const S = P.setters;
// `text` - __this is the preferred way to update an element's text content__ because the text supplied in the argument is not treated as HTML by the browser.
//
// When we update the DOM attribute `element.textContent`, it deletes any position-reporting corner divs we may have added to the element. Thus we need to repopulate the element with its 'kids' after updating the text
S.text = function (item) {
const el = this.domElement;
if (isa_dom(el)) {
const corners = el.querySelectorAll(CORNER_SELECTOR);
el.textContent = item;
corners.forEach(c => el.appendChild(c));
this.dirtyContent = true;
}
};
// `content` - __WARNING - this is a dangerous function!__ It does not perform any character escaping before inserting the supplied argument into the element. Raw HTML (including, for instance, <script> tags) will be added to the DOM. It's up to the developer to make sure this content is safe!
//
// When we update the DOM attribute `element.innerHTML`, it deletes any position-reporting corner divs we may have added to the element. Thus we need to repopulate the element with its 'kids' after updating the text
S.content = function (item) {
const el = this.domElement;
if (isa_dom(el)) {
const corners = el.querySelectorAll(CORNER_SELECTOR);
el.innerHTML = item;
corners.forEach(c => el.appendChild(c));
this.dirtyContent = true;
}
};
// #### Prototype functions
// `cleanDimensionsAdditionalActions` - overwrites mixin/position function.
P.cleanDimensionsAdditionalActions = function () {
this.dirtyDomDimensions = true;
};
// #### Snippet-related functions
// `addCanvas` - adds a new <canvas> element to Scrawl-canvas stack immediately before this element, and sets up the canvas to mimic the element (meaning it will mimic changes to the element's dimensions, positioning, scale and 3D rotational values)
// + The function can accept a Javascript object argument containing key:value pairs which will be used to set up the new canvas's attributes after it has been created.
// + To make the canvas look as if it is in front of the element, set the element's opacity CSS attribute to 0
// + This function is used when adding a Scrawl-canvas snippet to a stacked element.
P.addCanvas = function (items = Ωempty) {
if (!this.canvas) {
const canvas = document.createElement(CANVAS),
el = this.domElement;
canvas.id = `${this.name}-canvas`;
const rect = el.getBoundingClientRect();
el.parentNode.insertBefore(canvas, this.domElement);
const art = makeCanvas({
name: `${this.name}-canvas`,
domElement: canvas,
position: ABSOLUTE,
width: rect.width,
height: rect.height,
mimic: this.name,
lockTo: MIMIC,
useMimicDimensions: true,
useMimicScale: true,
useMimicStart: true,
useMimicHandle: true,
useMimicOffset: true,
useMimicRotation: true,
addOwnDimensionsToMimic: false,
addOwnScaleToMimic: false,
addOwnStartToMimic: false,
addOwnHandleToMimic: false,
addOwnOffsetToMimic: false,
addOwnRotationToMimic: false,
});
art.set(items);
this.canvas = art;
return art;
}
else return this.canvas;
};
// #### Factory
// ```
// Get a handle to a Stack wrapper
// let stack = scrawl.library.stack.mystack;
//
// stack.addNewElement({
//
// name: 'list',
// tag: 'ul',
//
// width: '25%',
// height: 80,
//
// startX: 400,
// startY: 120,
// handleX: 'center',
// handleY: 'center',
//
// roll: 30,
//
// classes: 'red-text',
//
// content: `<li>unordered list</li>
// <li>with several</li>
// <li>bullet points</li>`,
//
// css: {
// font: '12px fantasy',
// paddingInlineStart: '20px',
// paddingTop: '0.5em',
// margin: '0',
// border: '1px solid red',
// cursor: 'grab',
// },
//
// }).clone({
//
// name: 'list-no-border',
//
// startY: 250,
// scale: 1.25,
// pitch: 60,
// yaw: 80,
//
// css: {
// border: 0,
// },
// });
// ```
export const makeElement = function (items) {
if (!items) return false;
return new Element(items);
};
constructors.Element = Element;