Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootstrap tabs with CodeMirror

I have a pretty complex page where I have a number of instances of CodeMirror in hidden tabs within tabs. To then make it even more complex I remember the last active tabs.

I've manage to get it half working (http://codepen.io/anon/pen/LheaF) the problems are with the Second Editor tabs:

  1. Its loading the Second tabs before the main Code Mirror tabs has been clicked. When you do click the Code Mirror tab it doesn't load the editor correctly either, until you click twice.
  2. I want the second tabs to call the refresh() method if its already been initiated, like I do for the main editor.
  3. Bug where its duplicating the secondary editors

(function($) {
    var mainEditor;

    function initMainCodeEditor() {
        if (mainEditor instanceof CodeMirror) {
            mainEditor.refresh();
        } else {
            // Load main editor
            var el = document.getElementById("codifyme");
            mainEditor = CodeMirror.fromTextArea(el, {
                lineNumbers: true
            });
            mainEditor.setSize('100%', 50);
        }
    }

    function initSecondaryCodeEditor() {
        var $active = $('#code_mirror_editors > .active > a');
        var $sec_tab = $($active.data('target'));

        CodeMirror.fromTextArea($sec_tab.find('textarea')[0], {
            lineNumbers: true
        });
    }

    $(document).ready(function() {

        // Only load editors if tab has been clicked
        $('#maintabs > li > a[data-target="#codemirror"]').on('shown.bs.tab', function(e) {
            initMainCodeEditor();
        });

        $('#code_mirror_editors > li > a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
            initSecondaryCodeEditor();
        });

        // Remember tabs
        var json, tabsState;
        $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
            tabsState = localStorage.getItem("tabs-state");
            json = JSON.parse(tabsState || "{}");
            json[$(e.target).parents("ul.nav.nav-pills, ul.nav.nav-tabs").attr("id")] = $(e.target).data('target');

            localStorage.setItem("tabs-state", JSON.stringify(json));
        });

        tabsState = localStorage.getItem("tabs-state");

        json = JSON.parse(tabsState || "{}");
        $.each(json, function(containerId, target) {
            return $("#" + containerId + " a[data-target=" + target + "]").tab('show');
        });

        $("ul.nav.nav-pills, ul.nav.nav-tabs").each(function() {
            var $this = $(this);
            if (!json[$this.attr("id")]) {
                return $this.find("a[data-toggle=tab]:first, a[data-toggle=pill]:first").tab("show");
            }
        });

    }); // doc.ready
})(jQuery);
like image 594
John Magnolia Avatar asked Oct 21 '14 13:10

John Magnolia


3 Answers

The problems:

  1. it might happen that you create the CodeMirror on an element which is not visible (one of it's parents has display: none). This breaks various calculations done by CodeMirror
  2. by getting the CodeMirror instance right from the CodeMirror container element enables us to call refresh everytime you want (by finding the .CodeMirror next to your textarea)
  3. fixed as a side effect of 2.
like image 72
lloiser Avatar answered Sep 18 '22 01:09

lloiser


HTML

Here's a bootstrap tab frame, if you use jquery-UI, it's gonna be a little different.

<ul class="nav nav-tabs">
    <li class="active"><a href="#tab1" data-toggle="tab">tab1</a></li>
    <li><a href="#tab2" data-toggle="tab">tab2</a></li>
</ul>

<div class="tab-content">
    <div class="tab-pane fade in active" id="tab1"><textarea type="text" id="tab1Content" name="xxx"></textarea></div>    
    <div class="tab-pane fade" id="tab2"><textarea type="text" id="tab2Content" name="yyy"></textarea></div>
</div>

JS

var cm1, cm2;
$(document).ready(function () {
    //when the bootstrap tab is fully shown, call codemirror's refresh().
    //Also, if you use jquery-UI, it's gonna be different here. 
    $('.nav-tabs a').on('shown.bs.tab', function() {
        cm1.refresh();
        cm2.refresh();
    });  
    cm1 = CodeMirror.fromTextArea(document.getElementById("tab1Content"), {
        lineNumbers: true
    });
    cm2 = CodeMirror.fromTextArea(document.getElementById("tab2Content"), {
        lineNumbers: true
    });
});
like image 22
Gefan Meng Avatar answered Sep 20 '22 01:09

Gefan Meng


Working Solution: http://codepen.io/anon/pen/hupwy

I addressed issues 2 and 3 as I could not replicated 1.

I used a hash table to store the second editor CodeMirror objects. Then I modified your existing mainEditor code to refresh the objects if they already exist.

var seccondEditor = new Object();
function initSecondaryCodeEditor(){  
  var $active = $('#code_mirror_editors > .active > a');      
  var $sec_tab = $($active.data('target'));
  var sec_edi = ($sec_tab.find('textarea')[0]);
  if(seccondEditor[sec_edi.id] instanceof CodeMirror){
            seccondEditor[sec_edi.id].refresh();
      } else {
    seccondEditor[sec_edi.id] = CodeMirror.fromTextArea(sec_edi, {lineNumbers: true});
  }
}

I'm not well versed in CodeMirror so this might no be the most elegant solution, but it looks like the code above prevents the duplicates you were seeing. Hope this helps.

like image 42
JSuar Avatar answered Sep 19 '22 01:09

JSuar