rx/presenters

View on GitHub
views/mdc/assets/js/components/events/replaces.js

Summary

Maintainability
C
7 hrs
Test Coverage
import {expandParams} from './action_parameter';
import {VBase} from './base';
import {initialize} from '../initialize';
import {uninitialize} from '../uninitialize';

const MOUSE_DELAY_AMOUNT = 0; // ms
const KEYBOARD_DELAY_AMOUNT = 500; // ms

// Create a NodeList from raw HTML.
// Whitespace is trimmed to avoid creating superfluous text nodes.
function htmlToNodes(html, root = document) {
    const template = document.createElement('template');

    template.innerHTML = html.trim();

    return template.content.children;
}

function assertXHRSupport() {
    if (typeof window.XMLHttpRequest !== 'function') {
        throw new Error('Support for XMLHttpRequest is required');
    }
}

function delayAmount(event) {
    if (typeof window['InputEvent'] === 'function') {
        return event instanceof InputEvent ? KEYBOARD_DELAY_AMOUNT : MOUSE_DELAY_AMOUNT;
    }

    return event instanceof MouseEvent ? MOUSE_DELAY_AMOUNT : KEYBOARD_DELAY_AMOUNT;
}

// Replaces a given element with the contents of the call to the url.
// parameters are appended.
export class VReplaces extends VBase {
    constructor(options, url, params, event, root) {
        super(options, root);

        assertXHRSupport();

        this.element_id = options.target;
        this.url = url;
        this.params = params;
        this.event = event;
    }

    call(results, eventParams=[]) {
        this.clearErrors();

        const httpRequest = new XMLHttpRequest();
        const root = this.root;
        const elementId = this.element_id;
        const nodeToReplace = root.getElementById(elementId);
        const expandedParams = expandParams(results, this.params);

        const inputVals = this.inputValues().filter((vals) => {
          return this.options.ignore_input_values.indexOf(vals[0]) == -1;
        });
        const paramsCollection = [expandedParams, eventParams, inputVals, [['grid_nesting', this.options.grid_nesting]]];

        const url = this.buildURL(this.url, ...paramsCollection);
        const delayAmt = delayAmount(this.event);

        return new Promise(function(resolve, reject) {
            if (!nodeToReplace) {
                let msg = 'Unable to located node: \'' + elementId + '\'' +
                    ' This usually the result of issuing a replaces action ' +
                    'and specifying a element id that does not currently ' +
                    'exist on the page.';
                console.error(msg);
                results.push({
                    action: 'replaces',
                    statusCode: 500,
                    contentType: 'v/errors',
                    content: {exception: msg},
                });
                reject(results);
            }
            else {
                clearTimeout(nodeToReplace.vTimeout);
                nodeToReplace.vTimeout = setTimeout(function() {
                    httpRequest.onreadystatechange = function() {
                        if (httpRequest.readyState === XMLHttpRequest.DONE) {
                            console.debug(httpRequest.status + ':' +
                                this.getResponseHeader('content-type'));
                            if (httpRequest.status === 200) {
                                // NodeList.childNodes is "live", meaning DOM
                                // changes to its entries will mutate the list
                                // itself.
                                // (see: https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
                                // Array.from clones the entries, creating a
                                // "dead" list.
                                const newNodes = Array.from(htmlToNodes(
                                    httpRequest.responseText,
                                    root
                                ));

                                uninitialize(nodeToReplace);

                                nodeToReplace.replaceWith(...newNodes);

                                for (const node of newNodes) {
                                    initialize(node);
                                }

                                results.push({
                                    action: 'replaces',
                                    statusCode: httpRequest.status,
                                    contentType: this.getResponseHeader(
                                        'content-type'),
                                    content: httpRequest.responseText,
                                });
                                resolve(results);
                            }
                            else {
                                results.push({
                                    action: 'replaces',
                                    statusCode: httpRequest.status,
                                    contentType: this.getResponseHeader(
                                        'content-type'),
                                    content: httpRequest.responseText,
                                });
                                reject(results);
                            }
                        }
                    };
                    console.debug('GET:' + url);
                    httpRequest.open('GET', url, true);
                    httpRequest.setRequestHeader('X-NO-LAYOUT', true);
                    httpRequest.send();
                }, delayAmt);
            }
        });
    }
}