Without jQuery, make a "show more" "show less" a href - javascript

I know this is fairly straight forward using jQuery but for college assignment need to insert new paragraphs on clicking more link then remove them on clicking less links - we are not to use css or jQuery so my code so far looks like this - the insert works but the remove less() function doesn't any ideas why (even tried simple alert from teh less function and the return false on the a href doesn't work redirecting page to no javascript default.
window.onload= function()
{
var href = document.getElementById("more");
href.setAttribute("onclick","more(); return false;");
var more = document.getElementById("more");
more.onclick = function more()
{
var para1 = document.createElement("p");
para1.setAttribute("id", "para1");
var para1Cont = document.createTextNode("my text block 1");
para1.appendChild(para1Cont);
var more = document.getElementById("more");
more.parentNode.insertBefore(para1,more);
var para2 = document.createElement("p");
para2.setAttribute("id", "para2");
var para2Cont = document.createTextNode("My text block 2");
para2.appendChild(para2Cont);
more.parentNode.insertBefore(para2,more);
var toLess = more.setAttribute("id", "less");
var less = document.getElementById("less");
less.setAttribute("onclick", "less(); return false;");
less.innerHTML ="click here for less";
return false;
};
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
};
and the html code
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Help meeeee</title>
<link rel="stylesheet" type="text/css" href="styles/style.css">
<link href="scripts/mystyles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<h1>test page</h1>
</div>
<div id="content">
click for more
</div>
<script type="text/javascript" src="scripts/myscript.js"></script>
</body>

