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()
Related
Suppose I have the following structure:
<html>
<head></head>
<body>
<div id="myDiv"></div>
</body>
</html>
By redefining some native JavaScript functions, can I make myDiv unfindable?
For example, I can do:
window.HTMLDocument.prototype.getElementById = (function() {
var oldefinition = window.HTMLDocument.prototype.getElementById;
return function() {
var returnValue = oldefinition.apply(this, arguments);
if (returnValue && returnValue.id === 'myDiv') {
return oldefinition.call(this, 'blablabla');
} else {
return returnValue;
}
}
})();
and I can do the same for the other functions such as:
querySelector
querySelectorAll
getElementsByTagName
getElementsByClassName
etc.
This works, but the div is still available by calling:
document.body.children[0]
Then is there a way to make my div unfindable, that is, can I redefine the value of the children field?
Well, this is an attempt that seems to work okay - at least, with regards to document.body.children. I tested a variation of the following code on MDN's website to hide all script tags that are immediate children of the document body.
The way this works is we tell document.body to use a new property called children. We then return the original contents of children, minus the ones we don't want.
var oldchildren = document.body.children;
Object.defineProperty(document.body, 'children', {
get() {
var lst = [];
for (var item of oldchildren) {
if (!(item.tagName === "div" && item.id === 'myDiv'))
lst.push(item);
}
return lst;
}
});
Any code that references document.body.children after this code runs won't see the div. This code might cause other code on your site to misbehave.
This is my function set css attributes like as jquery
that.getNode = function() {
return element; // element is HTML node
};
that.css = function(obj) {
var node = that.getNode(),
attr;
if (node && node.style) {
for (attr in obj) {
if (obj.hasOwnProperty(attr) && node.style.hasOwnProperty(attr)) {
node.style[attr] = obj[attr];
}
}
}
return that;
};
Then I call, it runs on Chrome normally but can't work on IE9 or firefox
that.css({
border: "1px solid black"
});
Seems that the following returns false in some browsers:
node.style.hasOwnProperty(attr)
In these cases, the properties are being inherited from a prototype rather than owned by the Object (node.style) itself.
If you want to test that the property exists, whether owned or inherited, you can try the in operator:
attr in node.style
In context:
that.css = function(obj) {
var node = that.getNode(),
attr;
if (node && node.style) {
for (attr in obj) {
if (obj.hasOwnProperty(attr) && attr in node.style) {
node.style[attr] = obj[attr];
}
}
}
return that;
};
Make a Cross-browser solution by hand is lot of work, I recommend using some JavaScript Framework like JQuery, it will do the hard work for you.
If you choose JQuery you have a function named .CSS and you could do this task in 1 line, for example:
You have an HTML element:
<div id="someID"> Content </div>
right? so you want to add some CSS Classes to this DIV, you only need to say to JQuery which node in the HTML DOM want to select and with the function .CSS you can set some CSS Attributes (and other things):
$("#someID").css("SOME-ATTRIbute","SOME-VALUE");
(#someID is the ID of the element, .CSS is the function)
easy eh?, Use JQuery or any other Javascript Frameworks that will help you to do a cross-browser application.
PS: sorry for my english
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()
});
};
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);
});
i have a same question asked here(wasnt able to comment on it,maybe dont have a priviledge) , i want to get css width value defined in stylesheet but not yet applied on any element in dom ,(its bootstrap css with grid with responsive media queries)
.span6 {
width: 570px;
}
However solution provided in above referenced question return 0 i.e like this
$('<div/>').addClass('span6').width();
but works if i do something like this
$('<div/>').addClass('span6').hide().appendTo('body').width();
any easy way without appending that div?
In order to read a CSS property value from a nonexistent element, you need to dynamically insert that element (as hidden) to the DOM, read the property and finally remove it:
var getCSS = function (prop, fromClass) {
var $inspector = $("<div>").css('display', 'none').addClass(fromClass);
$("body").append($inspector); // add to DOM, in order to read the CSS property
try {
return $inspector.css(prop);
} finally {
$inspector.remove(); // and remove from DOM
}
};
jsFiddle here
Great answer by Jose. I modified it to help with more complex css selectors.
var getCSS2 = function (prop, fromClass, $sibling) {
var $inspector = $("<div>").css('display', 'none').addClass(fromClass);
if($sibling != null){
$sibling.after($inspector); //append after sibling in order to have exact
} else {
$("body").append($inspector); // add to DOM, in order to read the CSS property
}
try {
return $inspector.css(prop);
} finally {
$inspector.remove(); // and remove from DOM
}
};
JSFiddle