Search code examples
jquerytwitter-bootstraptabscodemirror

jQuery, bootstrap tabs and CodeMirror.refresh()


I am creating tabs when a link in a treeview is clicked. Within the tab there are two other tabs created and within them there are textareas that er converted to CodeMirror. I've got stuck on many things when it comes to creating the CodeMirror instance and usually my problems have been with the .refresh() event.

CodeMirror wasn't fully loading in the tab that's not active so I added some changes when it comes to creating the instances and I gather them all in arrays that I later in code loop through to use .refresh() so that the CodeMirror editors fully load.

Before I did that, CodeMirror was being added to all textareas in all tabs that I created on click but than I was always refreshing all the CodeMirror instances, not only the ones in the new tab. That caused values that had been entered in another tab to clear out.

So with the new change I'm only refreshing the current CodeMirror instances that are created in the new tab. But now with this change CodeMirror in all the older tabs that are still open (btw the tabs can be closed) disappears. So now I'm wondering, does CodeMirror.refresh() refresh the whole page?

Below is a part of my code, the part that creates the CodeMirror instances. I don't think the code that creates the tabs has anything to do with this dysfunctionality:

/* Add CodeMirror to convert all textboxes, needs timeout until all dom elements are loaded */

setTimeout(function () {
    var textAreaId = 0;
    $(".AI-textarea").each(function () {
        $(this).attr("id", "text" + textAreaId);
        ++textAreaId;
    });

    var queryArr = [];
    var dimensionArr = [];

    var queryBuilder = $(tabPanelDiv).find(".QueryBuilder");
    queryBuilder.each(function (index, el) {
        var editorQuery = CodeMirror.fromTextArea(el, {
            lineNumbers: true,
            tabMode: "indent",
            mode: "text/x-sql",
            theme: "eclipse"
        });
        queryArr.push(editorQuery);
    });

    var dimensionBuilder = $(tabPanelDiv).find(".DimensionBuilder");
    dimensionBuilder.each(function (index, el) {
        var editorQuery = CodeMirror.fromTextArea(el, {
            lineNumbers: true,
            tabMode: "indent",
            mode: "text/x-sql",
            theme: "eclipse"
        });
        dimensionArr.push(editorQuery);
    });

    for (var i = 0; i < queryArr.length; i++) {
        queryArr[i].refresh();
    }

    for (var a = 0; a < dimensionArr.length; a++) {
        dimensionArr[a].refresh();
    }

}, 100);

I have to have the code in a setTimeout function to wait for the textareas to be created. The function is within an on click event.

Is anyone that can point out what might be causing the CodeMirror to disappear from the Dom in older tabs? Is it maybe the CodeMirror.refresh() event or does a click event do something that I'm not aware of? Is the last CodeMirror instance getting lost while a new tab is being created?

Below are two screenshots of tabs, first one shows how a brand new tab looks like. The second shows how the older tab, that should look the same, looks like.

enter image description here

enter image description here

EDIT

