0

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">
       <a href="nojs.htm"  id="more"> click for more </a>
    </div>
    <script type="text/javascript" src="scripts/myscript.js"></script>
  </body>

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
  • [A useful website you may find interesting.](http://jsbeautifier.org) – Pointy Oct 27 '13 at 14:21
  • Another [useful site](http://www.jslint.org), but as the site says: using jslint _will_ hurt your feelings. PS: avoid excessive DOM queries, and learn about scope in JS, use closures to keep DOM references in scope where you need them – Elias Van Ootegem Oct 27 '13 at 14:29
  • Thanks Pointy - neat site – user2464423 Oct 27 '13 at 15:29
  • Elias I have attempted jslint before - is it really necessary to be quite so pedantic about the code - I found it very frustrating and gave up after a while feeling it was just a bit too critical and only became useful after a serious amount of code spacing etc to comply with the rules they enforce - I believe in tidy code but is jslint not a little bit @n@l about the whole thing? – user2464423 Oct 27 '13 at 15:33
  • @user2464423: Granted, jslint's preference in terms of white-space is the first thing I switch off, too, but using just `/*jslint browser: true, plusplus: true, white: true */` as settings (Assume browser, tollerate ++ and messy whitespace), the code in my answer passes JSLint witout issues. And then, it gives you a nicely formatted function report, too... Anyway: since you liked my answer, would you mind awfully accepting it? – Elias Van Ootegem Oct 27 '13 at 15:53
  • sorry I tried the up arrow to vote your answer up but I need 15 point to do that so being a novice user I couldn't - sorry. The breakdown is great - I am trying to digest the information and there, as you say, "is an awful lot I have to learn "- push and pop() were unknown functions for me as well as a lot of teh code you have submitted - I do intend to work through this so I fully comprehend what is going on at each stage so I get a better understanding of how to do this - I am sure the different points will be valuable to me in my later learning - cheers. – user2464423 Oct 27 '13 at 16:02

1 Answers1

5

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...

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Refreshing to see such a detailed explanation of a problem on Stack Overflow without any condescending overtones too. +1 my friend. – CodingIntrigue Oct 27 '13 at 15:09
  • Thanks - just trying to get my head around it all but far more complex than I anticipated - cheers. – user2464423 Oct 27 '13 at 15:12
  • @user2464423: Cheers, I've been looking around through some of my previous answers on JS questions, [this one came up](http://stackoverflow.com/questions/16472577/javascript-how-to-make-this-code-work/#16472719). It explains, in a bit more detail, how function scopes (and closures) can be used to avoid excessive DOM access. Basically, that's what I've done here with the `more`, `less` and `div` variables. JS works like this when it comes to objects, too [I've posted this answer](http://stackoverflow.com/a/15174505/1230836) which also links to a number of other answers. Some might be useful – Elias Van Ootegem Oct 27 '13 at 15:19