components/js/metadatatree.js

import { Resources } from '../../resources/resources';
import { Events } from './components';
import { checkBrowserCompatibility } from '../../view/helpers';
import $ from 'jquery';
import { createTree } from 'jquery.fancytree/dist/modules/jquery.fancytree';
import 'jquery.fancytree/dist/modules/jquery.fancytree.dnd5';
import 'jquery.fancytree/dist/skin-win8/ui.fancytree.min.css';
import 'font-awesome/css/font-awesome.css';

// meta data tree view class
var nodeTypes = {
    ServerNode: "servernode",
    FormNode: "formnode",
    FieldNode: "fieldnode",
    ValueNode: "valuenode",
    SlideNode: "slidenode",
};

function loadServerNode(forms, serverIndex) {
    var result = [];
    for (var i = 0; i < forms.length; i++) {
        var form = forms[i];
        var formNode = {
            title: form.FormName,
            type: nodeTypes.FormNode,
            lazy: false,
            serverIndex: serverIndex,
            folder: true,
            formId: form.FormID,
            children: [],
        };

        for (var j = 0; j < form.FormFields.length; j++) {
            var field = form.FormFields[j];
            if (this.showAllFields || field.FormList != null) {
                formNode.children.push({
                    title: field.Label,
                    type: nodeTypes.FieldNode,
                    fieldId: field.FieldID,
                    formId: form.FormID,
                    formListValues: field.FormList ? field.FormList.FormListValues : null,
                    lazy: true,
                    serverIndex: serverIndex,
                    folder: true,
                });
            }
        }

        if (formNode.children.length > 0) {
            result.push(formNode);
        }

    }

    return result;
}

function loadNode(event, data) {
    var dfd = new $.Deferred();
    data.result = dfd.promise();

    var node = data.node;
    var _this = this;
    var serverIndex = node.data.serverIndex;

    if (node.type == nodeTypes.ServerNode) {
        this.context.getFormDefinitions(_this.servers[serverIndex].url, [], "", function (sessionId, forms) {
            var result = loadServerNode.call(_this, forms, serverIndex);
            dfd.resolve(result);
        },
            function (error) {
                dfd.reject(error.Message ? error.Message : Resources.translate("Error loading forms"));
            });
    }
    else if (node.type == nodeTypes.FieldNode) {
        this.context.distinctValues({
            serverUrl: _this.servers[serverIndex].url,
            formId: node.data.formId,
            fieldId: node.data.fieldId,
            success: function (sessionId, values) {
                var result = [];
                for (var i = 0; i < values.length; i++) {
                    var label = values[i];
                    if (node.data.formListValues) {
                        for (var v = 0; v < node.data.formListValues.length; v++) {
                            if (node.data.formListValues[v].ValueID == label) {
                                label = node.data.formListValues[v].Value;
                                break;
                            }
                        }
                    }

                    result.push({
                        title: label,
                        type: nodeTypes.ValueNode,
                        formId: node.data.formId,
                        fieldId: node.data.fieldId,
                        value: values[i],
                        lazy: true,
                        serverIndex: serverIndex,
                        folder: true,
                    });
                }

                dfd.resolve(result);
            },
            failure: function (error) {
                dfd.reject(error.Message ? error.Message : Resources.translate("Error loading field"));
            }
        });
    }
    else if (node.type == nodeTypes.ValueNode) {
        this.context.metadata({
            serverUrl: _this.servers[serverIndex].url,
            expressions: [{
                FormID: node.data.formId,
                FieldID: node.data.fieldId,
                Operator: 0,
                Value: node.data.value
            }],
            success: function (sessionId, slides) {
                var result = [];
                for (var i = 0; i < slides.length; i++) {
                    var slide = slides[i];
                    result.push({
                        title: slide,
                        type: nodeTypes.SlideNode,
                        formId: node.data.formId,
                        fieldId: node.data.fieldId,
                        path: slide,
                        lazy: false,
                        serverIndex: serverIndex,
                        folder: false,
                    });
                }

                dfd.resolve(result);
            },
            failure: function (error) {
                dfd.reject(error.Message ? error.Message : Resources.translate("Error loading field"));
            }
        });
    }
}

function serverVersionResult(server, version) {
    server.version = version;
}