Here is the click event in whole, if that helps, maybe there is something earlier in the code causing this? I apologize for mixing javascript and jquery, will probably have to do some re-writing.

  $(".treeviewLinks").click(function (e) {
        e.preventDefault();
        var targetElement = e.target || e.srcElement;

        /*** Create everything for the upper tabs that open when tree view link is clicked ***/

        if ($(".tabHelper").find("#upperTab").length == 0) {
            var ul = document.createElement("ul");
            ul.className = "nav nav-tabs";
            ul.id = "upperTab";
            ul.setAttribute("role", "tablist");
            $(".tabHelper").append(ul);
        }

        var ul = $("#upperTab");
        var id = $('#upperTab li').size() + 1;

        var li = document.createElement("li");
        var a = document.createElement("a");
        var span = document.createElement("span");

        li.setAttribute("role", "presentation");
        a.href = "#" + id;
        a.setAttribute("aria-controls", id);
        a.setAttribute("role", "tab");
        a.setAttribute("data-toggle", "tab");
        span.className = "glyphicon glyphicon-remove pull-right exit";
        a.innerHTML = targetElement.innerHTML;

        if ($(".tabHelper").find(".tab-content").length == 0) {
            var tabContentDiv = document.createElement("div");
            tabContentDiv.className = "tab-content";
        }

        $(".tabHelper").append(tabContentDiv);

        var tabContentDiv = $(".tabHelper").find(".tab-content").first();
        var tabPanelDiv = document.createElement("div");

        tabPanelDiv.className = "tab-pane"
        tabPanelDiv.id = id;
        tabPanelDiv.setAttribute("role", "tabpanel");

        ul.append(li);
        li.appendChild(a);
        a.appendChild(span);
        tabContentDiv.append(tabPanelDiv);

        var upperTab = document.getElementById("upperTab");
        var tabContent = document.getElementsByClassName("tab-content")[0];
        var cId = $(tabContent).size() + 1;

        if (id == 1) {
            var tmpLi = upperTab.firstChild;
            tmpLi.className = "active";
        }
        else if (id > 1) {
            $(li).siblings().removeClass("active");
            li.className = "active";
        }

        if (cId == 1) {
            var tmpTabPane = tabContent.firstChild;
            tmpTabPane.className += " active";
        }
        else if (cId > 1) {
            $(tabPanelDiv).siblings().removeClass("active");
            tabPanelDiv.className += " active";
        }

        /*** Create ul for nav-tabs and tab-content for query and dimension builder tabs ***/

        var qdUl = document.createElement("ul");
        qdUl.className = "nav nav-tabs query-dimension-tabs";
        qdUl.setAttribute("role", "tablist");

        var qdDiv = document.createElement("div");
        qdDiv.className = "tab-content query-dimsension-tabs-content";

        for (var i = 0; i < tabContent.childNodes.length; i++) {
            var item = $(tabContent.childNodes[i]);

            if (item.children().length < 1) {
                item.append(qdUl);
                item.append(qdDiv);
            }
        }

        /*** Create li in nav-tabs and tab-panel in tab-content for query and dimension builder tabs ***/

        var qLi = document.createElement("li");
        var dLi = document.createElement("li");
        qLi.setAttribute("role", "presentation");
        qLi.className = "active";
        dLi.setAttribute("role", "presentation");

        var navTabs = $(".query-dimension-tabs");

        var qA = document.createElement("a");
        var dA = document.createElement("a");
        qA.setAttribute("role", "tab");
        qA.setAttribute("data-toggle", "tab");
        qA.innerHTML = "Query Builder";
        dA.setAttribute("role", "tab");
        dA.setAttribute("data-toggle", "tab");
        dA.innerHTML = "Dimension Builder";

        for (var i = 0; i < navTabs.length; i++) {
            var item = $(navTabs[i]);

            if (item.children().length < 1) {
                item.append(qLi);
                item.append(dLi);
                qLi.appendChild(qA);
                dLi.appendChild(dA);
            }
        }

        var qdId = 0;
        $(".query-dimension-tabs").each(function () {
            $(this).find("a").each(function () {
                $(this).attr("href", "#tab" + qdId);
                $(this).attr("aria-controls", "tab" + qdId);
                ++qdId;
            });
        });

        var tabPane1 = document.createElement("div");
        var tabPane2 = document.createElement("div");
        tabPane1.className = "tab-pane active";
        tabPane1.setAttribute("role", "tabpanel");
        tabPane2.className = "tab-pane";
        tabPane2.setAttribute("role", "tabpanel");

        $(".tab-content:not(:first)").each(function () {
            $(this).append(tabPane1);
            $(this).append(tabPane2);
        });

        var pId = 0;
        $(".query-dimsension-tabs-content").each(function () {
            $(this).find(".tab-pane").each(function () {
                $(this).attr("id", "tab" + pId);
                ++pId;
            });

            if (!($.contains($(this), "form"))) {
                var firstPane = $($(this).children()[0]);
                var secondPane = $($(this).children()[1]);
                firstPane.load("/Webfront/QueryBuilder");
                secondPane.load("/Webfront/DimensionBuilder");
            }           
        });

        //var url = '@Url.Action("QueryDimensionTab", "Webfront")'; 

        /*** Add CodeMirror to convert all textboxes, needs timeout until all dom elements are loaded ***/

        setTimeout(function () {
            var textAreaId = 0;
            $(".AI-textarea").each(function () {
                $(this).attr("id", "text" + textAreaId);
                ++textAreaId;
            });

            var queryArr = [];
            var dimensionArr = [];

            var queryBuilder = $(tabPanelDiv).find(".QueryBuilder");
            queryBuilder.each(function (index, el) {
                var editorQuery = CodeMirror.fromTextArea(el, {
                    lineNumbers: true,
                    tabMode: "indent",
                    mode: "text/x-sql",
                    theme: "eclipse"
                });
                queryArr.push(editorQuery);
            });

            var dimensionBuilder = $(tabPanelDiv).find(".DimensionBuilder");
            dimensionBuilder.each(function (index, el) {
                var editorQuery = CodeMirror.fromTextArea(el, {
                    lineNumbers: true,
                    tabMode: "indent",
                    mode: "text/x-sql",
                    theme: "eclipse"
                });
                dimensionArr.push(editorQuery);
            });

            for (var i = 0; i < queryArr.length; i++) {
                queryArr[i].refresh();
            }

            for (var a = 0; a < dimensionArr.length; a++) {
                dimensionArr[a].refresh();
            }

        }, 100);
    });

Solution

  • I found what was causing CodeMirror older instances to disappear. It was because I was loading the forms inside a jQuery .each() that is looping through all divs that have these forms. I moved the load outside of it and that fixed it.

    Old code:

        var pId = 0;
        $(".query-dimsension-tabs-content").each(function () {
            $(this).find(".tab-pane").each(function () {
                $(this).attr("id", "tab" + pId);
                ++pId;
            });
    
            if (!($.contains($(this), "form"))) {
                var firstPane = $($(this).children()[0]);
                var secondPane = $($(this).children()[1]);
                firstPane.load("/Webfront/QueryBuilder");
                secondPane.load("/Webfront/DimensionBuilder");
            }           
        });
    

    New Code:

        var pId = 0;
        $(".query-dimsension-tabs-content").each(function () {
            $(this).find(".tab-pane").each(function () {
                $(this).attr("id", "tab" + pId);
                ++pId;
            });           
        });
    
        var formToLoad = $($(tabPanelDiv).children()[1]);
        var firstPane = $($(formToLoad)[0].firstChild);
        var secondPane = $($(formToLoad)[0].lastChild);
        firstPane.load("/Webfront/QueryBuilder");
        secondPane.load("/Webfront/DimensionBuilder");
    

    If anybody has recommendation or ideas on how I could load these partial views dynamically in jQuery in a different way that would be appreciated. I think this is probably not the best way.