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:
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.
I want the second tabs to call the refresh() method if its already been initiated, like I do for the main editor.
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);
The problems:
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
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)
fixed as a side effect of 2.
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">tab1</li>
<li>tab2</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
});
});
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.
if the problem occurs when codemirror render content within an hidden div, why not just diplay all the tab-panel div, then call codemirror, then hide unneeded tab ??
HTML:
<div role="tabpanel" class="tab-pane active" id="tp1">
<textarea class="code" data-lang="text/html">
<!--- content #1 here -->
</textarea>
</div>
<div role="tabpanel" class="tab-pane active hideAfterRendering" id="tp2">
<textarea class="code" data-lang="text/html">
<!--- content #2 here -->
</textarea>
</div>
<div role="tabpanel" class="tab-pane active hideAfterRendering" id="tp3">
<textarea class="code" data-lang="text/html">
<!--- content #3 here -->
</textarea>
</div>
SCRIPT:
<script>
$( document ).ready(function() {
//render codeMirror
$('.code').each(function() {
CodeMirror.fromTextArea(this, {
mode: "properties",
lineNumbers: true,
indentUnit: 4,
readOnly: true,
matchBrackets: true
});
});
//hide tab-panel after codeMirror rendering (by removing the extra 'active' class
$('.hideAfterRendering').each( function () {
$(this).removeClass('active')
});
});
</script>
Works well for me !!
Why not just to set timeout?.. just exec that code when tab clicked:
update_mirror = function() {
var codeMirrorContainer = $sec_tab.find(".CodeMirror")[0];
if (codeMirrorContainer && codeMirrorContainer.CodeMirror) {
codeMirrorContainer.CodeMirror.refresh();
}
}
// Attention! magic!
setTimeout(update_mirror, 100);
That helped me a lot while I had a battle against that problem. Thanks to Sekaryo Shin, he saved my time.
Related
I'm trying to modify this pen I found on CodePen. I'd like to be able to open a specific list on the page from another page. Clicking the link should open the corresponding section on the next page on page load.
I'm a bit of a newbie when it comes to jQuery, so I appreciate any help I can get. I've tried searching around and have an idea of what I need to target, but I haven't been able to make it happen. Here is my code:
HTML:
<!--Link on Previous Page-->
Click Here
<!--Target List-->
<div class="integration-list">
<ul>
<li class="integration">
<a class="expand" id="list">
<div class="expand_intro"><h3 class="teal_bold">Click Here</h3></div>
<div class="right-arrow">▼</div>
</a>
<div class="detail">
<div><p>Lorem Ipsum Dolor...</p></div>
</div>
</li>
</ul>
</div>
JS:
$(function() {
$(".expand").on( "click", function() {
$(this).next().slideToggle(100);
$expand = $(this).find(">:nth-child(2)");
if($expand.text() == "▼") {
$expand.text("▲");
} else {
$expand.text("▼");
}
var hash = window.location.hash;
var thash = hash.substring(hash.lastIndexOf('#'), hash.length);
$('.expand').find('a[href*='+ thash + ']').trigger('click');
});
});
Few things that I did to get it to work:
The trigger event is probably firing before the handler is actually attached. You can use setTimeout as a way around this.
Also, even with setTimeout around $('.expand').find('a[href*='+ thash + ']').trigger('click'); it didn't work for me. I changed that to simply $(thash).click();.
The complete code of the "expand.js" file:
$(function() {
var hash = window.location.hash;
var thash = hash.substring(hash.lastIndexOf('#'), hash.length);
setTimeout(function() {
$(thash).click();
}, 10);
$(".expand").on( "click", function() {
$(this).next().slideToggle(100);
$expand = $(this).find(">:nth-child(2)");
if($expand.text() == "â–¼") { //If you copy/paste, make sure to fix these arrows
$expand.text("â–²");
} else {
$expand.text("â–¼");
}
});
});
Apparently the arrows don't display properly here, so watch that if you copy/paste this.
$(function() {
$('#toggle3').click(function () {
$('.toggle').hide('1000');
$('.toggle').text('I would like to add a navigation menu here'); // <--
$('.toggle').slideToggle('1000');
return false;
});
});
I am wondering the best way to edit the above code snippet to be able to hold HTML / CSS as I plan on calling a custom menu within. I will be using this snippet multiple times and calling multiple menus to trigger with toggle.
If at all possible, try to avoid embedding html on javascript: you're likely to run into escaping issues and multiline strings, and the overall result usually isn't pretty.
You might want to store the HTML on the DOM itself:
<div>
<span class="toggle" data-toggle="foo">Toggle foo</span>
<span class="toggle" data-toggle="bar">Toggle bar</span>
</div>
<div id="navmenu-store">
<div class='navmenu' data-for-toggle="foo">
navmenu "foo"
</div>
<div class='navmenu' data-for-toggle="bar">
navmenu "bar"
</div>
</div>
On the CSS, hide the 'store':
#navmenu-store {
display: none;
}
And then, with javascript, add the navmenus when requested:
$(".toggle").each(function() {
var $toggle = $(this);
var toggleName = $toggle.data("toggle");
var $navmenu = $(".navmenu[data-for-toggle=" + toggleName + "]");
// Store the navmenu on the toggle for later access
$navmenu.remove();
$toggle.data("navmenu", $navmenu);
});
$(".toggle").on("click", function() {
var $toggle = $(this);
var $navmenu = $toggle.data("navmenu");
var isInTheDom = $.contains(document, $navmenu[0]);
if(isInTheDom) {
$navmenu.remove();
} else {
// Here I'm inserting the navmenu after the toggle;
// you can do whatever you want
$navmenu.insertAfter($toggle);
}
});
I've created a very simple jsbin as a proof of concept: http://jsbin.com/OwUWAlu/1/edit
I have some collapsible panel divs that have a title attribute. When my jQuery opens the panel, I want the title attribute to change and I want to specify the div to change via the class of the panel currently being opened. i.e. this.div.class change title to "whatever".
To make the code stupid simple for your to follow:
<div class="panelContainer">
<div class="service">
<div class="serviceBrief" title="Click to Read More">
<p>Some Stuff for closed panel</p>
</div> <!-- end serviceBrief -->
<div class="serviceDescContainer">
<div class="serviceDesc">
<p>some more stuff that shows when panel is open</p>
</div><!-- end serviceDesc -->
</div><!-- end serviceDescContainer -->
</div><!-- end service -->
<div class="service">
<div class="serviceBrief" title="Click to Read More">
<p>Some Stuff for closed panel</p>
</div> <!-- end serviceBrief -->
<div class="serviceDescContainer">
<div class="serviceDesc">
<p>some more stuff that shows when panel is open</p>
</div><!-- end serviceDesc -->
</div><!-- end serviceDesc Container -->
</div><!-- end service -->
</div> <!-- end panelContainer -->
I understand how to do this using ID's
$('#sampleID').attr('title', 'Click to Read More');
But I want to do this referencing the div class to change the title attribute so when the panel is open the title="Click to Read Less"
I thought this would work:
$('.serviceBrief').attr('title', 'Click to Read Less');
and it does, but obviously it changes all instances of the title attribute instead of just the one that is open. I know I am missing making this a "this" type command in jQuery, but all my various attempts are failing and I can't for the life of me find a reference anywhere.
Can someone point me in the right direction? Thanks!
$(document).ready(function() {
$('.serviceBrief').each(function(){
$(this).append('<div class="panelOpenArrow"></div><div class="panelClosedArrow"></div>');
});
$('.serviceBrief').click(function(){
if ($(this).parent().is('.open')) {
$(this).closest('.service').find('.serviceDescContainer').animate({'height':'0'},500);
$(this).closest('.service').find('.panelOpenArrow').fadeOut(500);
$(this).closest('.service').find('.panelClosedArrow').animate({'height': '25px'});
$(this).closest('.service').removeClass('open');
}else{
var newHeight = $(this).closest('.service').find('.serviceDesc').height() + 'px';
$(this).closest('.service').find('.serviceDescContainer').animate({'height':newHeight},500);
$(this).closest('.service').find('.panelOpenArrow').fadeIn(500);
$(this).closest('.service').find('.panelClosedArrow').animate({'height':'0'});
$(this).closest('.service').addClass('open');
}
});
});
Why not just do:
$('.serviceBrief').click(function(){
if ($(this).parent().is('.open')) {
$(this).attr('title', 'Click to Read Less');
//rest of your code
You're right on the money. Just reference $(this) in your click event to apply the attribute to the clicked element, and not all .serviceBrief elements:
$('.serviceBrief').click(function(){
if ($(this).parent().is('.open')) {
$(this).attr( "title", "Click to Read Less");
$(this).closest('.service').find('.serviceDescContainer').animate({'height':'0'},500);
$(this).closest('.service').find('.panelOpenArrow').fadeOut(500);
$(this).closest('.service').find('.panelClosedArrow').animate({'height': '25px'});
$(this).closest('.service').removeClass('open');
}else{
var newHeight = $(this).closest('.service').find('.serviceDesc').height() + 'px';
$(this).attr( "title", "Click to Read More");
$(this).closest('.service').find('.serviceDescContainer').animate({'height':newHeight},500);
$(this).closest('.service').find('.panelOpenArrow').fadeIn(500);
$(this).closest('.service').find('.panelClosedArrow').animate({'height':'0'});
$(this).closest('.service').addClass('open');
}
});
You can pass your handler function a parameter e containing the event that triggered it. The e.currentTarget property will contain the actual element that is handling the event, so you can change the attribute of that to only affect the current element.
$('.serviceBrief').click(function(e){
var objThis = $(e.currentTarget);
var objService = objThis.parent();
if (objService.is('.open')) {
objService.find('.serviceDescContainer').animate({'height':'0'},500);
objService.find('.panelOpenArrow').fadeOut(500);
objService.find('.panelClosedArrow').animate({'height': '25px'});
objService.removeClass('open');
objThis.attr("title", "Click to Read More");
}else{
var newHeight = objService.find('.serviceDesc').height() + 'px';
objService.find('.serviceDescContainer').animate({'height':newHeight},500);
objService.find('.panelOpenArrow').fadeIn(500);
objService.find('.panelClosedArrow').animate({'height':'0'});
objService.addClass('open');
objThis.attr("title", "Click to Read Less");
}
});
Here is a fiddle: http://jsfiddle.net/gtXpx/
It's a good idea to cache your DOM queries in objects to improve performance.
You could write this all much easier. I'll shorten it "some", meaning there is even more beyond what I will show, but hopefully this breakdown will help you understand how powerful jQuery can really be.
Example
<script type="text/javascript">
$(function() { // same as $(document).ready ... but SHORTER!
$('.serviceBrief').each(function(i) { // of course i stands for the 0 based index of the elements in this object
$(this).append( // many different ways to (pre|ap)pend elemnts, this is my fav do to the "readability"
$('<div />', { class: 'panelArrow panelOpenArrow' }), // set attributes in the { }
$('<div />', { class: 'panelArrow panelClosedArrow' }) // don't forget to place comma before appending more elements
// keep in mind, you could continue inside here and append to what is being appended!
)
}) // here I continue to "chain", no need to recall the same object
.click(function(e) { // simple click event, you might also look at "delegate"ing events
var aroOpen = $(this).children('.panelOpenArrow'),
aroClose = $(this).children('.panelClosedArrow');
// i establish these variable for ease of use in next event
// considering the way your HTML is layed, there's really no need for all that "find"ing, it's just more code time, less action time!
$(this).next('.serviceDescContainer').slideToggle(500, function(e) { // this is much the same as what you were trying to do using .animate
if ($(this).is(':visible')) { // kind of like your class check, except this checks the display, opacity, and even considers height (in newer jQuery versions) for "visibility"
// at this point, this first line is "unneccesary", but I left it here in case you were doing some "CSS" using that class name
$(this).closest('.service').addClass('open');
// .stop prevents animations previously taking place, like if a user clicks this real fast
aroOpen.stop().fadeOut(500);
aroClose.stop().animate({ height: '25px' });
}
else {
$(this).closest('.service').removeClass('open');
aroOpen.stop().fadeIn(500);
aroClose.stop().animate({ height: 0 });
}
})
});
})
</script>
More Reading
How Does Chaining Work?
.ready()
.append()
learn to .delegate in jQuery 1.7+ with .on()!
.slideToggle()
Example with/out Comments
<script type="text/javascript">
$(function() {
$('.serviceBrief').each(function(i) {
$(this).append(
$('<div />', { class: 'panelArrow panelOpenArrow' }),
$('<div />', { class: 'panelArrow panelClosedArrow' })
)
})
.click(function(e) {
var aroOpen = $(this).children('.panelOpenArrow'),
aroClose = $(this).children('.panelClosedArrow');
$(this).next('.serviceDescContainer').slideToggle(500, function(e) {
if ($(this).is(':visible')) {
$(this).closest('.service').addClass('open');
aroOpen.stop().fadeOut(500);
aroClose.stop().animate({ height: '16px' });
}
else {
$(this).closest('.service').removeClass('open');
aroOpen.stop().fadeIn(500);
aroClose.stop().animate({ height: 0 });
}
})
});
})
</script>
I have the following javascript that shows or hides a div when a link is clicked:
<script type="text/javascript">
$(document).ready(function(){
$('.show_hide').showHide({
speed: 500, // speed you want the toggle to happen
changeText: 0, // if you dont want the button text to change, set this to 0
showText: 'View',// the button text to show when a div is closed
hideText: 'Close' // the button text to show when a div is open
});
});
(function ($) {
$.fn.showHide = function (options) {
//default vars for the plugin
var defaults = {
speed: 1000,
easing: '',
changeText: 0,
showText: 'Show',
hideText: 'Hide',
};
var options = $.extend(defaults, options);
$(this).click(function () {
// this var stores which button you've clicked
var toggleClick = $(this);
// this reads the rel attribute of the button to determine which div id to toggle
var toggleDiv = $(this).attr('rel');
// here we toggle show/hide the correct div at the right speed and using which easing effect
$(toggleDiv).slideToggle(options.speed, options.easing, function() {
// this only fires once the animation is completed
if(options.changeText==1){
$(toggleDiv).is(":visible") ? toggleClick.text(options.hideText) : toggleClick.text(options.showText);
}
});
return false;
});
};
})(jQuery);
</script>
The problem is, I have two such divs, but I only want one to be displayed at a time. So, if someone clicks on the link to display div 2, and div 1 is already displayed, it should hide itself first before displaying div2.
Relevant HTML:
<div class="button">FAQs</div>
<div class="button">Contact</div>
<div id="faq" class="faq">FAQs here </div>
<div id="contact" class="faq">Contact form here </div>
I don't have any experience with JS/Jquery, but I tried adding this code, which didn't work:
var otherDiv;
if ($(this).attr('rel') == 'contact')
otherDiv = 'faq';
else
otherDiv = 'contact';
if ($(otherDiv).is(":visible"))
$(otherDiv).slideToggle(options.speed);
Most people use CSS classes as a place to store 'metadata'. When a div becomes visible, add a class to it like "toggledVisible" or whatever. When a new toggle is clicked, find all instances of "toggledVisible", hide them, and remove that class.
Alternatively, you always keep track of some sort of "currentlyVisible" object and toggle it.
Maybe jQuery ui accordion is an alternative?
HTML:
<div class="accordion">
<h3>FAQs</h3>
<div id="faq" class="faq">FAQs here</div>
<h3>Contact</h3>
<div id="contact" class="faq">Contact form here</div>
</div>
JS:
jQuery(document).ready(function() {
jQuery(".accordion").accordion();
});
Also see my jsfiddle.
=== UPDATE ===
JS:
jQuery(document).ready(function() {
jQuery(".accordion").accordion({
autoHeight: false
});
});
Also see my updated jsfiddle.
Try to add this row in your click() function
$('.faq').not(this).hide();
This will hide all shown divs except the one you clicked.
hi i am trying to make a wizard for first time i want to disable all accordion tabs when i click on the link it enable next tab and open it..
i hve this code but it disable all tabs :(
thanks
$(function() {
$("#list1a").accordion({
autoHeight: false,
navigation: false
});
});
$("#list1a").accordion("disable");
$("#list1a").accordion("activate", 2 );
Don't use the accordion for that, it's not intended for wizardry. And since there's no wizard component available in jquery UI, lets make our own ;)
html:
<ul class="ui-wizard">
<li class="ui-wizard-panel">
<h3 class="ui-wizard-header">panel 1</h3>
<div class="ui-wizard-content">
Panel content
<span class="ui-wizard-next">Goto next</span>
</div>
</li>
<li class="ui-wizard-panel">
<h3 class="ui-wizard-header">panel 1</h3>
<div class="ui-wizard-content">
Panel content
<span class="ui-wizard-next">Goto next</span>
</div>
</li>
</ul>
javascript plugin:
$.fn.wizard = function(){
this.find('.ui-wizard-content').hide();
this.find('.ui-wizard-content:first').show();
this.find('.ui-wizard-content:last .ui-wizard-next').hide(); // just in case
this.delegate('.ui-wizard-next', 'click', function(){
// very long jquery chain...
$(this).closest('.ui-wizard-content')
.hide('fast')
.closest('.ui-wizard-panel')
.next()
.find('.ui-wizard-content')
.show('fast');
});
}
javascript impl:
$(".ui-wizard").wizard();
Ofcourse.. you'd have to theme it yourself, though copy/pasting and renaming accordion styles gets you a long way. A nicer way would be to make an official wizard widget out of this.
Can also check out this code: http://github.com/desdev/jWizard/
Think it's exactly what you need.
Try ui-state-disabled class: http://api.jqueryui.com/theming/css-framework/
Consider this piece of code that allows user go back, but not go to next accordion tab:
function disableAccordionNextTabs () {
var $accordion = $(".accordion");
var active = $accordion.accordion('option', 'active');
var $headers = $accordion.find($accordion.accordion('option', 'header'));
$headers.addClass('ui-state-disabled');
for (var i = active; i >= 0; i--) {
$headers.eq(i).removeClass('ui-state-disabled');
}
}