Access javascript object directly through DOM traversing with jQuery - javascript

I'm working on a simple client side interface where I have a jQuery object that I want to access directly when clicking on a hyperlink. Simplified code:
<div class="controls">
<div class="score">
<a class="button" href="/add">Add points!</a>
</div>
</div>
$(".controls").myControls();
$.fn.myControls = function() {
return $.extend(this, $.myControls).initialize();
}
$.myControls = {
initialize: function() {
this.scoreElement = $("div.score", this);
this.linkElement = $("a", this.scoreElement);
this.scoreElement.score = 0;
var _this = this;
this.linkElement.click(function() {
_this.clickHandler(this);
});
},
clickHandler: function(element) {
var scoreElement = $(element).parent();
scoreElement.score = 1;
}
}
Explanation: .controls element has .score element which doubles as a container for score information (this.scoreElement.score). When I click on a link within the .score element, I find the parent element, which is the same element in the DOM as this.scoreElement and try to set its score property to 1. Obviously, this won't work, as the local scoreElement.score property in the clickHandler method is "undefined".
So here's my question: is there a simple way to access my this.scoreElement object directly through traversing the DOM with jQuery?
Surely I can check if this.scoreElement == $(element).parent() in some way and then access the right property in my this.scoreElement object, but direct access would be more elegant and robust. Is this possible? Am I going at it the wrong way? Thanks!
PS: Ignore the fact I use parent() to find the scoreElement, I only use it to illustrate my problem. Unless it is part of the problem, in that case don't ignore :)

While it's certainly possible to use your own 'control-object' to store the related data, I usually prefer to rely on jQuery doing it - with .data() method, like this:
$(this.scoreElement).data('score', 0); // in initialize()
$(this).parent().data('score', 1); // in clickHandler()
This approach allows me to scale more easily, as I never have to fear 'overlapping' issues, using a single 'control' object rather than object-for-element.

I would think that if you used jQuery's proxy function for your click handler, you then could just go this.scoreElement inside of clickHandler and you wouldn't even need to traverse the DOM. Like this:
$.myControls = {
initialize: function() {
this.scoreElement = $("div.score", this);
this.linkElement = $("a", this.scoreElement);
this.scoreElement.score = 0;
this.linkElement.click($.proxy(this.clickHandler, this));
},
clickHandler: function(event) {
var element = event.target;
this.scoreElement.score = 1;
}
}

After progressive simplification (and storing the score slightly differently) I get the code below, in which scoreElement is discovered once per .controls div, then held in a closure to make it available to its corresponding click handler. You could alternatively use .closest() - see commented out line.
$.fn.myControls = function() {
return this.each(function() {
var scoreElement = $("div.score", $(this));
scoreElement.data('score', 0);
$("a", scoreElement).on('click', function() {
scoreElement.data('score', 1);//get scoreElement from closure formed by the outer "each" function.
//$(this).closest(".score").data('score', 1);//alternative to the line above, not requiring closure.
});
});
};
Call as in the question with:
$(".controls").myControls();
This is so trivial and unidimensional it doesn't really warrant, in its own right, a jQuery plugin. Unless there was some compelling reason for a plugin (eg. reuse or the need for closely related methods), then I would phrase it as follows :
$(".controls").each(function() {
var scoreElement = $("div.score", $(this));
scoreElement.data('score', 0);
$("a", scoreElement).on('click', function() {
scoreElement.data('score', 1);//get scoreElement from closure formed by the outer "each" function.
//$(this).closest(".score").data('score', 1);//alternative to line above, not requiring closure.
});
});
That's the same code with the plugin wrapper removed and attached directly to the same base jQuery object.
And if you really wanted, you could write the whole thing in three lines as follows:
$(".controls").find("div.score").data('score', 0).find("a.button").on('click', function() {
$(this).closest(".score").data('score', 1);
});

Related

JQuery event delegation: how to get the index of a JQuery Object in a Array?