Ok, You've got a lot of learning to do. I don't mean that in a bad way, but here's a vanillaJS example of how I'd tackle this:
window.addEventListener('load',function l()
{//use addEventListener, to avoid mem-leaks
"use strict";//for JSLint
var more = document.getElementById('more'),
less = document.getElementById('less'),
div = more.parentNode,//3 DOM reference, to be used by event handlers
added = [],//keep references to added elements, use as stack
rmHandle = function(e)
{//callback definition, don't bind unless less link should be usable
var rm = added.pop();
rm.parentNode.removeChild(rm);
if (added.length === 0)
{
less.removeEventListener('click', rmHandle, false);
}
e.preventDefault();
e.stopPropagation();
};
more.addEventListener('click',function(e)
{//add node:
var newP, count = added.length;
e.preventDefault();
e.stopPropagation();
if (count === 0)
{//bind less event handler here
less.addEventListener('click', rmHandle, false);
}
++count;
newP = document.createElement('p');//create node
newP.setAttribute('id','param'+count);//set id
newP.appendChild(document.createTextNode('New Paragraph #'+count));//add txt content
added.push(newP);//keep reference to node
div.insertBefore(newP, less);//append at end...
},false);
window.removeEventListener('load',l,false);//unbind load handler, this is the leak in IE
}, false);
Now, this by itself is a bit meaningless, so I've gone ahead and set up this fiddle
There are quite a few things left to be done (ie an unload event handler, hide the less link etc...)
Some clarification to help you understand the code:
addEventListener: Instead of setting attibutes, or directly binding event handlers using onload or onclick, I'm adding event listeners. These have the benefit of keeping everything JS-side. You're using setAttribute('onclick'...) somewhere in your code. This sets an attribute in the DOM, that refers back to JS. This is considered bad practice, and quite out-dated.
l-callback. My main callback (window.addEventListener('load', function l()... is called l. In this function, I query the DOM three times (sort of) and I assign the references to these DOM nodes to a variable: more, less and div. Next, I declare an array, to hold all nodes I'll be creating. A sort of stack, so I never need to query the dom to get a reference to those nodes I've created.
I also declare a function (rmHandle), which will handle the clicks on the less link. Because I declare this function within the scope of l, the function has access to all variables I previously declared (less, more, added). Again: I need never query the DOM...
more.addEventListener: This link has to work from the off, so I'm attaching my event listener to this DOM node on load.
no return false: Your question suggests that you know of/have used jQuery. return false in jQ and return false in JavaScript are not the same thing. if you attach an event handler to a form in VanillaJS return false might leave you gobsmacked, because at times: the form will still be submitted. Read about the W3C event model: the capturing and bubbling phases. quirksmode.org is a good resource for details. You'll understand why I'm calling these methods explicitly soon enough.
document.createTextNode: Now I will admit to using innerHTML every now and then, too. But since you're learning, I might aswell point out that innerHTML is not standard, the official standard is to use createTextNode.
At the end I remove the load event listener Because IE tends to leak memory if you don't. Then, the callback goes out of scope and there's nothing you can do about it. So everything can be flagged for GC, and there is no way anything can leak memory, still...
Edit:
I will admit that, listing up a few list-items, of which one just briefly touches on JS's way of resolving variable names in nested scopes isn't quite clear enough. It wasn't for me when I first started learning about closures, and it certainly isn't enough to explain why the code I posted is going to outperform yours by quite a bit.
So if you're up to it, I'm going to explain this a bit more, using a excerpt from your code, and walk you through a clean-up review:
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
This code should look familiar to you (it's copy-pasted from your question after all). Now why would I change this? First off: the DOM API (not part of JS BTW) is slow, clunky, illogical and a main source of frustration and using it too much kills woodland critters. In this snippet, we see this, though:
var less = document.getElementById("less");
less.onclick = function less()
{
//...
var less = document.getElementById("less");
}
So the name less is being used 3 times in an assignment context. Two of those assignments involve a DOM query. Not just a query, but exactly the same query: document.getElementById("less");! It's often said that one of the rules for writing good code is Do Not Repeat Yourself.
Another thing you might have heard is that, even when using loosely typed languages, it's not a bad idea to not assign different types to one variable. You're doing just that, though when you write function less(){}. Apart from a few (at times significant, but that's for some other time) semantic differences, this is basically the same as doing:
var less = function(){};
Each of these assignments is masking the previous one. If wou would've written:
var less = document.getElementById("less");
less.onclick = function less_func()
{
console.log(less);//logs a dom reference!
};
//or even:
less.onclick = function()
{//anonymous or lambda functions are valid... and quite common, too
console.log(less);
};
You wouldn't need that second DOM query witing the onclick function at all. This is because if JS's way of trying to resolve all variables to a previously declared variable. Consider this:
var evilGlobal = 'Do not use Globals';
function()
{
var goodLocal = 'Declared in function',
funcVar = function()
{
console.log(goodLocal);
console.log(evilGlobal);
},
func2 = function goodLocal(evilGlobal)
{
console.log(goodLocal);
console.log(evilGlobal);
console.log(funcVar());
};
funcVar();//logs Declared in function and Do not use Globals
func2();//logs itself (function), and undefined and then same as above
func2(goodLocal);//logs itself, Declared in Function and the same as funcVar
}
How does this come about? within funcVar it's fairly simple:
console.log(goodLocal);//<-- JS looks inside funcVar's function scope for var goodLocal
//not found? JS looks in the outer scope, that of the anonymous function that starts
//with var goodLocal = 'Declared in Function'
//This is the var used
The same applies to console.log(evilGlobal). Only this time, JS scans funcVar's scope, the anonymous function's scope and the global namespace. Why shouldn't you use globals? well, they're clearly slower, they can change state because functions can access them freely, and they clog the memory (the garbage collector only frees what is no longer referenced anywhere. The global namespace is always accessible).
The second case is a tad trickier, but not by much:
function goodLocal()//the goodLocal name is defined as the function!
this name is masks the variable in the outer scope. JS starts scanning the local scope, and finds goodLocal to be pointing to the function. It never checks the outer scope, so it never sees the goodLocal var in the parent function.
The same applies to evilGlobal:
function goodLocal(evilGlobal)
An argument is a variable, declared in the scope of the function. JS will never scan the global ns, because both names can be resolves localy, except for:
console.log(funcVar());
This will result in a scope scan of the parent function, which declares the funcVar variable, and assigns the previously discussed function to it. This function will still behave no different, as the function is called in its own scope/context.
Call contexts are quite tricky, too, so I'm going to gloss over this for a moment.
Back to your code: the other statements are actually repetitions of stuff you've written before, too: var para1 and var para2 are redundant, if you just keep them accessible in the outer scope.
Ah well, just keep reading, and keep learning, you'll get it soon enough...

Related

HTML5Canvas, remembering the last instance made visible

I would like to do a simple task in Adobe Animate under HTML5Canvas environment. There are a couple of buttons on stage and corresponding circle symbol instances beside them that are made invisible at the beginning. When I click a button, an adjacent circle is visible. Then if I click on another button randomly, its adjacent circle is visible, but the previously visible circle must become invisible again as only one circle should be visible at any given time.
As a simple solution, I started with 4 instances: button_1, button_2, circle_1, circle_2. I planned to store the circle instance's name in a variable called 'store' when I first click on any button. Then pass that information to the next button's mouse click event to make the previous circle instance invisible again. My rookie code looks like this...
/*Made circles invisible at the beginning*/
this.circle_1.visible = false;
this.circle_2.visible = false;
/*button's click events*/
var _this = this;
_this.button_1('click', function(){
_this.cicle_1.visible = true;
store.visible = false; /*make the previous circle invisible if any*/
var store = this.circle_1; /*updating current circle's name in variable 'store'*/
});
var _this = this;
_this.button_2.on('click', function(){
_this.circle_2.visible = true;
store.visible = false; /*make the previous circle invisible if any*/
var store = this.circle_2; /*updating current circle's name in variable 'store'*/
});
/* It also works if I can make all circles instances invisible and then show the intended one during every click event, but how can I get and set 20+ circle instances invisible in one step? */
However, the code didn't work. I have no programming experience so my logic could be laughable but this is the easiest solution I can think of. Maybe I should have declared my variable globally? Can anyone kindly improve this code or make it work, please? Please no For-i or Array solution because it makes my head spin :) Thanks in advance.
Welcome to Javscript! I can tell what you're trying to do, but there are a few errors you made that are easy for someone just starting out to make.
You are accessing store variable before actually declaring the variable with var store = this.circle_1. Using the var keyword will declare a variable at the top of it's "scope," no matter which line of that scope it's declared, and let will declare it on the line you specified. Either way, the existence of any variable will not exist outside of it's scope. A scope consists of a set of curly braces {} meaning you are declaring store but then it's immediately getting deleted when you leave the curly braces. Something like the following will fix that:
/*
A variable declared in one scope is available to all scopes inside it.
By declaring 'store' outside of any scope/curly braces, it will be accessible from anywhere in the code
*/
var store = this.circle_1; // you store either circle here. I'm just using circle_1 as a placeholder
//rest of code
this.button_1('click', function(){
store.visible = false;
store = this.circle_1;
this.circle_1.visible = true; //make circle 1 visible
});
I feel you are overthinking it a bit (which is ok, it happens), and if you just have 2 circles, there is a much easier way of doing it which I'll post below.
It also seems like you forgot the on keyword in this.button_1 event declaration.
You are re-declaring _this which shouldn't be necessary the first time, much less twice. The code var _this = this; stores a reference of this in a new variable named _this basically just renaming it, and doesn't do anything else.
I don't know much about Adobe Animate's style of JavaScript, but I'll try to modify your original JavaScript in a way that I assume should work with Animate.
the following code is the simplest way if you only have 2 circles
//Made circles invisible at the beginning
this.circle_1.visible = false;
this.circle_2.visible = false;
//button's click events
this.button_1.on('click', function(){
this.circle_1.visible = true; //make circle 1 visible
this.circle_2.visible = false; //make the other circle invisible
});
this.button_2.on('click', function(){
this.circle_2.visible = true;
this.circle_1.visible = false;
});
Let me know if it works, or if you run it in a browser press F12 > Click Console and let me know if there are any errors.
If you wanted an arbitrary amount of circles an "array solution," as you put it would be the best. Arrays and loops are pretty fundamental, and easy to understand once you start (even though the syntax looks scary). If you plan to proceed in learning programming, that should probably be the next thing you learn.
You likely have there a variable visibility problem. Declaring a var inside a function should not allow other pieces of code to reach that var's value. Instead, you should declare that var outside and store there a null, then call like if (store) store.visible=false;. So, just move the var store outside of both functions, and refer to store inside both of them, should do.

Do Javascript engines do this kind of optimisation?

NB I've done a bit of reading about JS engine optimisation, but most of it is too technical for me to understand enough to apply to this question. I'm also aware that not all engines are the same. I'd be interested in particular in handling by V8 and Rhino, I suppose.
If I create a table, and then rows, and then cells... And then I want to put identical key event listeners on all those cells.
Not only does the creation of these listeners for each cell take a certain amount of time, which could be significant with a biggish table, but in addition I'm supposing that each listener function is stored on its own, even though every listener function is actually identical.
The other key event listener approach which I can use is to put a key event listener on the TABLE, and to work out during the run, on each keydown event, which cell fired this event. I can do this by going
let elementOfInterest = document.activeElement;
"Get the currently focused element in the document" from here.
From my experiments, if you type inside a table cell, this TD does indeed have the focus and is indeed returned by the above call.
This way, I only have to create one listener, which will I assume be quicker and take less memory. The only (very) slight downside is that time then has to be spent getting this "active element" by means of the above call. And, just possibly, the risk that something will grab focus in an unexpected way - obviously if you want to listen to changes of text in a cell, the least error-prone technique must be to use a listener attached to that cell.
But I'm just wondering: maybe Javascript is cleverer than this: maybe if you create 100 separate cell listeners something somewhere identifies them as "all the same" and just makes one function in memory. This is the kind of optimisation you might typically expect from a Java compiler, for example.
Does any such optimisation ever occur? How clever is Javascript with a case like this? Or is it just "script and that's it": what you see is what you get?
The semantics of the language itself don't allow for two function expressions to be "merged" into one even if they were functionally equivalent:
> a = function(){return 'foo'};
ƒ (){return 'foo'}
> b = function(){return 'foo'};
ƒ (){return 'foo'}
> a === b
false
In addition, things get extra hairy when you start considering the closure of the function (e.g. the outer names it uses).
So no, that doesn't happen out of the box.
However, for your use case, there are two optimizations:
As you've found out, you can employ event bubbling and add the event listener on an ancestor element and use event.target (preferably instead of document.activeElement) to figure out where it was originally targeted (and event.currentTarget would be the node the handler is on)
If you can't use a common ancestor (tip: you almost always can; document is a valid target), you could define the function once (assuming it doesn't need to close over any dynamically changing variables) and again use event.target, e.g. event.target.dataset to figure out the data you're handling.
Below, a snippet demonstrating the two.
function createButton(parent, datum) {
const btn = document.createElement("button");
btn.dataset.datum = datum;
btn.innerHTML = datum;
parent.appendChild(btn);
return btn;
}
function eventHandler(event) {
if(event.target.tagName !== "BUTTON") return;
const msg = `real target: ${event.target} (datum="${event.target.dataset.datum}")\ncurrent target: ${event.currentTarget}`;
alert(msg);
}
const p2 = document.getElementById("parent2");
// bubbling listener
const p1 = document.getElementById("parent1");
p1.addEventListener("click", eventHandler, false);
for(var i = 0; i < 10; i++) {
createButton(p1, "p1-" + i);
}
// same function on multiple elements
for(var i = 0; i < 10; i++) {
createButton(p2, "p2-" + i).addEventListener("click", eventHandler, false);
}
<div id="parent1"></div>
<div id="parent2"></div>

Javascript - Array of prototype functions

I'm a javascript newbie so I'm writing ugly code so far sometimes due to my lack of experience and how different it is to the languages I'm used to, so the code I'll post below works, but I'm wondering if I'm doing it the right way or perhaps it works but it's a horrible practice or there is a better way.
Basically, I have a little dude that moves within a grid, he receives from the server an action, he can move in 8 directions (int): 0:up, 1: up-right, 2: right... 7: up-left.
the server will send him this 0 <= action <= 7 value, and he has to take the correct action... now, instead of using a switch-case structure. I created a function goUp(), goLeft(), etc, and loaded them in an array, so I have a method like this:
var getActionFunction = actions[action];
actionFunction();
However, what to set all this up is this:
1) create a constructor function:
function LittleDude(container) {
this.element = container; //I will move a div around, i just save it in field here.
}
LittleDude.prototype.goUp() {
//do go up
this.element.animate(etc...);
}
LittleDude.prototype.actions = [LittleDude.prototype.goUp, LittleDude.prototype.goUpLeft, ...];
//In this array I can't use "this.goUp", because this points to the window object, as expected
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction(); //LOOK AT THIS LINE
}
Now if you pay attention, the last line won't work.. because: when i use the index to access the array, it returns a LittleDude.prototype.goUp for instance... so the "this" keyword is undefined..
goUp has a statement "this.element"... but "this" is not defined, so I have to write it like this:
actionFunction.call(this);
so my doAction will look like this:
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction.call(this); //NOW IT WORKS
}
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way. Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
What you are trying to do is one of the possible ways, but it is possible to make it more simple. Since object property names are not necessary strings, you can use action index directly on prototype. You even don't need doAction function.
LittleDude = function LittleDude(container) {
this.container = container;
}
LittleDude.prototype[0] = LittleDude.prototype.goUp = function goUp() {
console.log('goUp', this.container);
}
LittleDude.prototype[1] = LittleDude.prototype.goUpRight = function goUpRight() {
console.log('goUpRight', this.container);
}
var littleDude = new LittleDude(123),
action = 1;
littleDude[action](); // --> goUpRight 123
littleDude.goUp(); // --> goUp 123
actionFunction.call(this); //NOW IT WORKS
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way.
No, using .call() is perfectly fine for binding the this keyword - that's what it's made for.
Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
You don't have to define them on the prototype if you don't use them directly :-) Yet, if you do you might not store the functions themselves in the array, but the method names and then call them with bracket notation:
// or make that a local variable somewhere?
LittleDude.prototype.actions = ["goUp", "goUpLeft", …];
LittleDude.prototype.doAction = function(action) {
var methodName = this.actions[action];
this[methodName](); // calls the function in expected context as well
}

