﻿<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>PandaDoc Settings</title>
    <!-- JS part -->
    <script defer src="../../../pandadoc_/Components/dependencies/jquery_2.2.0.min.js"></script>
    <script defer src="../../../pandadoc_/Components/dependencies/jstree/jstree.min.js"></script>

    <script defer src="../../../pandadoc_/Components/dependencies/json2.js"></script>
    <script defer src="../../../pandadoc_/Components/EntityForms/Infrastruction/ConfigurationProvider.js"></script>
    <script src="../../../ClientGlobalContext.js.aspx" type="text/javascript"></script>
    <!-- CSS -->
    <link rel="stylesheet" href="../../../pandadoc_/Components/dependencies/jstree/themes/default/style.min.css" />
    <link rel="stylesheet" href="pandadoc_settings.css" />
</head>
<body>
    <div class="section section-bordered">
        <h1><img src="../../../pandadoc_/Components/images/logo_48.png" width="24" height="24" alt="PD" class="pd-logo"> PandaDoc Settings</h1>
    </div>
    <div class="section section-bordered">
        <p>You can add a list of PandaDoc documents to the views you need inside of Microsoft Dynamics. PandaDoc supports basic and <strong>custom</strong> objects. Main objects supported are: <strong>Opportunities</strong>, <strong>Quotes</strong>, <strong>Accounts</strong> and <strong>Contacts</strong>.</p>
        <p>When you add PandaDoc to one of these views, the related documents to that object will display. You will also be able to send new documents within that object.</p>
        <ul>
            <li><a href="https://support.pandadoc.com/hc/en-us/articles/360021787034" target="_blank">Learn how to enable PandaDoc control for any entity</a></li>
            <li><a href="https://support.pandadoc.com/hc/en-us/articles/18911415676695" target="_blank">Learn how to create and send PandaDoc documents</a></li>
        </ul>
    </div>
    <div class="section section-bordered" style="display: none;" id="migration-section">
        <h2>Data migration</h2>
        <p>Our solution was updated. You can press "Migrate data" button to move all tokens settings</p>
        <button type="button" class="btn btn-primary" id="migrateBtn">Migrate data</button>
    </div>

    <div class="section">
        <h2>Configure tokens</h2>
        <p>When you create a new PandaDoc document inside Microsoft Dynamics, PandaDoc will automatically send data from Microsoft Dynamics as tokens directly into your PandaDoc document.</p>
    </div>
    <ul class="list list--tabs" id="entityNameSelect">
        <li class="active" data-entity-name="opportunity"><a href="javascript:void();">Opportunity</a></li>
        <li data-entity-name="quote"><a href="javascript:void();">Quote</a></li>
        <li data-entity-name="account"><a href="javascript:void();">Account</a></li>
        <li data-entity-name="contact"><a href="javascript:void();">Contact</a></li>
        <li class="tab-add"><a href="#" class="js-dialog-open">+ Add entity</a></li>
    </ul>
    <div class="section">
        <div>
            <input type="text" id="tokens-search" class="search" placeholder="Type to search..." />
        </div>
        <br />
        <div class="grid grid--spacing-lg tokens-picker">
            <div class="grid-item w-50">
                <h3>Available tokens</h3>
                <div class="tokens-col">
                    <div id="tokens-tree-container"></div>
                </div>
            </div>
            <div class="grid-item w-50">
                <h3>Selected tokens</h3>
                <div class="tokens-col" id="selected-tokens"></div>
                <button type="button" class="btn btn-primary" id="saveBtn">Save mapping</button>
                <div id="deleteBtnBlock" style="display: none;">
                    <h3 style="padding-top: 20px;">Delete entity</h3>
                    <p>If you want to delete this entity from PandaDoc mapping click "Delete".</p>
                    <button type="button" class="btn btn-danger" id="deleteBtn">Delete this entity</button>
                </div>
            </div>
        </div>
    </div>
    <div class="pd-dialog" style="display: none;">
        <div class="dialog-backdrop">
            <form class="dialog-window" id="add-entity-form">
                <div class="dialog-head">
                    Add entity
                    <button class="dialog-close js-dialog-close">&times;</button>
                </div>
                <div class="dialog-body">
                    <div id="add-entity-select" class="add-entity-variant">
                        <p class="add-entity-label">
                            Select an entity
                        </p>
                        <p>
                            <input type="checkbox" id="showOnlyCustomEntities" checked="checked">
                            <label for="showOnlyCustomEntities" style="vertical-align: top; cursor: pointer;">Show custom entities only</label>
                        </p>
                        <p>
                            <input type="checkbox" id="showEntittiesFromMsSolutions" checked="checked">
                            <label for="showEntittiesFromMsSolutions" style="vertical-align: top; cursor: pointer;">Show entities from Microsoft Solutions</label>
                        </p>
                        <select name="add-entity-value-select" id="add-entity-value-select">
                            <option value="">-- Please select --</option>
                        </select>
                    </div>
                </div>
                <div class="dialog-footer">
                    <button type="button" class="btn btn-primary" id="addEntityBtn">Save</button>
                    <button type="reset" class="btn btn-danger dialog-cancel-btn js-dialog-close">Cancel</button>
                </div>
            </form>
        </div>
    </div>
    <div id="cover" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.5; cursor: progress;">
    </div>

    <script type="text/javascript">

        var clientUrl = "";
        var serviceUrl = "";

        var pandadocDefaultTokens = {};

        var isMigrationState = false;

        var pandadocRetrievedTokens = [];

        var currentEntityName = "";
        var configurationProvider = null;

        document.onreadystatechange = function () {
            var state = document.readyState;
            clientUrl = GetGlobalContext().getClientUrl();
            serviceUrl = clientUrl + "/api/data/v9.0/";
            if (state == 'complete') {
                $.jstree.defaults.core.themes.icons = false;
                $.jstree.defaults.conditionalselect = function () { return true; };
                $.jstree.plugins.conditionalselect = function (options, parent) {
                    this.select_node = function (obj, supressEvent, preventOpen) {
                        if (this.settings.conditionalselect.call(this, this.get_node(obj))) {
                            parent.select_node.call(this, obj, supressEvent, preventOpen);
                        }
                    };
                };
                configurationProvider = new PandaDoc.Components.Infrastruction.ConfigurationProvider();
                pandadocDefaultTokens = configurationProvider.getPandaDocDegaultTokens();
                (function () {
                    initTokenView();
                })();
            }
        }

        function initTokenView() {
            loadEntities().then(function (loadedEntities) {
                var migrationSection = $("#migration-section");
                if (loadedEntities.length == 0) {
                    isMigrationState = true;
                    migrationSection.show();
                } else {
                    isMigrationState = false;
                    migrationSection.hide();
                }

                initTabs(loadedEntities);

                initAddEntityPopup();

                initButtons();

                initSearch();

                loadData("opportunity");
            });
        }

        function initTabs(loadedEntities) {
            var pandadocDefaultEntities = configurationProvider.getDefaultEntitiesList();
            loadedEntities.forEach(function (loadedEntity) {
                if (pandadocDefaultEntities.indexOf(loadedEntity.name) > -1) {
                    return;
                }

                addTab(loadedEntity.name, loadedEntity.id);
            });

            $("#entityNameSelect").on("click", "li:not(.tab-add)", function () {
                if ($(this).hasClass("active")) {
                    return;
                }

                $("#entityNameSelect li").removeClass("active");
                $(this).addClass("active");

                $.jstree.destroy();
                $("#tokens-tree-container").html("");

                var entityName = $(this).data("entity-name");
                if (entityName) {
                    loadData(entityName);
                    $("#deleteBtnBlock").toggle(!pandadocDefaultTokens[entityName]);
                }
            });
        }

        function initAddEntityPopup() {
            $.getJSON(serviceUrl + "EntityDefinitions?$select=LogicalName,IsCustomEntity,IsCustomizable").then(function (result) {
                var entities = result.value;
                entities.sort(function (a, b) {
                    if (a.LogicalName > b.LogicalName) {
                        return 1;
                    }

                    if (a.LogicalName === b.LogicalName) {
                        return 0;
                    }

                    return -1;
                });

                fillEntitiesSelect(entities);

                $("#showOnlyCustomEntities").change(function () {
                    fillEntitiesSelect(entities);
                });
                $("#showEntittiesFromMsSolutions").change(function () {
                    fillEntitiesSelect(entities);
                });

            });

            $("#addEntityBtn").click(function () {
                var entityName = $("#add-entity-value-select").val();
                if (!entityName) {
                    alert("Please select entity");
                    return;
                }

                if ($("#entityNameSelect li[data-entity-name='" + entityName + "']").length > 0) {
                    alert("This entity already added");
                    return;
                }

                saveTokens(entityName, []).then(function (mapping) {
                    var tab = addTab(entityName, mapping.pandadoc_pandadoctokensmappingid);
                    tab.click();
                    $(".pd-dialog").hide();
                });
            });

            $(".js-dialog-open").click(function (e) {
                e.preventDefault();
                $(".pd-dialog").show();
            });

            $(".js-dialog-close").click(function (e) {
                e.preventDefault();
                $(".pd-dialog").hide();
            });
        }

        function fillEntitiesSelect(entities) {
            $("#add-entity-value-select").html("");
            $("#add-entity-value-select").append("<option value=\"\">-- Please select --</option>");
            var showOnlyCustomEntities = $("#showOnlyCustomEntities")[0].checked;
            var showEntittiesFromMsSolutions = $("#showEntittiesFromMsSolutions")[0].checked;
            entities.forEach(function (entity) {
                if (entity.IsCustomizable.Value === false || entity.LogicalName === "pandadoc_pandadoctokensmapping") {
                    return;
                }

                if (entity.LogicalName.substring(0, 6) === "msdyn_" && !showEntittiesFromMsSolutions) {
                    return;
                }

                if (!entity.IsCustomEntity && showOnlyCustomEntities === true) {
                    return;
                }

                $("#add-entity-value-select").append("<option value=\"" + entity.LogicalName + "\">" + entity.LogicalName + "</option>");
            });
        }

        function initButtons() {
            $("#saveBtn").click(function () {
                var entityName = $("#entityNameSelect li.active").first().data("entity-name");
                var ref = $("#tokens-tree-container").jstree(true);
                var nodes = ref.get_checked(true);
                sortTokenNodesById(nodes);
                var tokens = nodes.map(function (node) {
                    return {
                        token: node.original.id,
                        relationsPath: node.original.relationsPath
                    };
                });

                saveTokens(entityName, tokens);
            });

            $("#deleteBtn").click(function () {
                if (!confirm("Are you sure?")) {
                    return;
                }

                showCover();
                var entityId = $("#entityNameSelect li.active").first().data("entity-id");

                var settings = {
                    type: "DELETE",
                    async: true,
                    headers: {
                        "OData-MaxVersion": "4.0",
                        "OData-Version": "4.0",
                        "Content-Type": "application/json; charset=utf-8",
                        "Accept": "application/json"
                    }
                };

                $.ajax(`${serviceUrl}pandadoc_pandadoctokensmappings(${entityId})`, settings).then(function () {
                    $("#entityNameSelect li.active").first().remove();
                    $("#entityNameSelect li").first().click();
                });
            });

            $("#migrateBtn").click(function () {
                runMigration();
            });
        }

        function addTab(entityName, mappingId) {
            return $("<li data-entity-name=\"" + entityName + "\" data-entity-id=\"" + mappingId + "\"><a href=\"javascript:void();\">" + entityName + "</a></li>").insertBefore($("#entityNameSelect li.tab-add"));
        }

        function initSearch() {
            $("#tokens-search").keyup(function () {
                var value = $(this).val().toLowerCase();
                var items = $("#tokens-tree-container ul li");
                for (var i = 0; i < items.length; i++) {
                    var showOrHide = items[i].id.toLowerCase().indexOf(value) > -1 || items[i].innerText.toLowerCase().indexOf(value) > -1;
                    $(items[i]).toggle(showOrHide);
                    if (showOrHide && $(items[i]).attr("aria-level") === "2") {
                        $(items[i]).closest("ul").closest("li").show();
                    }
                }
            });
        }

        function loadEntities() {
            var url = serviceUrl + "pandadoc_pandadoctokensmappings?$select=pandadoc_pandadoctokensmappingid,pandadoc_name";
            return $.getJSON(url).then(function (result) {
                var loadedEntities;
                if (!result.value || result.value.length === 0) {
                    loadedEntities = [];
                } else {
                    loadedEntities = result.value.map(function (r) { return ({ name: r.pandadoc_name, id: r.pandadoc_pandadoctokensmappingid }); });
                }

                return loadedEntities;
            });
        }

        function loadData(entityName) {
            showCover();
            pandadocRetrievedTokens = [];
            currentEntityName = entityName;
            loadTokens(entityName).then(function (selectedTokens) {
                retrieveTokensFromEntity(entityName, null, null, selectedTokens).then(function (data) {
                    var openedNodes = [];
                    $("#tokens-tree-container")
                        .on("ready.jstree", function () {
                            var ref = $("#tokens-tree-container").jstree(true);
                            var needToOpenNodes = selectedTokens
                                .filter(function (st) { return st.token.indexOf(".") > -1; })
                                .map(function (st) { return st.token.substring(0, st.token.lastIndexOf(".")); })
                                .filter(function (st) { return st.toLowerCase() !== entityName.toLowerCase(); })
                                .reduce(function (prev, cur) {
                                    if (prev.indexOf(cur) === -1) {
                                        prev.push(cur);
                                    }
                                    return prev;
                                }, []);
                            if (needToOpenNodes.length > 0) {
                                needToOpenNodes.forEach(function (st) { ref.open_node(st); });
                            } else {
                                writeTokens(selectedTokens);
                                hideCover();
                            }
                        })
                        .on("after_open.jstree", function (e, data) {
                            if (openedNodes.indexOf(data.node.original.id) > -1) {
                                return;
                            }
                            loadChildNodes(data, selectedTokens, openedNodes);
                        })
                        .on("select_node.jstree", function (e, data) {
                            if (data.node.children && data.node.children.length > 0) {
                                if (isChildNodesShouldBeLoaded(data)) {
                                    loadChildNodes(data, selectedTokens, openedNodes).then(function (tokens) {
                                        selectChildNodes(data, selectedTokens);
                                        writeTokens(selectedTokens);
                                    });
                                } else {
                                    selectChildNodes(data, selectedTokens);
                                    writeTokens(selectedTokens);
                                }
                            } else {
                                selectedTokens.push(createToken(data.node.original.id, data.node.original.text));
                                writeTokens(selectedTokens);
                            }
                        })
                        .on("deselect_node.jstree", function (e, data) {
                            selectedTokens = $.grep(selectedTokens, function (tok) { return tok.token != data.node.original.id; });
                            selectedTokens = selectedTokens.filter(function (el) {
                                return data.node.children.indexOf(el.token) < 0;
                            });
                            writeTokens(selectedTokens);
                        })
                        .jstree({
                            "core": {
                                "animation": 0,
                                "check_callback": true,
                                "themes": { "stripes": true },
                                "data": data
                            },
                            "plugins": [
                                "types", "wholerow", "checkbox", "conditionalselect"
                            ]
                        });
                    hideCover();
                });
            });
        };

        function isChildNodesShouldBeLoaded(data) {
            return data.node.children.length == 1;
        }

        function loadChildNodes(data, selectedTokens, openedNodes) {
            var deferred = $.Deferred();
            showCover();
            var ref = $("#tokens-tree-container").jstree(true);
            var dummyChildren = JSON.parse(JSON.stringify(data.node.children));
            retrieveTokensFromEntity(data.node.original.entityType, data.node.original.id, data.node.original.relationsPath, selectedTokens).then(function (tokens) {
                for (var i = 0; i < tokens.length; i++) {
                    ref.create_node(data.node, tokens[i]);
                }
                ref.delete_node(dummyChildren);
                hideCover();
                openedNodes.push(data.node.original.id);
                writeTokens(selectedTokens);
                deferred.resolve();
            });
            return deferred;
        }

        function selectChildNodes(data, selectedTokens) {
            data.node.children.forEach(function (element) {
                selectedTokens.push(createToken(element,
                    getDisplayNameFromTokensArray(pandadocRetrievedTokens, element)));
            });
        }

        function retrieveTokensFromEntity(entityType, propertiesPath, relationsPath, selectedTokens) {
            var deferred = $.Deferred();

            propertiesPath = propertiesPath ? propertiesPath + "." : "";
            relationsPath = relationsPath ? relationsPath + "." : "";
            var url = serviceUrl + `EntityDefinitions(LogicalName='${entityType}')?$select=EntitySetName&$expand=Attributes,ManyToOneRelationships`;
            $.getJSON(url).then(function (result) {
                var data = [];
                for (var i = 0; i < result.Attributes.length; i++) {
                    var attribute = result.Attributes[i];
                    if (isAttributeValidForToken(attribute)) {
                        var tokenId = getTokenId(entityType, propertiesPath + attribute.SchemaName);
                        var selected = isTokenShouldBeSelected(selectedTokens, tokenId);

                        if (attribute.AttributeType === "Lookup" || attribute.AttributeType === "Customer") {
                            var entities = ["contact", "account", "lead", "systemuser", "opportunity", "quote", "uom"];
                            if (!attribute.IsCustomAttribute && entities.indexOf(attribute.Targets[0]) === -1) {
                                continue;
                            }
                            var logicalName = attribute.LogicalName;
                            var relationships = result.ManyToOneRelationships.filter(function (item) {
                                return item.ReferencingAttribute === logicalName;
                            });

                            if (relationships.length === 0) {
                                continue;
                            }
                            var displayName = getDisplayName(attribute);
                            pandadocRetrievedTokens.push(createToken(tokenId, getDisplayName(attribute)));
                            var shouldGenerateComplexName = relationships.length > 1;

                            if (relationsPath) {
                                data.push(createTokenNode(tokenId, relationsPath + attribute.SchemaName, displayName, selected, attribute.Targets[0]));
                            } else {
                                relationships.forEach(function (rel, index) {
                                    var currentDisplayName = displayName;
                                    var currentTokenId = tokenId;
                                    if (shouldGenerateComplexName) {
                                        currentDisplayName = displayName + " (" + rel.ReferencedEntity + ")";
                                        currentTokenId = tokenId + "_" + rel.ReferencedEntity;
                                        selected = isTokenShouldBeSelected(selectedTokens, currentTokenId);
                                    }
                                    data.push(createTokenNode(currentTokenId, relationsPath + rel.ReferencingEntityNavigationPropertyName, currentDisplayName, selected, rel.ReferencedEntity, getEmptyChildren()));
                                });
                            }
                            continue;
                        }
                        pandadocRetrievedTokens.push(createToken(tokenId, getDisplayName(attribute)));
                        data.push(createTokenNode(tokenId, relationsPath + attribute.SchemaName, getDisplayName(attribute), selected));
                    }
                }
                sortTokenNodesByText(data);
                deferred.resolve(data);
            });

            return deferred;
        }

        function isTokenShouldBeSelected(selectedTokens, tokenId) {
            return $.grep(selectedTokens, function (e) { return e.token == tokenId; }).length > 0;
        }

        function isAttributeValidForToken(attribute) {
            var result = true;
            var types = ["String", "Memo", "Integer", "Decimal", "Double", "DateTime", "Money", "Lookup", "Picklist", "Customer", "Boolean"];

            if (attribute.AttributeOf ||
                    (attribute.IsValidForCreate.toString() === "false" &&
                    attribute.IsValidForUpdate.toString() === "false" &&
                    attribute.IsValidForRead.toString() === "false")) {
                result = false;
            }

            if (attribute.SchemaName.indexOf("_Base") > 0 || attribute.SchemaName.indexOf("BehalfBy") > 0) {
                result = false;
            }

            // hide Company for prevent user confusing
            if (attribute.SchemaName === "Company") {
                result = false;
            }

            if (types.indexOf(attribute.AttributeType) === -1) {
                result = false;
            }
            return result;
        }

        function getDisplayName(attribute) {
            if (attribute && attribute.DisplayName && attribute.DisplayName.LocalizedLabels
                && attribute.DisplayName.LocalizedLabels[0]) {
                return attribute.DisplayName.LocalizedLabels[0].Label;
            }
            return attribute.SchemaName;
        }

        function sortTokenNodesByText(nodes) {
            nodes.sort(function (a, b) {
                if (a.text < b.text)
                    return -1;
                else if (a.text > b.text)
                    return 1;
                else
                    return 0;
            });
        }

        function sortTokenNodesById(nodes) {
            nodes.sort(function (a, b) {
                if (a.id < b.id)
                    return -1;
                else if (a.id > b.id)
                    return 1;
                else
                    return 0;
            });
        }

        function getTokenId(entityType, curPropPath) {
            if (curPropPath === "FirstName") {
                curPropPath = "First_Name";
            }

            if (curPropPath === "LastName") {
                curPropPath = "Last_Name";
            }

            if (curPropPath === "Contact.FirstName") {
                curPropPath = "Contact.First_Name";
            }

            if (curPropPath === "Contact.LastName") {
                curPropPath = "Contact.Last_Name";
            }

            if (curPropPath.indexOf(".") > 0) {
                return curPropPath;
            }

            if (curPropPath.indexOf("ParentAccountId") === 0) {
                return curPropPath.replace("ParentAccountId", "Account");
            }

            if (curPropPath.indexOf("ParentContactId") === 0) {
                return curPropPath.replace("ParentContactId", "Contact");
            }

            return entityType.substring(0, 1).toUpperCase() + entityType.substring(1) + "." + curPropPath;
        }

        function createTokenNode(id, relationsPath, text, selected, entityType, children, display) {
            if (!display) {
                display = text;
            }
            return {
                id: id,
                text: text,
                state: {
                    opened: false,
                    selected: selected
                },
                children: children,
                entityType: entityType,
                relationsPath: relationsPath
            }
        }

        function getEmptyChildren() {
            return [" "];
        }

        function createToken(key, display) {
            var token = {};
            token.display = display;
            token.token = key;
            return token;
        }

        function loadTokens(entityName) {
            var url = serviceUrl + "pandadoc_pandadoctokensmappings?$filter=pandadoc_name%20eq%20%27" + entityName + "%27&$select=pandadoc_name,pandadoc_tokens";
            return $.getJSON(url).then(function (result) {
                var loadedTokens;
                if (!result.value || result.value.length === 0) {
                    loadedTokens = pandadocDefaultTokens[entityName];
                    if (!isMigrationState) {
                        saveTokens(entityName, pandadocDefaultTokens[entityName]);
                    }
                } else {
                    loadedTokens = JSON.parse(result.value[0].pandadoc_tokens);
                }

                if (!loadedTokens) {
                    return [];
                }

                return loadedTokens.map(function (x) { return createToken(x.token, x.DisplayName); });
            });
        }

        function saveTokens(entityName, tokens) {
            var deferred = $.Deferred();
            showCover();
            var url = serviceUrl + "pandadoc_pandadoctokensmappings?$filter=pandadoc_name%20eq%20%27" + entityName + "%27&$select=pandadoc_pandadoctokensmappingid";
            $.getJSON(url).then(function (result) {
                var mapping = {};
                var isNew = false;
                var recordId = "";
                if (!result.value || result.value.length === 0) {
                    isNew = true;
                    mapping.pandadoc_name = entityName;
                } else {
                    recordId = result.value[0].pandadoc_pandadoctokensmappingid;
                }

                mapping.pandadoc_tokens = JSON.stringify(tokens);

                var headers = {
                    "OData-MaxVersion": "4.0",
                    "OData-Version": "4.0",
                    "Accept": "application/json",
                    "Content-Type": "application/json; charset=utf-8"
                };

                var settings = {
                    method: !isNew ? "PATCH" : "POST",
                    headers: headers,
                    data: JSON.stringify(mapping)
                }

                $.ajax(serviceUrl + "pandadoc_pandadoctokensmappings" + (isNew ? "" : "(" + recordId + ")"), settings).then(function (data, textStatus, xhr) {
                    var uri = xhr.getResponseHeader("OData-EntityId");
                    var regExp = /\(([^)]+)\)/;
                    var matches = regExp.exec(uri);
                    var resultId = matches[1] ?? recordId;
                    deferred.resolve({ pandadoc_pandadoctokensmappingid: resultId });
                    hideCover();
                });
            });

            return deferred.promise();
        }

        function writeTokens(tokens) {
            tokens.sort(function (a, b) {
                if (a.token < b.token)
                    return -1;
                else if (a.token > b.token)
                    return 1;
                else
                    return 0;
            });
            var tokensLIs = tokens.map(function (token) {
                var display = token.display;
                var tokenId = token.token;
                if (!tokenId) {
                    tokenId = token;
                }
                if (!display) {
                    display = getDisplayNameFromTokensArray(pandadocRetrievedTokens, tokenId) || tokenId;
                }
                return "<li>" +
                    "<div class=\"token-list__name\"><div class=\"token-list__name-clip\"><span class='token-name'>" + display + "</span>&nbsp;<span class='token'>[" + tokenId + ']</span>' + "</div></div>" +
                    "<div class=\"tokens-list__del\"><button title=\"Remove\" onclick=\"deselectToken('" + tokenId + "')\"></button></div>" +
                    "</li>";
            });
            $("#selected-tokens").html("<ul class=\"tokens-list\">" + tokensLIs.join("") + "</ul>");
        }

        function getDisplayNameFromTokensArray(source, tokenId) {
            var searchResult = $.grep(source, function (e) { return e.token == tokenId; })[0];
            if (searchResult) {
                return searchResult.display;
            }
            return;
        }

        function deselectToken(token) {
            var ref = $("#tokens-tree-container").jstree(true);
            ref.deselect_node(token);
            return false;
        }

        function showCover() {
            $("#cover").show();
        }

        function hideCover() {
            $("#cover").hide();
        }

        function runMigration() {
            showCover();
            var query = "pandadoc_MigrationToNewVersion";
            var req = new XMLHttpRequest();
            req.open("POST", clientUrl + "/api/data/v9.0/" + query, true);
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.onreadystatechange = function () {
                if (this.readyState == 4 /* complete */) {
                    req.onreadystatechange = null;
                    if (this.status == 204 || this.status == 200) {
                        hideCover();
                        initTokenView();
                    } else {
                        var error = JSON.parse(this.response).error;
                        console.error(error.message);
                        runMigrationInOldVersion();
                    }
                }
            };
            req.send();
        }

        function runMigrationInOldVersion() {
            var actionName = "pandadoc_MigrationToNewVersion";
            var requestXml = [];
            requestXml.push("<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>");
            requestXml.push("<s:Body>");
            requestXml.push("<Execute xmlns='http://schemas.microsoft.com/xrm/2011/Contracts/Services' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>");
            requestXml.push("<request xmlns:a='http://schemas.microsoft.com/xrm/2011/Contracts'>");
            requestXml.push("<a:Parameters xmlns:b='http://schemas.datacontract.org/2004/07/System.Collections.Generic'>");
            requestXml.push("</a:Parameters>");
            requestXml.push("<a:RequestId i:nil='true' />");
            requestXml.push("<a:RequestName>" + actionName + "</a:RequestName>");
            requestXml.push("</request>");
            requestXml.push("</Execute>");
            requestXml.push("</s:Body>");
            requestXml.push("</s:Envelope>");

            var xmlhttp = new XMLHttpRequest();
            xmlhttp.open("POST", serviceUrl + "web", true);
            xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
            xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
            xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
            xmlhttp.onreadystatechange = function () { parseActionSOAPResponse(xmlhttp, successResponse, errorResponse); };
            xmlhttp.send(requestXml);
        }

        function parseActionSOAPResponse(req, successCallback, errorCallback) {
            if (req.readyState == 4) {
                req.onreadystatechange = null;
                if (req.status == 200) {
                    successCallback(req.responseXML);
                }
                else {
                    errorCallback(req.responseXML);
                }
            }
        }

        function successResponse(responseXml) {
            console.log(responseXml);
            hideCover();
            initTokenView();
        }

        //In case of error
        function errorResponse(responseXml) {
            console.log(responseXml);
            hideCover();
            alert('Migration data was not found.\nCould you please configure tokens manually?');
        }

    </script>
</body>
</html>