I have JS code where I try to determine if the size of an image was explicitly set via CSS or the by using the width/height attributes. If so, I respect the set sizes, otherwise I execute code to size the image myself. Here is the code. It uses some jQuery, so image is a jQuery object and image[0] accesses the actual DOM element:
if (image.attr('height') == undefined && image[0].style.height.length == 0) {
image.data("explicit_height", false);
//Do stuff to the image size here
} else {
image.data("explicit_height", true);
}
// ...SAME FOR WIDTH...
The problem I am facing is that this code works fine as long as either the height/width or style attributes are used. When the height/width is set via a CSS selector (for example a class selector), the code is NOT working. The code is executing on window load.
Anyone got an idea why this happening and how to fix it?
You have to use getComputedStyle or currentStyle to get styles set in a stylesheet (the latter works in earlier versions of IE.) style will only return inline style properties.
(image.getAttribute('height') == null
&& ((window.getComputedStyle(image)||image.currentStyle)['height']) == '0px') ?
//do stuff
:
//do other stuff
You could also map the evaluation of that statement to an object of functions:
function foo() { image.data("explicit_height", false) }
function bar() { image.data("explicit_height", true) }
var bool = ( image.getAttribute('height') == null
&& ((window.getComputedStyle(image)||image.currentStyle)['height']) == '0px' ),
fn = {
true:foo,
false:bar
}[bool]();
You'll need to play around with image.getAttribute('height') == null and the statement that follows that, because they are based on default values in the browser, which could be different (I only tested on Firefox 26 for Mac 10.9, and the default values where there is no height attribute is null and where there is no height declared in a stylesheet is 0px.)
Updated Answer
Check out this jsfiddle:
var image = document.getElementsByTagName('img')[0];
function foo() { image.setAttribute("name", "foo") }
function bar() { image.setAttribute("name", "bar") }
var styleSheets = document.styleSheets;
function isHeightDefinedInStyleSheets(tag) {
for(i in styleSheets) {
var rules = styleSheets[i]['cssRules'||'rules'];
if(rules!==undefined) {
for(j in rules) {
if(rules[j]['style']!==undefined&&rules[j].selectorText===tag) {
if(parseInt(rules[j]['style']['height'])>0) {
console.log(true);
return true;
}
}
}
}
}
console.log(false);
return false;
}
var fn = {
true:foo,
false:bar
}[!isHeightDefinedInStyleSheets('img') && image.getAttribute('height') == null]();
This function will check the stylesheets themselves for whether a rule exists (which is different than getComputedStyle, which will get the style of the element based on its computed value, regardless of whether the value was defined inline or in a stylesheet, etc.) Its return value is passed along as a key with the return value checking whether height was defined inline as an attribute to the image. If the statement evaluates "true", it will execute the "true" function (foo) in the object fn; otherwise it will execute bar.
I haven't dealt with the styleSheets property before, so the code needs to be cleaned up quite a bit, but it demonstrates the basic point. Also, if you have a collection of img elements, you'll need to implement a looping function to iterate through each element in the collection.
Related
This is not for use in my project, Only for learning purposes.
In jQuery,
When we call $('h1'). it simply returns all the h1 elements from the document. Again when we make some action on an element like $('h1').hide(), it simply hides all the elements(cool ah?)
I want to learn this similar functionality, for example:
function app(elm){
const x = (typeof elm !== 'object') ? document.querySelectorAll(elm) : elm
return {
hide : function(){
x.forEach( target =>{
target.style.display = 'none';
});
}
}
}
This is a simple code here. So, If I call it like app('h1').hide(); it will hide all the h1 elements from the document. But if I call it like app('h1') it returns the object what I return that's normal.
In here I need all h1 elements from the document like jQuery. I mean It should work like this,
$('h1') === app('h1') //JQuery is equal to myCFunction (problem)
$('h1').hide === app('h1').hide() //jQuery is equal to myCFunction (solved)
[NOTE] Here is an article that is similar to my question but it's not my question answer.
Article Link
You can return x instead of a custom object, but before returning inject the hide function into x object's prototype like x.prototype.hide = function(){/*...*/}.
I think $("h1") does not return selected elements. It stores the selected elements. Instead we can have new function(getElement) to get select elements.Hope this code helps.
var App = function() {
var x ;
this.app = function (elem) {
x = document.querySelectorAll(elem);
return this;
}
this.hide = function(){
x.forEach(target => {
target.style.display = 'none';
});
return;
}
this.getElement = function(){
return x;
}
}
var $ = new App();
$.app("h1").hide();
console.log($.app("h1").getElement());
I've got a mostly working solution, but you still have to fix one small but annoying problem (see caveat 3). It's mostly done so I'll put it here anyway.
I think this is what you are looking for:
function app(selector) {
const retArr = document.querySelectorAll(selector); // The array to return
// Add proxies for all prototype methods of all elements
for (let e of retArr) {
let methods = getProtoMethods(e);
for (let mKey in methods) {
// Skip if the proxy method already exists in retArr
if (retArr[mKey] !== undefined) continue;
// Otherwise set proxy method
Object.defineProperty(retArr, mKey, {
value: function(...args) {
// Loop through all elements in selection
retArr.forEach(el => {
// Call method if it exists
if (el[mKey] !== undefined) el[mKey](...args);
});
}
});
}
}
return retArr;
// Gets all prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
return methods;
}
}
The idea is to put all the selected objects in an array, then define additional methods on the array. It should have all the method names of the selected objects, but those methods are actually proxies of those original methods. When one of these proxy methods is called, it calls the original method on all (see caveat 1) the selected objects in the array. But otherwise the returned object can just be used as a normal array (or more accurately, NodeList in this case).
However it's worth mentioning that there are several caveats with this particular implementation.
The list of proxy methods created is the union of the methods of all selected objects, not intersection. Suppose you selected two elements - A and B. A has method doA() and B has method doB(). Then the array returned by app() will have both doA() and doB() proxy methods. However when you call doA() for example, only A.doA() will be called because obviously B does not have a doA() method.
If the selected objects do not have the same definition for the same method name, the proxy method will use their individual definitions. This is usually desired behaviour in polymorphism but still it's something to bear in mind.
This implementation does not traverse the prototype chain, which is actually a major problem. It only looks at the prototypes of the selected elements, but not the prototypes of prototypes. Therefore this implementation does not work well with any inheritance. I did try to get this to work by making getProtoMethods() recursive, and it does work with normal JS objects, but doing that with DOM elements throws weird errors (TypeError: Illegal Invocation) (see here). If you can somehow fix this problem then this would be a fully working solution.
This is the problematic recursive code:
// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
// obj[pKey] throws error when obj is already a prototype object
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
// If obj's prototype has its own prototype then recurse.
if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
return methods;
} else {
return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
}
}
Sorry I cannot solve your problem 100%, but hopefully this at least somewhat helpful.
I'm trying to conditionally set a property on a collection of elements.
render: {
var buttons = [];
for (var i = 1; i <= this.props.totalWeeks; i++) {
buttons.push(
<button
onClick={ this.changeWeek.bind(this, i) }
disabled={ i === this.state.currWeek }>{ i }
</button>);
}
}
Everything works great in the browser. But PHPStorm (version 8.0.3) marks the expression { i === this.state.currWeek } as an error for wrong attribute value.
I've tried changing that with a function call, a variable, etc., but can't seem to make error go away. I've also tried to turn off that inspection rule on PHPStorm, but can't find the one setting that would turn that off.
QUESTION
How can I make that error go away in PHPStorm? If that's a bug, then how can I get rid of that by conditionally adding HTML attributes to a group of elements some other way?
1) If it's render of react (not your custom function), it should be "render() { return ; }" against your code
It's 100% syntax error, browser ignores it, because it should, if you use it in class definition body, syntax is next:
class Test {
objectExample: { some: "value" }
functionExample() { return someExecutionCode(); }
lambdaFunctionExample = () => { return someExecutionCode(); }
}
But you mix 1st and 2nd lines in same time, start as object definition, with body as a function, which are not fits to each other.
2) Your render function NOT return anything, it's making array, but not return it.
It's been a while since I wrote Javascript without jQuery, so please bear with me. I'm assuming I'm just doing something silly. I have this function that converts link urls to an internal representation that I use with a router I wrote.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onClick = function(self, link) {
return function() { self.router.go(link.getAttribute(self.HREF)); };
}(this, this.links[i]);
}
}
}
This function is called the first time when Templater is initialized. The first time it works correctly. However, I run it a second time after I append some html into the body of the document. I run it again just in case that appended html has links in it too:
<body>
<!-- arbitrary new html is loaded in here -->
Login <!-- becomes Login correctly -->
Home <!-- becomes Home correctly -->
</body>
When I console.log(this.links[0], this.links[0].onClick) after the function has been run but still within a Templater function, I get the correct html and then undefined for the onClick event:
Discover undefined
When I log the same to values within the replace_links scope, I get what I'm expecting. I.e. the function is shown:
Discover function () { self.router.go(link.getAttribute(self.HREF)); }
I was playing around with it some more and tried this way and got the same kind of thing.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
(function(self, link) {
link.setAttribute(self.HREF, link.getAttribute("href"));
link.setAttribute("href", self.VOID);
link.onClick = function() { self.router.go(link.getAttribute(self.HREF)); };
})(this, this.links[i]);
}
}
}
I console.log after the replace_link scope ends like before and this time I still get:
Discover undefined
I'd really appreciate any help and/or suggestions! Please let me know if I'm missing anything helpful.
The key points here have been treated as minor details.
I append some html into the body of the document
and
this.links[i].onClick = function(self, link) {
My point is, if you alter innerHTML, which I assume is the way you "append some html into the body of the document," the browser will serialize the DOM objects into HTML, do the string concatenation, and then parse it again. This results in new objects which no longer have the expandos, such as onClick. onClick is a custom property; you probably meant onclick anyway.
However, some of your changes will be serialized and parsed successfully, namely the setAttribute operations. Thus, when you run replace_links after the HTML appending, the
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF)))
check will treat the link as already replaced and not assign the onClick again.
Here's a fiddle that shows this in action. http://jsfiddle.net/k9d7b2ds/
UPDATE: Made some additional changes. The onclick event's default this object is always referencing the window object. You need to pass over the closure.
Check sample code here:
http://jsfiddle.net/y0443fz6/
Templater.prototype.replace_links = function() {
var that = this;
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onclick = function(self, link) {
return function() {
self.router.go(link.getAttribute(self.HREF));
};
}(that, this.links[i]);
}
console.log(this.links[i], this.links[i].onclick);
}
}
hope that helps. gl
The following is my current JavaScript code which is not working. I'm trying to change the image.
function imgchange(a)
{
var e=document.getElementById(a);
if(e.src == "plus.png")
{
e.src = "minus.png";
}
else
{
e.src="plus.png";
}
}
When you are using img.src it returns whole path to img src, not only plus.png
You have to make comparison like http://localhost/images/plus.png (whatever your path is)
or use getAttribute method like is mentioned in undefined's post
src property includes the full url of the image, try using getAttribute method, which returns the specified value in the HTML.
if ( e.getAttribute("src") === "plus.png" )
Note that means that you should also set the new value using .setAttribute() for future comparisons. If you want to use the .src property you should either compare the full paths or use other methods like regular expression or split method:
if ( e.src.split('/').pop() === "plus.png" )
the function itself should be working. there must be something wrong at the place you are calling imgchange("xyz").
so maybe you can show us the code where the function is actually called.
function imgchange (a) {
var e=document.getElementById(a);
if (e.src.replace(/.+\//, '') === 'plus.png') {
e.src = "minus.png";
} else {
e.src="plus.png";
}
}
Should work.
I am moving my code from document.ready() to self executing anonymous function. I have already done a few bigger code pieces but I'm mostly struggling with the smaller ones. Like this one:
/**
Advanced properties toggle
**/
$('a.toggle-link').click(function (e) {
$(this).next().slideToggle('slow');
e.preventDefault();
});
How do I refactor this to be able to introduce variables for the selector a.toggle-link (so anything can be passed into the function), for the .slideToggle (so I can pass in the .slideDown, .slideUp, ...) and for the slow?
This approach uses jQuery, though I've stuck with native DOM methods for the most part:
function actOnElem(el, method, duration) {
// if no passed 'el' or 'method' return
if (!el || !method) {
return false;
}
else {
// if 'el' is an element-node, use 'el' else assume it's an id
el = el.nodeType == 1 ? el : document.getElementById(el);
// duration is used if passed, otherwise 'slow' is used as the default
duration = duration || 'slow';
// create a jQuery object from 'el',
// call the method, if it exists,
// and use the 'duration'
$(el)[method](duration);
}
}
actOnElem(document.getElementById('two'), 'slideDown', 1000);
JS Fiddle demo.
Please note that there are no sanity checks, so if the element is already visible and you call the function with slideDown then nothing will happen. Though while I think this answers your question I'm entirely unsure why you want to take this approach, rather than directly calling upon the jQuery methods.
Slightly-revised function to allow for an (incredibly simple) failure reporting:
function actOnElem(el, method, duration, debug) {
if (!el || !method) {
return false;
}
else {
el = el.nodeType == 1 ? el : document.getElementById(el);
duration = duration || 'slow';
if ($(el)[method]) {
$(el)[method](duration);
}
else if (debug) {
console.log('Did you make a typo? There seems to be no "' + method + '" method.');
}
}
}
actOnElem(document.getElementById('two'), 'slidedown', 1000, true);
// ^
// +--- typo, should be 'slideDown'
JS Fiddle demo.