I have created an array full of jQuery Object - dom elements:
var tabComponet = new Class({
var $tab = $("<div class='tab'></div>");
var $container = $("<div id='container'></div>");
var tabsArr = [];
[a, b, c].each(function(){
tabsArr.push($tab);
$container.append($tab);
});
$container.on('click', '.tab', function (e) {
e.preventDefault();
var index = $(tabsArr).indexOf($(this));
}
});
I created tabsArr to avoid when there are multiple components created by this Class above by then there will be multiple components contains .tab class thus using $('.tab') would not be working right, but it seems I could not simply put a $ sign on tabsArr to make this working. Also in the delegation, I should avoid using '.tab' as parameter too, right?
I googled about it, it may be because that everytime I use $ to make a JQuery object they would be not the same but just contain the same value, so $(this) is not in tabsArr, because tabsArr doesn't contain it but just the same value.
so how should I do this? how should I create a object that contains the exact tab elements that belong to its own component?
When you create element, you obtain reference to this element. Reference is not value. Your code serves as nice example: when your for loop finishes, you will see only one element inside container no matter what..
Wrapping javascript element into jQuery always create new reference. When you compare two elements(and any non-primitive type) in javascript, you don't compare values(content), but their references. Fortunately you can use get() method to obtain javascript direct reference to element!
var $container = $("<div id='container'></div>");
var tabsArr = [];
for (var i=0;i<2;i++) {
let $tab = $("<div class='tab'></div>");
tabsArr.push($tab.get(0));
$container.append($tab);
};
$container.on('click', '.tab', function (e) {
e.preventDefault();
console.log(tabsArr.indexOf(this));
});
$container.appendTo('body');
.tab {
height: 100px;
width: 200px;
background-color: red;
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

jquery storing dynamic innerhtml into usable jquery variable

var = cooldynamicelement
How could I store the inner html I grab with jQuery from my div ie. <div class="username"> </div> to store as an accessible variable in jQuery eg. cooldynamicelement so I can grab and use at different areas of my site by just calling ie. $cooldynamicelement and updates with the dynamic .username element value.
1. Store HTML into localStorage
var dynamicElementHTML = localstorage.dynamicElementHTML || $(".username").html() || "";
localstorage["dynamicElementHTML"] = dynamicElementHTML;
To make it available to other pages a way would be to use the power of localstorage
https://developer.mozilla.org/en/docs/Web/API/Window/localStorage
If you're actually interested in the whole element (not only it's inner HTML) than instead of .html() use .prop("outerHTML")
2. Binding using jQuery (essential idea)
If you only want a way to reflect some variable HTML as actual html and make it alive you could do like:
var $myElement = $("<div />", {
class : "userData",
append : $someDynamicElements,
appendTo : $someParentElement,
on : {
contentUpdate : function() {
$(this).html( $someDynamicElements );
}
}
});
than whenever your $someDynamicElements changes you can trigger a contentUpdate
$myElement.trigger("contentUpdate")
3. Binding using jQuery (concept)
Here's the same elements binding concept gone wild:
// Here we will store our elements
var EL = {};
// Create desired HTML elements like this:
var LIST = {
username: $("<b/>", {
html : "UNKNOWN",
click : function() {
alert( $(this).text() );
}
}),
email: $("<a/>", {
html : "test#test.test",
href : "mailto:"+ "test#test.test"
}),
// add more here, you got the idea.
// don't forget that you can assign any JS / jQuery propery to your element.
// You can go insane using .on() and later .trigger()
};
// Our small "program" that replaces data-bind elements
// with dynamic elements from our list
$("[data-bind]").replaceWith(function(i){
var bind = this.dataset.bind;
if(!LIST[bind]) return;
if(!EL.hasOwnProperty(bind)) EL[bind] = [];
var klon = LIST[bind].clone(true)[0];
EL[bind].push(klon);
return klon;
});
// That's it. Now goes your code ///////////////
$(EL.username).css({color:"red"}); // just to test if it works :D
$("[data-target]").on("input", function(){
var target = this.dataset.target;
$(EL[target]).html( this.value );
});
// P.S: Even having thousands of elements inside EL
// say you have "EL.tableRows" you can do fabulously
// quick stuff like i.e: sorting, cause you iterate over a plain JS array.
// After the sorting of EL.tableRows is done and you need a jQuery
// representation simply use $(EL.tableRows).
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Dynamic element Binding in jQuery</h2>
Enter some text and see the update trigger in different places<br>
<input data-target="username"><br>
Welcome <span data-bind="username"></span> !!<br>
You name is <span data-bind="username"></span> Click the red text!<br>
<span data-bind="email"></span>
Well if you want to have the jqueryObject in a variable, just do this:
$(function(){
window.$cooldynamicelement = $("div.username");
})
that way you're able to use $cooldynamicelement in a global context. If is that what you want. This way you're saving a reference to your .username element and thus every time you use it will be updated.
NOTE: If you decide to do this, be careful with polluting your global context.:

Looping through generated HTML with jQuery

I know if I wanted to bind events to generated HTML, I'd need to use something like .on(), but I've only used it when binding events like .click().
I'm creating a web app that applys a list of colors. Colors are generated from a JSON file. Once fetched, I add it to the page, with certain information contained in attributes. I'd like to do something with the new generated HTML, which is list-elements. But what console.log() is showing me is there is nothing in the parent ul. Even though on the page I see the newly added content.
Here's the entire code based around it.
var setColors = function(){
getColors = function(){
$.getJSON('js/colors.json', function(colors) {
$.each(colors, function(i, colors) {
//console.log(colors);
$('<li>', {
text: colors['color'],
'name' : colors['color'],
'data-hex' : colors['hex'],
'data-var' : colors['var']
}).appendTo('#picker');
})
});
addColors();
}
addColors = function(){
var el = $('#picker').children;
$(el).each(function(){
console.log($(this));
});
}
return getColors();
}
$(function(){
setColors();
});
addColors() is where I'm having trouble with. The error says 'Uncaught TypeError: Cannot read property 'firstChild' of null. How can I work with the newly generated HTML?
You are missing parentheses on the children method:
var el = $('#picker').children();
Also, if you want the addColor method to be executed on the newly generated html, then you must add a call to it after the html is generated, from within the getJSON callback method.
addColors = function(){
var el = $('#picker').children;
$(el).each(function(){
console.log($(this));
});
}
A few issues:
missing end semi-color
missing parentheses on .children()
children() returns a jQuery object, no need for $(el)
Updated:
window.addColors = function(){
var $el = $('#picker').children();
$el.each(function(){
// do stuff here, but could attach each() to above, after children()
});
};

Using $(this) in setTimeout();

I want to set timeouts dynamically in jQuery. The dynamically set timeout functions need to use $("this"), but I can't seem to get it working.
An exmple:
$("div").each(function(){
var content = $(this).attr('data-content')
setTimeout("$(this).html('"+content+"')",$(this).attr('data-delay'));
});​
http://jsfiddle.net/qmhmQ/
What is the best way to do this?
$("div").each(function(){
var content = $(this).attr('data-content'),
$this = $(this); // here $this keeps the reference of $(this)
setTimeout(function() {
// within this funciton you can't get the $(this) because
// $(this) resides within in the scope of .each() function i.e $(this)
// is an asset of .each() not setTimeout()
// so to get $(this) here, we store it within a variable (here: $this)
// and then using it
$this.html(content);
}, $this.attr('data-delay'));
});​
DEMO
Your code should look like this:
pass a function instead of a string.
Explanation:
When passing a string to setTimeout you get problems, because it runs in a different scope than you original one, and thus you get errors.
use the jQuery data()method
$("div").each(function(){
var content = $(this).attr('data-content'),
$el = $(this),
setContent = function(){
$el.html(content);
}
setTimeout(setContent,$el.data('delay'));
});​
You can assign a function to a variable and pass that variable as parameter to setTimeout, this is the cleanest way.
Use closures (some tutorials).
Using strings with setTimeout is not a good idea. Also beware this, since it can change its context (ie. call-site) if used inside a closure.
If using data attributes you can use the jQuery data function.
$("div").each(function() {
var instance = $(this);
var content = instance.data('content');
var method = function() {
instance.html(content);
};
setTimeout(method, instance.data('delay'));
});
div {
border: 1px solid black;
margin: 5px;
height: 1.5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-content="fus" data-delay="1000"></div>
<div data-content="ro" data-delay="2000"></div>
<div data-content="dah" data-delay="5000"></div>
I am just expanding answer one above,
Use class or id to refer div in JavaScript. This will avoid further tag name conflicts in the page.
So your updated HTML is,
<div data-content="fus" data-delay="1000" class="dv"></div>
<div data-content="ro" data-delay="2000" class="dv"></div>
<div data-content="dah" data-delay="5000" class="dv"></div>​
Now your updated JavaScript code is,
$(".dv").each(function(){
var content = $(this).attr('data-content'),
$this = $(this);
setTimeout(function() {
$this.html(content);
}, $this.attr('data-delay'));
});​
Where main line is
$this = $(this);
Where we are assigning the current element to our variable used in the setTimeout function.
Please refer this link
Take $(this) out of settimeout and save that in local variable say 'self' just after $("div").each(function(){ this line
var self=$(this);
and use that self further.
The following seems like a good compromise of whitespace, readability and revealing intention.
$('div').each(function(){
var element = $(this)
var content = element.attr('data-content')
var delayms = element.attr('data-delay')
var action = function() { element.html(content) }
setTimeout(action, delayms)
})
​
SEE: http://jsfiddle.net/wilmoore/LSs6g/

jQuery Selector + SVG Incompatible?

I've been working with an HTML5 document with inline SVG and javascript animation.
I'd like to have a box pop up when the user clicks anywhere, and I'd like the box to go away when the user clicks somewhere that isn't the box. This means I can't use $(window).click(), which works.
I've tried selecting the SVGs on top by giving them class names and using $(".svgclassname").click(), but this doesn't seem to work. Neither does selecting individual ones with $("#svgname").click().
What is the problem?
(When I replace $(".eyesvg") with $(window), a blue box appears near the cursor when the user clicks anywhere in the window.)
This happens because SVG DOM spec differs a lot from HTML DOM.
SVG DOM is a different dialect, and some properties have same names but mean different things. For example, to get the className of an svg element, you use:
svg.className.baseVal
The properites affected by this are
className is SVGAnimatedString
height,width, x, y, offsetWidth, offsetHeight are SVGAnimatedLength
These Animated properties are structs, with baseVal holding the same value you'd find in HTML DOM and animatedVal holding I am not sure what.
SVG DOM is also missing some properties libraries depend on, such as innerHTML.
This breaks jQuery in many ways, anything that depends on above properties fails.
In general, SVG DOM and HTML DOM do not mix very well. They work together just enough to lure you in, and then things break quietly, and another angel loses its wings.
I wrote a little jQuery extension that wraps SVG elements to make them look more like HTML DOM
(function (jQuery){
function svgWrapper(el) {
this._svgEl = el;
this.__proto__ = el;
Object.defineProperty(this, "className", {
get: function(){ return this._svgEl.className.baseVal; },
set: function(value){ this._svgEl.className.baseVal = value; }
});
Object.defineProperty(this, "width", {
get: function(){ return this._svgEl.width.baseVal.value; },
set: function(value){ this._svgEl.width.baseVal.value = value; }
});
Object.defineProperty(this, "height", {
get: function(){ return this._svgEl.height.baseVal.value; },
set: function(value){ this._svgEl.height.baseVal.value = value; }
});
Object.defineProperty(this, "x", {
get: function(){ return this._svgEl.x.baseVal.value; },
set: function(value){ this._svgEl.x.baseVal.value = value; }
});
Object.defineProperty(this, "y", {
get: function(){ return this._svgEl.y.baseVal.value; },
set: function(value){ this._svgEl.y.baseVal.value = value; }
});
Object.defineProperty(this, "offsetWidth", {
get: function(){ return this._svgEl.width.baseVal.value; },
set: function(value){ this._svgEl.width.baseVal.value = value; }
});
Object.defineProperty(this, "offsetHeight", {
get: function(){ return this._svgEl.height.baseVal.value; },
set: function(value){ this._svgEl.height.baseVal.value = value; }
});
};
jQuery.fn.wrapSvg = function() {
return this.map(function(i, el) {
if (el.namespaceURI == "http://www.w3.org/2000/svg" && !('_svgEl' in el))
return new svgWrapper(el);
else
return el;
});
};
})(window.jQuery);
It creates a wrapper around SVG objects that makes them look like HTML DOM to jQuery. I've used it with jQuery-UI to make my SVG elements droppable.
The lack of DOM interoperability between HTML and SVG is a total disaster. All the sweet utility libraries written for HTML have to be reinvented for SVG.
u can use jquery-svg plugin, like a charm:
<script>
//get svg object, like a jquery object
var svg = $("#cars").getSVG();
//use jquery functions to do some thing
svg.find("g path:first-child()").attr('fill', color);
</script>
sometimes I don't get it... but actually it doesn't work with the class-selector. If you use the id $("#mysvg") or the element $("svg") it does work! Strange....
And it only works when you move the onClick script from the header to the body after the svg element! jquery can only bind the onclick when the element is declared before the binding.
You have to use CSS-path from div to SVG element to click on the object
as per below example:
$('div#layout > svg > #line').click()

Categories