git @ Cat's Eye Technologies Gemooy / master src / yoob / source-manager.js
master

Tree @master (Download .tar.gz)

source-manager.js @masterraw · history · blame

/*
 * This file is part of yoob.js version 0.8
 * Available from https://github.com/catseye/yoob.js/
 * This file is in the public domain.  See http://unlicense.org/ for details.
 */
if (window.yoob === undefined) yoob = {};

/*
 * A SourceManager co-operates with a Controller and maybe a PresetManager.
 * It is for editing a program/configuration in some editing interface
 * which is mutually exclusive, UI-wise, with the run/animation interface.
 */
yoob.SourceManager = function() {
    /*
     * editor: an element (usually a textarea) which stores the source code
     * hideDuringEdit: a list of elements which will be hidden when editing
     * (typically this will be the animation display of a yoob.Controller)
     * disableDuringEdit: a list of elements which will be disabled when editing
     * storageKey: key under which sources will be saved/loaded from localStorage
     * panelContainer: an element into which to add the created button panel
     * (if you do not give this, no panel will be created.  You're on your own.)
     * onDone: if given, if a function, it becomes the onDone method on this
     */
    this.init = function(cfg) {
        this.supportsLocalStorage = (
            window['localStorage'] !== undefined &&
            window['localStorage'] !== null
        );
        this.editor = cfg.editor;
        this.hideDuringEdit = cfg.hideDuringEdit;
        this.prevDisplay = {};
        for (var i = 0; i < this.hideDuringEdit.length; i++) {
            this.prevDisplay[this.hideDuringEdit[i]] = this.hideDuringEdit[i].display;
        }
        this.disableDuringEdit = cfg.disableDuringEdit;
        this.storageKey = cfg.storageKey || 'default';
        this.controls = {};
        if (cfg.panelContainer) {
            this.panel = this.makePanel();
            cfg.panelContainer.appendChild(this.panel);
        }
        if (cfg.onDone) {
            this.onDone = cfg.onDone;
        }
        this.clickDone();
        return this;
    };

    this.makePanel = function() {
        var panel = document.createElement('div');
        var $this = this;
        var makeButton = function(action) {
            var button = document.createElement('button');
            var upperAction = action.charAt(0).toUpperCase() + action.slice(1);
            button.innerHTML = upperAction;
            button.style.width = "5em";
            panel.appendChild(button);
            button.onclick = function(e) {
                if ($this['click' + upperAction]) {
                    $this['click' + upperAction]();
                }
            }
            $this.controls[action] = button;
        };
        var keys = ["edit", "done", "load", "save"];
        for (var i = 0; i < keys.length; i++) {
            makeButton(keys[i]);
        }
        return panel;
    };

    this.clickEdit = function() {
        var hde = this.hideDuringEdit;
        for (var i = 0; i < hde.length; i++) {
            this.prevDisplay[hde[i]] = hde[i].style.display;
            hde[i].style.display = 'none';
        }
        for (var i = 0; i < this.disableDuringEdit.length; i++) {
            this.disableDuringEdit[i].disabled = true;
            /* But if it's not a form control, disabled is meaningless. */
            this.disableDuringEdit[i].style.pointerEvents = 'none';
            this.disableDuringEdit[i].style.opacity = '0.5';
        }
        this.editor.style.display = 'block';
        this.controls.edit.disabled = true;
        var keys = ["done", "load", "save"];
        for (var i = 0; i < keys.length; i++) {
            this.controls[keys[i]].disabled = false;
        }
        this.onEdit();
    };

    this.clickDone = function() {
        var hde = this.hideDuringEdit;
        for (var i = 0; i < hde.length; i++) {
            hde[i].style.display = this.prevDisplay[hde[i]];
        }
        for (var i = 0; i < this.disableDuringEdit.length; i++) {
            this.disableDuringEdit[i].disabled = false;
            /* But if it's not a form control, disabled is meaningless. */
            this.disableDuringEdit[i].style.pointerEvents = 'auto';
            this.disableDuringEdit[i].style.opacity = '1.0';
        }
        this.editor.style.display = 'none';
        this.controls.edit.disabled = false;
        var keys = ["done", "load", "save"];
        for (var i = 0; i < keys.length; i++) {
            this.controls[keys[i]].disabled = true;
        }
        this.onDone();
    };

    this.clickLoad = function() {
        if (!this.supportsLocalStorage) {
            var s = "Your browser does not support Local Storage.\n\n";
            s += "You may instead open a local file in a text editor, ";
            s += "select all, copy to clipboard, then paste into ";
            s += "the textarea, to load a source you have saved locally.";
            alert(s);
            return;
        }
        this.loadSource(
            localStorage.getItem('yoob:' + this.storageKey + ':default')
        );
    };

    this.clickSave = function() {
        if (!this.supportsLocalStorage) {
            var s = "Your browser does not support Local Storage.\n\n";
            s += "You may instead select all in the textarea, copy to ";
            s += "clipboard, open a local text editor, paste in there, ";
            s += "and save, to save a source locally.";
            alert(s);
            return;
        }
        localStorage.setItem(
            'yoob:' + this.storageKey + ':default',
            this.getEditorText()
        );
    };

    this.onEdit = function() {
    };

    /*
     * Override this to load it into the controller
     */
    this.onDone = function() {
    };

    /*
     * Loads a source text into the editor element.
     */
    this.loadSource = function(text) {
        this.setEditorText(text);
        this.onDone();
    };

    /*
     * You may need to override if your editor is not a textarea.
     */
    this.setEditorText = function(text) {
        this.editor.value = text;
    };

    /*
     * You may need to override if your editor is not a textarea.
     */
    this.getEditorText = function() {
        return this.editor.value;
    };

    /*
     * Loads a source text into the source element.
     * Assumes it comes from an element in the document, so it translates
     * the basic HTML escapes (but no others) to plain text.
     */
    this.loadSourceFromHTML = function(html) {
        var text = html;
        text = text.replace(/\&lt;/g, '<');
        text = text.replace(/\&gt;/g, '>');
        text = text.replace(/\&amp;/g, '&');
        this.loadSource(text);
    };

    /*
     * This is the basic idea, but not fleshed out yet.
     * - Should we cache the source somewhere?
     * - While we're waiting, should we disable the UI / show a spinny?
     */
    this.loadSourceFromURL = function(url, errorCallback) {
        var http = new XMLHttpRequest();
        var $this = this;
        if (!errorCallback) {
            errorCallback = function(http) {
                $this.loadSource(
                    "Error: could not load " + url + ": " + http.statusText
                );
            }
        }
        http.open("get", url, true);
        http.onload = function(e) {
            if (http.readyState === 4 && http.responseText) {
                if (http.status === 200) {
                    $this.loadSource(http.responseText);
                } else {
                    errorCallback(http);
                }
            }
        };
        http.send(null);
    };
};