Titanium Javascript: "that." does not work

Neither this nor that works. Does anyone know what is going on??
Edit:
qwerty is simply called as "qwerty();" when in other pieces of code.
It is supposed to be indepedent.
Edit: I realize what is wrong. The problem lies with the i...
function qwerty () {
..... for loop that changes i ......
var that = this;
this.chara[i] = createlabel.....
this.chara[i].addEventListener('click', function(e) {
var j = e.source.id;
alert("hello word");
alert(this.chara[j].width); // I get the error here
});
this.chara[i].addEventListener('doubleclick', function(e) {
alert("hello word");
alert(that.chara[i].width); // I get the error here too.
});
}
Any JS problem relating to this is likely due to the way the function using this is called. Storing a reference to this in your that variable should let you reference it from within your nested functions, exactly the way you are doing it already - assuming that qwerty() is called in a way that sets this to the correct object in the first place. (Personally I like to call such a variable self since it more accurately reflects what the variable is doing.)
However, in your function you say you get the error on this line:
that.chara[i].width
Given that you say this.chara[i].addEventListener(...) I'm guessing that the chara[i] variable holds a reference to a DOM element. If that is the case I'm guessing it is an element type that doesn't have a width property. Try this:
that.chara[i].style.width
https://developer.mozilla.org/en/CSS/width
That's the best I can do for you without more information about what error you're getting and how the qwerty() function is called...

