JS can't read a row generated by JS? - javascript

In an HTML file, I use JS to generate table rows to show data returned by database:
function appendResult(data) {
var result = JSON.parse(data);
var row = result.length;
if (row == 0) {
$("#productList").html("No matching result.");
} else {
$("#productList").html("");
var i = 0;
while (i < row) {
// For each return record, create a new row, then write data into cell accordingly. New records are always written to last cell.
$("#productList").append("<tr class='hightLight'><td class='sku'></td><td class='productName'></td><td class='description'></td><td class='qtyPerCtn'></td><td class='weight'></td><td class='upc'></td><td class='gtin'></td><td class='vendorCode'></td><td class='note'></td></tr>");
$("td.sku").last().html(result[i]["sku"]);
$("td.productName").last().html(result[i]["productName"]);
$("td.description").last().html(result[i]["description"]);
$("td.qtyPerCtn").last().html(result[i]["qtyPerCtn"]);
$("td.weight").last().html(result[i]["masterWeightLb"]);
$("td.upc").last().html(result[i]["UPC"]);
$("td.gtin").last().html(result[i]["gtin"]);
$("td.vendorCode").last().html(result[i]["vendorCode"]);
$("td.note").last().html(result[i]["note"]);
i++;
}
}
}
Then I have a function to highlight the row when the mouse rolls over it:
// high light row when mouse roll over
$(document).ready(function () {
$(".hightLight").hover(function () {
$(this).addClass("highLightOnRollOver");
}, function () {
$(this).removeClass("highLightOnRollOver");
});
});
But apparently this doesn't work. The highlight function doesn't work. But if I put a row in plain html, it works:
<table>
<tr class="hightLight"><td>test</td></tr>
</table>
Does it mean JS functions can't identify the elements generated by JS? How can I solve this problem?

This will work even if you add elements after the dom is ready:
// high light row when mouse roll over
$(document).ready(function () {
$("table")
.on('mouseenter', ".hightLight", function () {
$(this).addClass("highLightOnRollOver");
})
.on('mouseleave', ".hightLight", function () {
$(this).removeClass("highLightOnRollOver");
});
});