/**
* Represents a UI component that shows a tree view that allows browsing through the forms and their values submitted from multiple PMA.core servers. This component uses {@link https://github.com/mar10/fancytree|fancytree} under the hood.
* @param  {PMA.UI.Components.Context} context
* @param  {object} options - Configuration options
* @param  {PMA.UI.Components.Tree~server[]} options.servers An array of servers to show files from
* @param {string|HTMLElement} options.element - The element that hosts the tree view. It can be either a valid CSS selector or an HTMLElement instance.
* @param  {function} [options.renderNode] - Allows tweaking after node state was rendered
* @param  {PMA.UI.Components.Tree~rootDirSortCb} [options.rootDirSortCb] - Function that sorts an array of directories
* @param  {boolean} [options.autoExpandNodes=false] - Whether the tree should expand nodes on single click
* @param  {boolean} [options.showAllFields=false] - Whether to show all fields or list fields only
* @fires PMA.UI.Components.Events#ValueExpanded
* @fires PMA.UI.Components.Events#SlideSelected
*/
export class MetadataTree {
    constructor(context, options) {
        if (!checkBrowserCompatibility()) {
            return;
        }

        if (options.element instanceof HTMLElement) {
            this.element = element;
        }
        else if (typeof options.element == "string") {
            var el = document.querySelector(options.element);
            if (!el) {
                console.error("Invalid selector for element");
            }
            else {
                this.element = el;
            }
        }

        this.context = context;
        this.servers = options.servers || [];
        var _this = this;

        this.autoExpand = options.autoExpandNodes === true;
        this.lastSearchResults = {};
        this.showAllFields = options.showAllFields === true ? true : false;
        this.listeners = {};
        this.listeners[Events.ValueExpanded] = [];
        this.listeners[Events.SlideSelected] = [];

        if (typeof options.rootDirSortCb === "function") {
            this.rootDirSortCb = options.rootDirSortCb;
        }

        var sourceData = [];
        for (var i = 0; i < this.servers.length; i++) {
            sourceData.push({
                title: this.servers[i].name,
                type: nodeTypes.ServerNode,
                serverNode: true,
                key: this.servers[i].url,
                serverIndex: i,
                extraClasses: "server",
                dirPath: "/",
                lazy: true,
                unselectableStatus: false,
                unselectable: true,
                selected: false,
            });

            // try get version for each server
            this.context.getVersionInfo(this.servers[i].url, serverVersionResult.bind(this, this.servers[i]));
        }

        var tree = createTree(this.element, {
            keyPathSeparator: "?",
            extensions: [],
            selectMode: 3,
            toggleEffect: { effect: "drop", options: { direction: "left" }, duration: 400 },
            wide: {
                iconWidth: "1em",
                iconSpacing: "0.5em",
                levelOfs: "1.5em" // Adjust this if ul padding != "16px"
            },
            icon: function (event, data) {
                switch (data.node.type) {
                    case nodeTypes.ServerNode:
                        return "server";
                    case nodeTypes.FormNode:
                        return "fa fa-table";
                    case nodeTypes.SlideNode:
                        return "image";
                    case nodeTypes.FieldNode:
                        return "fa fa-tag";
                    case nodeTypes.ValueNode:
                        return "fa fa-code";
                    default:
                        return "fa fa-table";
                }
            },
            renderNode: (typeof options.renderNode === "function" ? options.renderNode : null),
            source: sourceData,
            lazyLoad: loadNode.bind(this),
            activate: function (event, data) {
                // A node was activated:
                var node = data.node;
                if (_this.autoExpand === true) {
                    node.setExpanded(true);
                }

                if (node.type == nodeTypes.SlideNode) {
                    _this.fireEvent(Events.SlideSelected, { serverUrl: _this.servers[node.data.serverIndex].url, path: node.data.path });
                }
            },
            select: function (event, data) {
                var n = data.tree.getSelectedNodes();
                var selectionArray = [];
            },
            expand: function (event, data) {
                var node = data.node;

                if (node.type == nodeTypes.ValueNode) {
                    var slides = [];
                    if (node.children && node.children.length > 0) {
                        for (var i = 0; i < node.children.length; i++) {
                            slides.push(node.children[i].data.path);
                        }
                    }

                    _this.fireEvent(Events.ValueExpanded, { serverUrl: _this.servers[node.data.serverIndex].url, slides: slides });
                }

            },
            dblclick: function (event, data) {
                var node = data.node;
            }
        });
    }
    /**
    * Adds a new server to the tree
    * @param  {PMA.UI.Components.Tree~server} server A server object
    */
    addServer(server) {
        if (server) {
            this.servers.push(server);

            var serverInfo = {
                title: this.servers[this.servers.length - 1].name,
                serverNode: true,
                key: this.servers[this.servers.length - 1].url,
                serverIndex: this.servers.length - 1,
                extraClasses: "server",
                dirPath: "/",
                lazy: true,
                unselectableStatus: false,
                unselectable: true,
                selected: false,
            };

            // try get version for server
            this.context.getVersionInfo(server.url, serverVersionResult.bind(this, server));
            $(this.element).fancytree("getRootNode").addChildren(serverInfo);
        }
    }
    /**
     * Removes a server from the tree
     * @param {number} index The index of the server to remove
     */
    removeServer(index) {
        var children = $(this.element).fancytree("getRootNode").getChildren();
        if (children && children.length && index >= 0 && index < children.length) {
            children[index].remove();
        }
        else {
            console.error("No children found or index out of range");
        }
    }
    /**
    * Attaches an event listener
    * @param {PMA.UI.Components.Events} eventName - The name of the event to listen to
    * @param {function} callback - The function to call when the event occurs
    */
    listen(eventName, callback) {
        if (!this.listeners.hasOwnProperty(eventName)) {
            console.error(eventName + " is not a valid event");
        }

        this.listeners[eventName].push(callback);
    }
    // fires an event
    fireEvent(eventName, eventArgs) {
        if (!this.listeners.hasOwnProperty(eventName)) {
            console.error(eventName + " does not exist");
            return;
        }

        for (var i = 0, max = this.listeners[eventName].length; i < max; i++) {
            this.listeners[eventName][i].call(this, eventArgs);
        }
    }
}