Optimizing Javascript Loop for Wheel Game

I have a game I'm creating where lights run around the outside of a circle, and you must try and stop the light on the same spot three times in a row. Currently, I'm using the following code to loop through the lights and turn them "on" and "off":
var num_lights = 20;
var loop_speed = 55;
var light_index = 0;
var prevent_stop = false; //If true, prevents user from stopping light
var loop = setTimeout(startLoop, loop_speed);
function startLoop() {
prevent_stop = false;
$(".light:eq(" + light_index + ")").css("background-color", "#fff");
light_index++;
if(light_index >= num_lights) {
light_index = 0;
}
$(".light:eq(" + light_index + ")").css("background-color", "red");
loop = setTimeout(startLoop, loop_speed);
}
function stopLoop() {
clearTimeout(loop);
}
For the most part, the code seems to run pretty well, but if I have a video running simultaneously in another tab, the turning on and off of the lights seems to chug a bit. Any input on how I could possibly speed this up would be great.
For an example of the code from above, check out this page: http://ericditmer.com/wheel
When optimizing the thing to look at first is not doing twice anything you only need to do once. Looking up an element from the DOM can be expensive and you definitely know which elements you want, so why not pre-fetch all of them and void doing that multiple times?
What I mean is that you should
var lights = $('.light');
So that you can later just say
lights.eq(light_index).css("background-color", "red");
Just be sure to do the first thing in a place which keeps lights in scope for the second.
EDIT: Updated per comment.
I would make a global array of your selector references, so they selector doesn't have to be executed every time the function is called. I would also consider swapping class names, rather than attributes.
Here's some information of jQuery performance:
http://www.componenthouse.com/article-19
EDIT: that article id quite old though and jQuery has evolved a lot since. This is more recent: http://blog.dynatrace.com/2009/11/09/101-on-jquery-selector-performance/
You could try storing the light elements in an array instead of using a selector each time. Class selectors can be a little slow.
var elements = $('.light');
function startLoop() {
prevent_stop = false;
$(elements[light_index]).css('background-color', '#fff');
...
}
This assumes that the elements are already in their intended order in the DOM.
One thing I will note is that you have used a setTimeout() and really just engineered it to behave like setInterval().
Try using setInterval() instead. I'm no js engine guru but I would like to think the constant reuse of setTimeout has to have some effect on performance that would not be present using setInterval() (which you only need to set once).
Edit:
Curtousy of Diodeus, a related post to back my statement:
Related Stack Question - setTimeout() vs setInterval()
OK, this includes some "best practice" improvements, if it really optimizes the execution speed should be tested. At least you can proclaim you're now coding ninja style lol
// create a helper function that lend the array reverse function to reverse the
// order of a jquery sets. It's an object by default, not an array, so using it
// directly would fail
$.fn.reverse = Array.prototype.reverse;
var loop,
loop_speed = 55,
prevent_stop = false,
// prefetch a jquery set of all lights and reverses it to keep the right
// order when iterating backwards (small performance optimization)
lights = $('.light').reverse();
// this named function executes as soon as it's initialized
// I wrapped everything into a second function, so the variable prevent_stop is
// only set once at the beginning of the loop
(function startLoop() {
// keep variables always in the scope they are needed
// changed the iteration to count down, because checking for 0 is faster.
var num_lights = light_index = lights.length - 1;
prevent_stop = false;
// This is an auto-executing, self-referencing function
// which avoids the 55ms delay when starting the loop
loop = setInterval((function() {
// work with css-class changing rather than css manipulation
lights.eq( light_index ).removeClass('active');
// if not 0 iterate else set to num_lights
light_index = (light_index)? --light_index:num_lights;
lights.eq( light_index ).addClass('active');
// returns a referenze to this function so it can be executed by setInterval()
return arguments.callee;
})(), loop_speed);
})();
function stopLoop() {
clearInterval(loop);
}
Cheers neutronenstern

Categories

Resources