简体   繁体   中英

Without jQuery, make a “show more” “show less” a href

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>

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM