Made a fiddle: http://jsfiddle.net/n6ub3/
I'm aware that the code has a LOT of repeating in it, its on the list to refactor once functionality is correct.
The behaviour i'm trying to achieve is if there is no selectedTab on page load, set the first tab in each group to selectedTab. If there is a selectedTab present, then use this as the default shown div.
However, as you can see from the fiddle its not working as planned!
If anyone has any ideas how to refactor this code down that'd be great also!
Change
if($('.tabs1 .tabTrigger:not(.selectedTab)')){
$('.tabs1 .tabTrigger:first').addClass('selectedTab');
}
to
if ( !$('.tabs1 .tabTrigger.selectedTab').length ) {
$('.tabs1 .tabTrigger:first').addClass('selectedTab');
}
Demo at http://jsfiddle.net/n6ub3/1/
They way you are doing it (the first code part) you are adding the .selectedTab class if there is at least one of the tabs in that group that is not selected at start .. (that means always)
Update
For a shortened version look at http://jsfiddle.net/n6ub3/7/
Your selector are doing exactly what you're writing them for.
$('.tabs3 .tabTrigger:not(.selectedTab)') is true has long as there is at least one tab that has not the selected tab (so always true in your test case).
So you should change the logic to !$('.tabs3 .tabTrigger.selectedTab').length which is true only if there are no selectedTab
WORKING DEMO with simplified code
$('.tabContent').hide();
$('.tabs').each(function(){
var search = $(this).find('div.selectedTab').length;
if( search === 0){
$(this).find('.tabTrigger').eq(0).addClass('selectedTab')
}
var selectedIndex = $(this).find('.selectedTab').index();
$(this).find('.tabContent').eq(selectedIndex).show();
});
$('.tabTrigger').click(function(){
var ind = $(this).index();
$(this).addClass('selectedTab').siblings().removeClass('selectedTab');
$(this).parent().find('.tabContent').eq(ind).fadeIn(700).siblings('.tabContent').hide();
});
That's all! You don't need all that ID's all around. Look at the demo!
With a couple of very minor changes you code can be reduced to:
$('.tabContent').hide();
$('.tabs').each(function(){
if($('.tabTrigger.selectedTab',$(this)).length < 1)
$('.tabTrigger:first').addClass('selectedTab');
});
$('.tabTrigger').click(function(){
var content = $(this).data('content');
$(this).parents('div').children('.tabContent').hide();
$(this).parents('div').children('.tabTrigger').removeClass('selectedTab');
$(this).addClass('selectedTab');
$('#' + content).show();
});
$('.tabTrigger.selectedTab').click();
Those changes are
Change the class on the surrounding div to just class="tabs.
Add a data-content attribute with the name of the associated content div
Live example: http://jsfiddle.net/gsTBQ/
Well, I'm a bit behind the times obviously; but, here's my updated version of your demo...
I have updated your fiddle as in the following fiddle: http://jsfiddle.net/4y3Xp/1/.
Basically I just tidied it up a bit, and to refactor I put everything in a separate function instead of having each of the cases in their own. This is basically just putting a new function in that does similar to what yours was doing (e.g. not modifying your HTML model), but I tried to clean it up a bit, and I also just made a function that took the tab number and did each of the items that way rather than needing a separate copy for each.
The main issue with the 'not' part of your query is that the function doesn't return a boolean; like all JQuery queries, it's returning all matching nodes. I just updated that part to return whether .selected was returning more than 0 results; if not, I go ahead and call the code to select the first panel.
Glad you got your problem resolved :)
$(document).ready(function(){
var HandleOne = function (i) {
var idxString = i.toString();
var tabName = '.tabs' + idxString;
var tabContent = tabName + ' .tabContent';
$(tabContent).hide();
var hasSelected = $(tabName + ' .tabTrigger.selectedTab').length > 0;
if (!hasSelected)
$(tabName + ' .tabTrigger:first').addClass('selectedTab');
var selectedTabId =
$(tabName + ' .tabTrigger.selectedTab').attr('id');
var selectedContentId = selectedTabId.replace('tab','content');
$('#' + selectedContentId).show();
$(tabName + ' .tabTrigger').click(function() {
$(tabName + ' .tabTrigger').removeClass('selectedTab');
$(tabName + ' .tabContent').hide();
$(this).addClass('selectedTab');
var newContentId = $(this).attr('id').replace('tab','content');
$('#' + newContentId).show();
});
}
HandleOne(1);
HandleOne(2);
HandleOne(3);
});
Related
I want to "copy" a certain elements and the change some of the text inside them with a regex.
So far so good: (/w working fiddle - http://jsfiddle.net/8ohzayyt/25/)
$(document).ready(function () {
var divs = $('div');
var patt = /^\d\./;
var match = null;
for (i = 0; i < divs.length; i++) {
match = ($(divs[i]).text().match(patt));
$(divs[i]).text($(divs[i]).text().replace(match[0], "5."));
}
});
HTML
<div>1. peppers</div>
<div>2. eggs</div>
<div>3. pizza</div>
This works exactly the way I want it, but I want to add some of the content dynamically, but when I try to change the content of the copied divs, nothing happens.
Please refer to this fiddle:
http://jsfiddle.net/8ohzayyt/24/
I have put some comments, to be more clear what I want to achieve.
I thing that your problem is that you're not passing an element to your changeLabel function, but just a string.
Look at this solution: http://jsfiddle.net/8ohzayyt/26/
Here is the line I changed to make your code work:
var newContent = $("<hr/><div id='destination'>" + $("#holder").html() + "</div>");
I just wrapped your HTML in $(). this creates an element from the string.
try:
var newContent = $("<hr/><div id='destination'>" + $("#holder").html() + "</div>");
EDIT:
Brief explanation What I've done.
In order to make $(el).find('div'); work changeLabel() needs an element. Instead of passing newContent as a string doing the above will make it pass as an element which will make $(el).find('div'); work.
I have a bit of javascript / jquery that upon pressing "enter" in a search box goes through all table rows and then hides those which are not applicable/ do not contain the string. This process is however is extremely taxing on slower systems and so I decided to implement a small loading gif so people know something is happening even though it seems the browser has frozen. The problem though is that the image never appears. I'm assuming it's because the browser freezes. So, now to my question. How can i either make the loop faster, use less computing power, and show the gif? Thank you very much
var $rows = $('tbody tr.visall');
$('#search').keydown(function(e) {
if (e.keyCode == 13){
$('.load').show();
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function () {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
};
$('.load').hide();
});
edit: this code goes through about 9000-10000 tr elements.
Hard to say if it will be enough for your data. But here is what you can do:
Reduce the amount of dom manipulations. You can add|remove class hidden to show hide rows.
If your text is static you can create text cache and do not extract it on every search.
You can use setTimeout to delay search execution to show loading gif. Not even sure you will need one. Searching in memory is quite fast.
Demo.
Code
$(function() {
var table = $('#mytable'), //your table
rows = table.find('tr').map(function(){ //all rows you need
return $(this);
}),
rowsCache = (function(from){ //text cache
return from.map(function(){
return this.text();
});
}(rows));
function delay(func) { //delayed function executor
setTimeout(func, 13);
}
var load = $('#load'); //your loader
$('#search').keydown(function(e){
var val;
if(e.keyCode === 13) {
val = $.trim($(this).val());
load.show();
table.hide(); //release dom
delay(function() {
//search in text cache
var toShow = rowsCache.map(function(_, row) {
return row.indexOf(val) > -1;
});
rows.each(function(i){
//simply toggle class let css work for you
this.toggleClass('hidden', !toShow[i]);
});
load.hide();
table.show();
});
}
});
});
Personally I would say drop the filter and use CSS selectors instead then it's just basic DOM manipulation which jQuery should be optimized for.
See http://api.jquery.com/contains-selector/ for documentation and here is a little fiddle http://jsfiddle.net/tgz5X/
Here is what I use in the fiddle as a simple example
function search(mySearchValue) {
$("tr > td:contains(" + mySearchValue + ")").show();
$("tr > td:not(:contains(" + mySearchValue + "))").hide();
}
You would need more but you should get the idea behind this approach.
Or use filter still but with selectors, also this may trigger hide on element which will have show after so more DOM manipualtion
$("tr td").hide().filter(":contains(" + mySearchValue + ")").show();
So I have found all over stackoverflow how to remove an entire attribute, however I only want to remove the first value of an onClick attribute but not the second. All my different instances of each container have a unique function associated to them and they all share the first function in common... Once any of these containers are clicked, the first function needs to be gone but I need to retain the second without altering it at all. My code follows:
<div class="halves marginal" onClick="buildFrame(),viscalc()">
<div class="two_one marginal" onClick="buildFrame(),percentOf()">
etc etc
Once buildFrame() executes once:
function buildFrame(){
document.getElementById('screenframe').innerHTML = "<img src='img/screen.png'><iframe id='framecontent'>";
I would like to remove it from each class ( but keep percentOf(), viscalc() etc etc )
How can I remove only one attribute value and not the other?
so this is what i would do.
$("div.marginal").on("click", function(){
$(this).off("click", buildFrame);
});
that removes it from the event queue for the functions listener.
You cant do it with anon functions.
idea:
$("div.marginal").on("click", function(){
var funcs = $(this).attr("onclick").split(",");
$(this).attr("onclick", funcs[1]);
console.log("value of 'onclick'", $(this).attr("onclick"));
// or alternatively
$(this).off("click", funcs[0].substr(0,funcs[0].length-2))
//this breaks apart the onclick, turns off the click event for event0, while maintaining
// the second event.
});
the issue that i can see, from my point of view is that since it is inline, it really executes it once, so changing onclicks value might not be enough as it might not be refreshed. The above jquery though, will do what your case would want.
I think the most pin-pointed approach to this would be pass this in your buildFrame and then inside the buildFrame remove itself from the calling object.
<div class="halves marginal" onClick="buildFrame(this),viscalc()">
function buildFrame(obj){
document.getElementById('screenframe').innerHTML = "<img src='img/screen.png'><iframe id='framecontent'>";
// not sure why your not closing the iframe tag ill assume you wanted it this way
$( this ).off("click", buildFrame);
};
** EDIT **
Missed your edit where you want to remove it from all instances, this complicates it much, this solution is no longer viable.
Fairly straightforward to do with a custom selector. I think this can be done without the eval() but I'm not sure how offhand.
Here you go:
HTML
<div class="halves marginal" onClick="buildFrame();viscalc();"></div>
<div class="two_one marginal" onClick="buildFrame();percentOf();"></div>
Javascript
$.expr[':'].hasFrameMethod = function(obj){
var onClick = $(obj).attr('onClick');
return onClick != undefined ? onClick.split(';').indexOf("buildFrame()") != -1 : false;
};
function buildFrame(){
document.getElementById('screenframe').innerHTML = "<img src='img/screen.png'><iframe id='framecontent'>";
$('div:hasFrameMethod').each(function(idx, item){
htItem = $(item);
var itemClickFns = htItem.attr('onClick') != undefined ? htItem.attr('onClick').split(';') : (htItem.attr('onclick') != undefined ? htItem.attr('onclick').split(';') : "");
htItem.attr('onClick', '');
htItem.attr('onclick', '');
htItem.off("click");
// re-add any correct secondary actions
if(itemClickFns.length > 1){
var strFnClick = "function onclick(event) { ";
itemClickFns.forEach(function(elem,idx){
if(elem != "buildFrame()"){
if(elem.length > 0){
strFnClick += elem + ";";
htItem.attr('onclick', htItem.attr('onclick') + elem + ";");
}
}
});
strFnClick += "}";
// Rebind the event
htItem.prop('onclick', eval(strFnClick));
htItem.click(eval(strFnClick));
}
});
}
As long as this code loads after jQuery it should work. :)
I'm building a page that features a hierarchical tree-type structure. I've posted a simplified version of it at JSFiddle
It mostly works as I'd like but for one thing - on clicking closed a Brand-level row I would like, as well as the town and shoe rows to contract (which they do), for the anchors on the Town rows to change their text to '+'.
I've attempted to do so with
$(this).parent().parent().nextUntil(".TRBrand", ".TownToggle").text("+");
but try as I might it won't play nicely.
Can anyone point me in the right direction ...?
Nested lists are better for tree like structures. You can see the js is easier to write with this markup:
http://jsfiddle.net/RANmK/1/
There were several problems with your version:
The last <a> (for Reebok) had the wrong class : TRTown instead of TownToggle
Your nextUntil(...) for TownToggle was only stopping when it sees .TRTown, which means it hides too much when it is the last Town in the list and continues to hide the next brand as well. It should also stop on .TRBrasnd. You can specify both selectors by seperating them with a comma.
a.toggleTown was not targetted correctly when updating the text value to +
If I understand your requirements correctly, the following should do what you want: http://jsfiddle.net/Sx4qg/69/
$('.BrandToggle').click(function() {
var t = $(this);
var txt = t.text();
var tr = t.closest("tr");
if (txt == "+") {
tr.nextUntil(".TRBrand", ".TRTown").show();
} else {
tr.nextUntil(".TRBrand", ".TRTown, .TRShoes").hide();
tr.nextUntil(".TRBrand", ".TRTown").find("a.TownToggle").text("+");
}
t.text(txt == "+" ? "-" : "+");
});
$(".TownToggle").click(function() {
var t = $(this);
var txt = t.text();
var tr = t.closest("tr");
if (txt == "+") {
tr.nextUntil(".TRBrand,.TRTown", ".TRShoes").show();
} else {
tr.nextUntil(".TRBrand,.TRTown", ".TRShoes").hide();
}
t.text(txt == "+" ? "-" : "+");
});
Try this:
$(this).parent().parent().nextUntil(".TRBrand").find('.TownToggle').text("+");
http://jsfiddle.net/sangdol/Sx4qg/64/
Hope this fiddle will help
$(this).parent().parent().nextUntil("tr:not(.TRTown, .TRBrand)", ".TownToggle").text("+");
How could I change the text below so that the text within it has a number appended to it.
<div class="right">This is some text</div>
<div class="right">This is some text</div>
<div class="right">This is some text</div>
So the code above would become,
This is some text
This is some text
This is some text
you should use an ordered list... ol
or else you will need use css and add the content property your selector with the :after pseudo element.
How about the following?
$("div.right").each(function(i){
$(this).prepend((i + 1) + ". ");
});
UPDATE:
Here is one way that should work.
"number" is a custom element (it can be anything you want) that will/should be ignored by browsers.
$("div.right").each(function(i){
$(this).find("number").remove().end()
.prepend("<number>(i + 1) + ". </number>");
});
OR use the following which is probably a little slower but semantically correct...
$("div.right").each(function(i){
$(this).find("span.number").remove().end()
.prepend("<span class='number'>" + (i + 1) + ". </span>");
});
OR an even better way would be to prepend span.number before your first drag:
$(function(){ // document ready...
// caching the "numbers" will only work if you use the DOM
// for updating div position (like jQuery's "append()", "prepend()", "before()", and "after()") and not "innerHTML" or "html()"
var numbers = $("div.right").each(function(i){
$(this).prepend("<span class='number'>" + (++i) + "</span>. ");
}).find("span.number");
function dragEnd(){
// do your drag end stuff here...
numbers.each(function(i){
this.innerHTML = ++i;
});
)};
});
This is really an elaboration on another comment. I can't format code in a comment, I guess. You could use jQuery core's each:
$('div.right').each(function(ii){
html = $(this).html();
$(this).html(ii + '. ' + html);
});
jQuery selectors are your friend...
Get your stuff and loop on through something like this:
texts = $("div.right");
for(i = 0;i < texts.length;i++)
{
node = $(texts[i]);
content = node.html();
number = i + 1;
node.html(number + ". " + content);
}
Update: Jeez, last time post untested code straight off the dome here (disclaimer: not actually the last time). In the interest of correctness, I've updated it to at least run (and work!) if you still want to do it this way. Although I admit the other solutions are cleaner and more elegant.
Does this have to be done dynamically through jquery? Can't you just combine all that text into one div and then make a ordered list around it?
Using [] notation with a result set will give you the raw DOM element which does not have the html() function. Use the eq() function to get each element wrapped in a jQuery object.
You can also use each() as mentioned above, but I prefer straight 'for loops' so I don't have to adjust for 'this' if I'm in an event handler.
var texts = $("div.right");
var elem;
for(i = 1; i < texts.length; i++) {
elem = texts.eq(i);
html = elem.html();
elem.html(i + '. ' + html);
}