• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Anchor factory

    In Scrawl-canvas, an Anchor object holds all the data and functionality required to turn an artefact into a link. That functionality gets defined in this file.

    Scrawl-canvas uses the Anchor mixin to add anchor functionality to artefacts - in particular canvas entitys. This (alongside Button objects) gives us a interactive canvas containing dynamic, clickable regions.

    NOTE - generating an anchor will have an impact on the DOM document code, as an (off-viewport) <a> element will be added to it.

    The makeAnchor function is not exposed to the ‘scrawl’ object, thus objects can only be created indirectly. Anchors can be saved, cloned and killed as part of wider save/kill/clone functionality.

  • §

    Imports

    import { constructors } from '../core/library.js';
    
    import { doCreate, isa_fn, mergeOver, pushUnique, Ωempty } from '../helper/utilities.js';
    
    import baseMix from '../mixin/base.js';
  • §

    Shared constants

    import { _keys, ANCHOR, BLUR, CLICK, DATA_TAB_ORDER, DOWNLOAD, FOCUS, HREF, HREFLANG, NAME, PING, REFERRERPOLICY, REL, TARGET, UNDEF, TYPE, ZERO_STR } from '../helper/shared-vars.js';
  • §

    Local constants

    const _A = 'a',
        T_ANCHOR = 'Anchor';
  • §

    Anchor constructor

    const Anchor = function (items = Ωempty) {
    
        this.makeName(items.name);
        this.register();
    
        this.set(this.defs);
    
        this.host = items.host;
        this.controller = items.controller;
        this.hold = items.hold;
    
        this.clickAction = null;
        this.domElement = null;
        this.hasBeenRecentlyClicked = null;
    
        this.set(items);
    
        this.dirtyAnchor = true;
    
        return this;
    };
  • §

    Anchor prototype

    const P = Anchor.prototype = doCreate();
    P.type = T_ANCHOR;
    P.lib = ANCHOR;
    P.isArtefact = false;
    P.isAsset = false;
  • §

    Mixins

    baseMix(P);
  • §

    Anchor attributes

    const defaultAttributes = {
  • §

    host - Every anchor will belong to exactly one Artefact.

        host: null,
  • §

    description - The text that Scrawl-canvas will include between the anchor tags, when building the anchor. Always include a description for accessibility.

        description: ZERO_STR,
  • §

    disabled - When set to true, will prevent the anchor <a> element from being added to the <canvas> element’s <nav> element on the next build cycle.

        disabled: false,
  • §

    tabOrder - All hidden Anchor <a> elements have a default tabOrder attribute value of 0. SC does not touch this attribute. Instead, to order Anchor (and Button) DOM elements within the host <canvas> element’s <nav> element we set a data-tab-order attribute with the tabOrder value, which the Canvas wrapper can then use to reorder the elements as part of the Display cycle.

        tabOrder: 0,
  • §

    The following attributes are detailed in MDN’s <a> reference page.

        download: ZERO_STR,
        href: ZERO_STR,
        hreflang: ZERO_STR,
        ping: ZERO_STR,
        referrerpolicy: ZERO_STR,
        rel: 'noreferrer',
        target: '_blank',
        anchorType: ZERO_STR,
  • §

    clickAction - function - actions to be performed when user tabs to the hidden <a> element and presses the keyboard return button. Function cannot take any arguments.

        clickAction: null,
  • §

    We can instruct the anchor to add event listeners for focus and blur events using the focusAction and blurAction Boolean flags. When set to true, the focus event listener will invoke the host entity’s onEnter function; the blur event listener invokes the onLeave function.

        focusAction: true,
        blurAction: true,
    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

    P.packetExclusions = pushUnique(P.packetExclusions, ['domElement']);
    P.packetObjects = pushUnique(P.packetObjects, ['host']);
    P.packetFunctions = pushUnique(P.packetFunctions, ['clickAction']);
  • §

    Clone management

    No additional clone functionality required

  • §

    Kill management

    P.demolish = function () {
    
        const { host, controller, domElement, hold, clickAction, focusAction, blurAction } = this;
    
        if (domElement && clickAction) domElement.removeEventListener(CLICK, clickAction, false);
        if (host && domElement && focusAction) domElement.removeEventListener(FOCUS, () => host.onEnter(), false);
        if (host && domElement && blurAction) domElement.removeEventListener(BLUR, () => host.onLeave(), false);
    
        if (hold && domElement) hold.removeChild(domElement);
    
        if (controller) controller.dirtyNavigationTabOrder = true;
    
        if (host) host.anchor = null;
    
        this.deregister();
    };
  • §

    Get, Set, deltaSet

    While the Scrawl-canvas anchor object keeps copies of all of its <a> DOM element’s attributes locally, they also need to be updated on that element.

    The artefact with which an anchor object is associated maps these attributes to itself as follows:

    anchor.anchorType      ~~> artefact.anchorType
    anchor.description     ~~> artefact.anchorDescription
    anchor.download        ~~> artefact.anchorDownload
    anchor.href            ~~> artefact.anchorHref
    anchor.hreflang        ~~> artefact.anchorHreflang
    anchor.ping            ~~> artefact.anchorPing
    anchor.referrerPolicy  ~~> artefact.anchorReferrerPolicy
    anchor.rel             ~~> artefact.anchorRel
    anchor.tabOrder        ~~> artefact.anchorTabOrder
    anchor.target          ~~> artefact.anchorTarget
    

    One or more of these attributes can also be set (in the artefact factory argument, or when invoking artefact.set) using an ‘anchor’ attribute.

    P.set = function (items = Ωempty) {
    
        let i, key, val, fn;
    
        const keys = _keys(items),
            keysLen = keys.length;
    
        if (keysLen) {
    
            const setters = this.setters,
                defs = this.defs;
    
            for (i = 0; i < keysLen; i++) {
    
                key = keys[i];
                val = items[key];
    
                if (key && key !== NAME && val != null) {
    
                    fn = setters[key];
    
                    if (fn) fn.call(this, val);
                    else if (typeof defs[key] !== UNDEF) this[key] = val;
                }
            }
            this.dirtyAnchor = true;
        }
        return this;
    };
  • §

    Prototype functions

    The build function builds the <a> element and adds it to the DOM

    P.build = function () {
    
        const { host } = this;
    
        if (host) {
    
            if (!this.controller) this.controller = host.getCanvasWrapper();
            if (!this.hold) this.hold = host.getCanvasNavElement();
    
            const { hold, controller } = this;
    
            if (hold && controller) {
    
                const { anchorType, blurAction, clickAction, description, disabled, download, focusAction, href, hreflang, name, ping, referrerpolicy, rel, tabOrder, target } = this;
    
                let link = this.domElement;
    
                if (link) {
    
                    if (clickAction) link.removeEventListener(CLICK, clickAction, false);
                    if (focusAction) link.removeEventListener(FOCUS, () => host.onEnter(), false);
                    if (blurAction) link.removeEventListener(BLUR, () => host.onLeave(), false);
    
                    hold.removeChild(link);
                    this.domElement = null;
                }
    
                if (!disabled) {
    
                    link = document.createElement(_A);
    
                    link.id = name;
    
                    if (download) link.setAttribute(DOWNLOAD, download);
                    if (href) link.setAttribute(HREF, href);
                    if (hreflang) link.setAttribute(HREFLANG, hreflang);
                    if (ping) link.setAttribute(PING, ping);
                    if (referrerpolicy) link.setAttribute(REFERRERPOLICY, referrerpolicy);
                    if (rel) link.setAttribute(REL, rel);
                    if (target) link.setAttribute(TARGET, target);
                    if (anchorType) link.setAttribute(TYPE, anchorType);
    
                    link.setAttribute(DATA_TAB_ORDER, tabOrder);
    
                    if (clickAction && isa_fn(clickAction)) link.addEventListener(CLICK, clickAction, false);
    
                    if (description) link.textContent = description;
    
                    if (focusAction) link.addEventListener(FOCUS, () => host.onEnter(), false);
                    if (blurAction) link.addEventListener(BLUR, () => host.onLeave(), false);
    
                    this.domElement = link;
    
                    hold.appendChild(link);
                }
                controller.dirtyNavigationTabOrder = true;
            }
    
        }
    };
  • §

    rebuild - called as part of the Display cycle

    P.rebuild = function () {
    
        if (this.dirtyAnchor) {
    
            this.build();
            this.dirtyAnchor = false;
        }
    }
  • §

    To action a user click on an artifact with an associated anchor object, we generate a DOM MouseEvent originating from the anchor element which the browser can act on in the usual manner (browser/device dependent)

    P.click = function () {
    
        if (!this.hasBeenRecentlyClicked) {
    
            const e = new MouseEvent(CLICK, {
                view: window,
                bubbles: true,
                cancelable: true
            });
  • §

    This choke mechanism is intended to prevent “Maximum call stack size exceeded” errors occurring

    • Was causing an issue in Demo Canvas-027, where two entitys share the same anchor
            this.hasBeenRecentlyClicked = true;
    
            const self = this;
            setTimeout(() => self.hasBeenRecentlyClicked = false, 200);
    
            return this.domElement.dispatchEvent(e);
        }
        else return false;
    };
  • §

    Factory

    To create an anchor, include an anchor definition object in any artefact object’s factory argument:

    // get a handle on the canvas where the block/link will be defined
    // (in this case a canvas with id="mycanvas")
    let canvas = scrawl.library.artefact.mycanvas;
    canvas.setAsCurrentCanvas();
    
    // Define a block entity
    scrawl.makeBlock({
    
        name: 'demo-anchor-block',
    
        width: '40%',
        height: '40%',
    
        startX: '25%',
        startY: '25%',
    
        // Define the anchor object's attributes
        anchor: {
            name: 'wikipedia-water-link',
            href: 'https://en.wikipedia.org/wiki/Water',
            description: 'Link to the Wikipedia article on water (opens in new tab)',
        },
    
        // Add an action to take when user clicks on the block entity
        onUp: this.clickAnchor,
    });
    
    // Add a listener to propagate DOM-detected click events on our canvas
    // back into the Scrawl-canvas event system
    scrawl.addListener('up', () => canvas.cascadeEventAction('up'), canvas.domElement);
    
    export const makeAnchor = function (items) {
    
        if (!items) return false;
        return new Anchor(items);
    };
    
    constructors.Anchor = Anchor;