You'll have to use delegation, like this:
$(document).on("hover", ".hightLight", function () {
If the javascript is sourced before the DOM is created, it wont see it. Delegation gets around it by saying "look for a hover within document, if that hover is within .hightLight, then do this...
You could replace also document with a closer parent of .hightLight... it looks like #productList would probably work well.

Related

binding dynamic dom elements to content.js plugin

I have a plugin that im making use of called content.js http://innovastudio.com/content-builder.aspx
Im adding in dynamic divs to the page which I would like to have the content.js plugin assigned to it, so I can make use of its functionality.
On a single div, or already defined div within the page, I dont appear to have any issues with multiple divs.
However if I add in a div with the same class, I cant seem to bind the plugin to it.
Ive included the code for instantiating the div with the contentbuilder plugin, but I wondering if there is a way to bind it to new elements that are added to the page with the class of "letter". Or if there is a generic way of binding plugins to divs using jquery.
$('div.letter').contentbuilder({
enableZoom:false,
snippetOpen: true,
imageselect: 'images.html',
fileselect: 'images.html',
snippetFile: '/assets/templates/content-builder/default/snippets.html',
toolbar: 'left',
//sourceEditor: false,
onDrop:function(){
// function for when an item is dragged into the editable area
},
onRender: function () {
var coverLength = $("#coverpage div.row").length;
var mainContent = $("#maincontent div.row").length;
if(coverLength == 0)
{
$("#coverpage").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#coverpage div.no-content-on-page").remove();
}
if(mainContent == 0)
{
$("#maincontent").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#maincontent div.no-content-on-page").remove();
}
//custom script here
}
});
If you must add these divs in a dinamic way, i think that you should init the plugin for each time that you add a new div. To avoid init same div twice, use some class like in the following example:
function createLetter(){
$("body").append('<div class="letter mustBeActivated"></div>');
initContentBuilder();
}
function initContentBuilder(){
$('div.letter.mustBeActivated').contentbuilder({
enableZoom:false,
snippetOpen: true,
imageselect: 'images.html',
fileselect: 'images.html',
snippetFile: '/assets/templates/content-builder/default/snippets.html',
toolbar: 'left',
//sourceEditor: false,
onDrop:function(){
// function for when an item is dragged into the editable area
},
onRender: function () {
var coverLength = $("#coverpage div.row").length;
var mainContent = $("#maincontent div.row").length;
if(coverLength == 0)
{
$("#coverpage").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#coverpage div.no-content-on-page").remove();
}
if(mainContent == 0)
{
$("#maincontent").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#maincontent div.no-content-on-page").remove();
}
//custom script here
}
}).removeClass('mustBeActivated');
}

Replacing multiple <li> text simultaneously on hover using Javascript (not CSS!)

I have a requirement to change the text on hover of multiple menu items at once but I cannot use CSS and nor can I give each individual item its own CSS class. What I would like to do is when the mouse hovers anywhere over the .menu-wrapper the Javascript replaces each of the <li> item texts with the relevant replacement text.
I have a script which works perfectly for a single item:
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
</ul>
</div>
Javascript:
var originalText = $('.menu-wrapper > ul > li').text();
$('.menu-wrapper').hover(function () {
var $p = $(this).find('li');
$p.fadeOut(300, function () {
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
// set original text
$(this).find('li').fadeOut(300, function () {
$(this).text(originalText).fadeIn(300);
});
});
But obviously if you add multiple <li> items it breaks because it is only storing a single .text() variable and concatenates all the entries after the first mouseout event.
I tried using a switch statement to look for the value of the .text() and change the text value accordingly but it didn't work (my Javascript is weak...).
I'd appreciate any help with this. I only have four items to replace the text of so repeating any script as necessary is not a problem. Normally I would give each one it's own class identity and use what I already have but unfortunately I can't.
Please don't suggest using CSS as I already know how to do that but for this I need to use Javascript.
I could not find this question elsewhere.
Thanks!
Main issue is first line:
var originalText = $('.menu-wrapper > ul > li').text();
This will get all text from all elements in the collection:
What you could do is store that text on each element using jQuery data() by looping over the elements and dealing with instances:
$('.menu-wrapper > ul > li').each(function(){
$(this).data('original', $(this).text());
});
Then in mouseout part of hover read the previously stored text using data() again
$(this).find('li').fadeOut(300, function () {
var originalText = $(this).data('original');
$(this).text(originalText).fadeIn(300);
});
Several options for the new text:
Put it in markup as data attribute
<li data-alt_text="alternate word">
Then within mousenter callback of hover:
$p.fadeOut(300, function () {
$(this).text($(this).data('alt_text')).fadeIn(300);
});
Or put in array and use first loop to add the array data to element
var words=['W1','W2','W3'];
// first argument of "each" is "index"
$('.menu-wrapper > ul > li').each(function(index){
$(this).data(
{
'alt_text': words[index],
'original', $(this).text()
}
);
});
You can make use of javascripts ability to assign any property to an object (element) to store the original text instead of storing it in a single variable (or use jquery data functionality to do the same)
$('.menu-wrapper li').hover(function () {
$(this).fadeOut(300, function () {
this.originalText = $(this).text();
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text(this.originalText).fadeIn(300);
});
});
fiddle
For this to work, instead of binding to the .menu-wrapper div directly, you can use .menu-wrapper li to bind to the individual li elements inside the div. Afterwards the orignal text can be stored before changing it. The same can be done beforehand, storing all values, the advantage of this way is that you always store the latest value, in case the text is dynamically altered after startup.
To couple the replacement texts to the li elements, without altering the html safest would be to couple the replacement to the text. Easiest is an indexed based solution:
var replacements = ['replacement Word1', 'for word2' , 'third time\'s a charm'];
$('.menu-wrapper li').hover(function () {
var $this= $(this);
$this.fadeOut(300, function () {
$this.data('originalText', $this.text()).
text(replacements[$this.index()]).fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text($(this).data('originalText')).fadeIn(300);
});
});
fiddle
For completeness sake, this would be an alternative while using the li text (provided the text can be used as a property):
var replacements ={
WORD1 : 'replacement Word1',
WORD2 : 'for word2',
WORD3: 'third time\'s a charm'
};
$('.menu-wrapper li').hover(function () {
var $this= $(this);
$this.fadeOut(300, function () {
$this.data('originalText', $this.text()).
text(replacements[$this.text()]).fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text($(this).data('originalText')).fadeIn(300);
});
});
fiddle
Here's a short and simple solution to your problem:
var originalText;
$('.menu-wrapper').hover(function () {
var $p = $(this).find('li');
$p.fadeOut(300, function () {
this.originalText = $(this).text(); // STORES VALUE BEFORE REPLACEMENT
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
$(this).find('li').fadeOut(300, function () {
$(this).text(this.originalText).fadeIn(300);
});
});
Just store the value of that element in originalText before replacing it.
We can use two arrays to store Original text and New text. And then use $.each to loop through each of the lis and use their index to replace the text.
HTML :
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
<li>WORD2</li>
<li>WORD3</li>
</ul>
</div>
jQuery :
var originaltext = ['Word1','Word2','Word3'];
var newText = ['New text1','New text2','New text3'];
$('.menu-wrapper').hover(function () {
$('.menu-wrapper li').each(function(i){
$this = $(this);
$this.html(newText[i])
});
}, function(){
$('.menu-wrapper li').each(function(i){
$this = $(this);
$this.html(originaltext[i])
});
});
jsfiddle
Since all of the other answers here use jQuery, I'll add one done with vanilla js.
To do this, we're going to need to use a javascript closure. This is used so that on completion of the fade-out, we have (a) the element just faded and (b) which is far more important, an index into the originalStrings array. (B) is the more important here, because the target element is something the animate code already has - we could easily pass the original element to the callback function. However, we really need the index or the string that corresponds to each element. The closure gives a means to do so.
The following code will fade-out all/any matching elements and then perform a fade-in after changing the text.
Using the equations found here: Math: Ease In, ease Out a displacement using Hermite curve with time constraint we can then set about making some code that will perform a smooth fade/move/scale pitch/volume slide etc, etc. I did this an ended up a few functions that facilitate simple animations. I've included minified versions of them below, for an all-in-one complete solution that relies on no other resources.
<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
document.getElementById('goBtn').addEventListener('click', onButtonClick, false);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// animation stuff
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function cubicHermite(a,b,d,e,c){var g=a*a,f=g*a;return(2*f-3*g+1)*b+(f-2*g+a)*e+(-2*f+3*g)*d+(f-g)*c}
function interp(a,b,d,e,c){var g,f;f=e/(a/2+b+d/2);g=f*a/2;f*=b;return c<=a?cubicHermite(c/a,0,g,0,f/b*a):c<=a+b?g+f*(c-a)/b:cubicHermite((c-a-b)/d,g+f,e,f/b*d,0)}
function linear(a){return a}
function cubic(a){return interp(0.35,0.3,0.35,1,a)}
function doAnimStep(a,b,d,e,c){a<=b?(setTimeout(function(){doAnimStep(a,b,d,e,c)},d),e(a/b),a++):void 0!=c&&null!=c&&c()}
function doAnim3(totalMs, stepCallbackFunc, doneCallbackFunc)
{
var stepDelay = 1000 / 60.0; // set anim to 60 fps
var numSteps = (totalMs / stepDelay)>>0;
setTimeout( doAnim3TimeoutCallback, stepDelay );
function doAnim3TimeoutCallback()
{
doAnimStep(0, numSteps, stepDelay, stepCallbackFunc, doneCallbackFunc);
};
}
function animFadeOut(elem, callback){ doAnim3(500,function(raw){elem.style.opacity=1-cubic(raw)},callback); }
function animFadeIn(elem, callback) { doAnim3(500,function(raw){elem.style.opacity=cubic(raw)},callback); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var replacementStrings = [ "replacement 1", "I'm next", "mee too", "fourth item" ];
function onButtonClick(evt)
{
var originalStrings = [];
var targetLiElems = document.querySelectorAll('.menu-wrapper > ul > li');
for (var i=0,n=targetLiElems.length;i<n;i++)
{
var curElem = targetLiElems[i];
originalStrings.push(curElem.innerText);
animFadeOut(curElem, createFunc(i) );
}
function createFunc(i)
{
return function(){ var curElem = targetLiElems[i]; curElem.innerText = replacementStrings[i]; animFadeIn(curElem); };
}
}
</script>
<style>
</style>
</head>
<body>
<button id='goBtn'>Change the text</button>
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
<li>WORD2</li>
<li>WORD3</li>
<li>WORD4</li>
</ul>
</div>
</body>
</html>

Hiding images with same data value jquery

Im trying to code a site where the objective is to click on two identical images and it hides the both the images you've managed to match to eachother.
$(document).ready(function(){
var animal1;
var animal2;
$(".memory1").on("click", function(){
animal1 = $(this).data('animal');
});
$(".memory2").on("click", function(){
animal2 = $(this).data('animal');
if (animal1==animal2){
$(this).data('animal').hide();
}
else {
alert("Wrong, Try again!");
}
});
});
so the line where its going wrong is obviously
$(this).data('animal').hide();
But I cant figure out a way to hide both images, or a better way of going about it.. :/
http://jsfiddle.net/4vgfca76/
This doesn't work the way you think it does
$(this).data('animal').hide();
When data is used with one argument, it get's the data attribute, which you should already know as you're doing it a few lines above.
What you get is the string hund etc. and that string doesn't have a hide() method.
You should be using the attributes selector to select the elements with that attribute instead
$(document).ready(function () {
var animal1, animal2;
$(".memory1").on("click", function () {
animal1 = $(this).data('animal');
});
$(".memory2").on("click", function () {
animal2 = $(this).data('animal');
if (animal1 == animal2) {
$('img[data-animal="'+animal1+'"]').hide();
} else {
alert("Fel! Försök igen");
}
});
});

jQuery focus between .click() events

This is homework, just declaring it now.
I have to load a 'quiz' via XML (completed successfully), and generate td cells (done) to display said questions (not done, test data instead).
Here is my source code for the javascript
var selected;
var $cell;
var $cell2;
$(document).ready(function() {
$("#getTitle").click(function() {
selected = $("#quizname>option:selected").text();
$("#quiztitle").html(selected+" Quiz");
$("#quiz2").html(selected+" Quiz");
murl = "quizdata.xml";
$.ajax({type:"GET",
url:murl,
success:loaddata,
cache:false,
dataType:"xml",
data:selected,
error:ajaxerror
});
});
});
var $xml;
function loaddata(respobj,status,xhr) {
//to do:
//dynamic td creation for each xml question
$("#questions").empty();
$xml = $(respobj).find("quiz:contains("+selected+")");
for (var i=0;i<$xml.attr("qnum");i++) {
$('<tr>').attr("id","questions"+(i+1)).appendTo("#questions");
$("<td>").attr("id","question"+(i+1)).appendTo("#questions"+(i+1));
$("#question"+(i+1)).html((i+1)+". "+$xml.find("question[num='"+(i+1)+"']").text());
$("#question"+(i+1)).addClass("th.thirty");
$("<td>").attr("id","blank_question"+(i+1)).appendTo("#questions"+(i+1));
$("#blank_question"+(i+1)).addClass("question");
$("#blank_question"+(i+1)).html("Put Answer Here");
$("<td>").attr("id","answer"+(i+1)).appendTo("#questions"+(i+1));
$("#answer"+(i+1)).addClass("question");
$("#answer"+(i+1)).html((i+1)+". "+$xml.find("answer[num='"+(i+1)+"']").text());
$("#answer"+(i+1)).click(selectCell);
}
}
function selectCell() {
$cell = $(this);
$cell.css("background-color","red");
for (i=0;i<$xml.attr("qnum");i++) {
$("#blank_question"+(i+1)).click(function() {
$cell2 = $(this);
$cell.css("background-color","lightgray");
temp_text = $cell.text();
temp_id = $cell.attr("id");
$cell.attr("id",$cell2.attr("id"));
$cell.text($cell2.attr('id'));
$cell2.attr("id",temp_id);
$cell2.text(temp_id);
$("#answer"+(i+1)).unbind("click");
$("#answer"+(i+1)).bind("click", function() {
selectCell();
});
});
}
}
function swapCell() {
$cell.css("background-color","lightgray");
alert($(this).attr("id"));
}
function ajaxerror(xhr,status,error) {
$("td#desc").attr("class","");
$("td#desc").html("xhr="+xhr);
$("td#desc").append("<br /><br />status="+status)
$("td#desc").append("<br /><br />error="+error);
}
My issue is (try it here: HomeWork Link) that the first time you click the first cell, swap it with the second, it works. However, it only works every OTHER click and swap, which makes me think that there are some binding issues or focus issues because I need it to swap seamlessly. Is there an obvious error in the code or am I missing a specific focus/bind event?
Thanks!
Edit: the values being displayed AFTER swapping are the cells ID attribute
After googling "jquery recursive .click binding" I found that instead of .click() I changed it to .live() and that works perfectly.

Refactoring Code

Let's say I have the following code:
$(function () {
$(".buy-it-now.ribbon").click(function () {
$(".bid-to-beat.ribbon.active").removeClass("active");
$(".bid-to-beat.ribbon").addClass("inactive");
$(".buy-it-now.ribbon.inactive").removeClass("inactive");
$(".buy-it-now.ribbon").addClass("active");
$(".bid-now").hide();
$(".buy-now").show();
$(".add-to-cart").hide();
})
$(".bid-to-beat.ribbon").click(function () {
$(".buy-it-now.ribbon.active").removeClass("active");
$(".buy-it-now.ribbon").addClass("inactive");
$(".bid-to-beat.ribbon").removeClass("inactive");
$(".bid-to-beat.ribbon").addClass("active");
$(".buy-now").hide();
$(".bid-now").show();
$(".add-to-cart").show();
});
});
It is a simple function that allows for multiple UI related things to happen on the front-end of a site I am working on. I am fairly (very) new to jQuery and JavaScript in general and am learning about refactoring and making my code more condensed now. The way I currently write code is sort of line per thought I have. So my question is how would an experienced developer write this same code? Or rather, how could I refactor this code?
Try the following:
$(function () {
var $handlers = $('.buy-it-now.ribbon, .bid-to-beat.ribbon');
$handlers.click(function() {
$handlers.toggleClass("active inactive");
var $elements = $(".bid-now, .add-to-cart"),
$buyElement = $(".buy-now");
if($(this).is('.buy-it-now.ribbon')) {
$elements.hide();
$buyElement.show();
} else {
$elements.show();
$buyElement.hide();
}
});
});
This question would be better suited for codereview, but yes it can be condensed a little using method chaining.
$(function () {
$(".buy-it-now.ribbon").click(function () {
$(".bid-to-beat.ribbon").removeClass("active").addClass("inactive");
$(".buy-it-now.ribbon").removeClass("inactive").addClass("active");
$(".bid-now").hide();
$(".buy-now").show();
$(".add-to-cart").hide();
})
$(".bid-to-beat.ribbon").click(function () {
$(".buy-it-now.ribbon").removeClass("active").addClass("inactive");
$(".bid-to-beat.ribbon").removeClass("inactive").addClass("active");
$(".buy-now").hide();
$(".bid-now").show();
$(".add-to-cart").show();
});
});
You could condense it further by pre selecting the elements and caching them in variables before the click events as long as no elements are added or removed during the life of the page.
As your code it is you can combine some of the selectors into a single line. And also because your elements looks to be static you can cache them into a variable and use them later as it reduces the number of times a element is looked up in the DOM reducing the accessing time..
Also you can limit the scope of these variables or selectors by encasing them in an object or a closure..
Maybe something in these lines..
$(function () {
cart.init();
});
var cart = {
elems : {
$buyRibbon : null,
$bidRibbon : null,
$bidNow: null,
$buyNow: null,
$addToCart: null
},
events : {
},
init : function() {
this.elems.$buyRibbon = $(".buy-it-now.ribbon");
this.elems.$bidRibbon = $(".bid-to-beat.ribbon");
this.elems.$bidNow = $(".bid-now") ;
this.elems.$buyNow = $(".buy-now") ;
this.elems.$addToCart = $(".add-to-cart") ;
this.events.buyClick();
this.events.bidClick();
}
};
cart.events.buyClick = function() {
cart.elems.$buyRibbon.on('click', function(){
cart.elems.$bidRibbon.removeClass('active').addClass('inactive');
cart.elems.$buyRibbon.removeClass('inactive').addClass('active');
cart.elems.$bidNow.hide();
cart.elems.$buyNow.show();
cart.elems.$addToCart.hide();
});
}
cart.events.bidClick = function() {
cart.elems.$bidRibbon.on('click', function(){
cart.elems.$buyRibbon.removeClass('active').addClass('inactive');
cart.elems.$bidRibbon.removeClass('inactive').addClass('active');
cart.elems.$bidNow.show();
cart.elems.$buyNow.hide();
cart.elems.$addToCart.show();
});
}
So basically in here your whole cart is a object ..And the cart has different properties which are related to this.. You follow the principles of object oriented programming here..
Using closures I heard gives you better design limiting the scope of your code..
Might I suggest something like this:
$(function () {
var buyNowButton = $('buy-it-now.ribbon'),
bidToBeatButton = $('.bid-to-beat.ribbon'),
buyNowEls = $('.buy-now'),
bidToBeatEls = $('.bid-now,.add-to-cart');
var toggleButtons = function(showBuyNow){
buyNowButton.toggleClass('active', showBuyNow);
bidToBeatButton.toggleClass('active', !showBuyNow);
buyNowEls.toggle(showBuyNow);
bidToBeatEls.toggle(!showBuyNow);
}
buyNowButton.click(function(){ toggleButtons(true) });
bidToBeatButton.click(function(){ toggleButtons(false) });
});
You could save a some lines by removing the selectors at the start and just do the selection in place, if the saved space would be more important than the minor performance hit. Then it would look like this:
$(function () {
var toggleButtons = function(showBuyNow){
$('buy-it-now.ribbon').toggleClass('active', showBuyNow);
$('.bid-to-beat.ribbon').toggleClass('active', !showBuyNow);
$('.buy-now').toggle(showBuyNow);
$('.bid-now,.add-to-cart').toggle(!showBuyNow);
}
$('buy-it-now.ribbon').click(function(){ toggleButtons(true) });
$('.bid-to-beat.ribbon').click(function(){ toggleButtons(false) });
});
The first version selects the elements once and holds them in memory; the second selects them each time the button is clicked. Both solve the problem I believe would occur with the selected answer where clicking the same button twice would cause the .active and .inactive classes to get out of sync with the shown/hidden elements.

Categories