0

Good night stackoverflow.

I'm having a problem with a Javascript snippet, that works in JSFiddle but does not work on actual HTML.

I have a gist of why it doesn't work, when the following line is called:

while (list.firstChild) {

At that point in time the value of list is still null. So it might at this point crash and burn.

However, why does it work in the fiddle?

I know I can solve this by adding one item previous to the list as to avoid making this null, however, I'm more curious about why the discrepancy appears (How does the jsfiddle and html differ) and how one would go through to restoring the jsfiddle functionality.

Fiddle of this working

http://jsfiddle.net/vSHQD/9/

My Test.html doesn't work

<script>
var list = document.getElementById('deliveryIdArray');
var names = [];

window.changeText2 = function() {
    var deliveryIdentification = document.getElementById('deliveryIdentification').value;
    names.push(deliveryIdentification);//simply add new name to array;
    //array changed re-render list
    renderList();
}

window.renderList = function(){
    while (list.firstChild) {
        list.removeChild(list.firstChild);
    }
    //create each li again
    for(var i=0;i<names.length;i++){
        var entry = document.createElement('li');
        entry.appendChild(document.createTextNode(names[i]));
        var removeButton = document.createElement('button');
        removeButton.appendChild(document.createTextNode("X"));
        removeButton.setAttribute('onClick','removeName('+i+')');
        entry.appendChild(removeButton);
        list.appendChild(entry);
    }
}


window.removeName = function(nameindex){
    names.splice(nameindex,1);
    //array changed re-render list
    renderList();
}

window.getDeliveries = function(){
    return names;
}
</script>

<html>
    <body>
        <b>Number </b>
        <input id = "deliveryIdentification" name = "deliveryIdentification" type = "text" size = "16" maxlength = "30">

        <!-- Array Area Creation -->
        <input type='button' onclick='changeText2()' value='Add' />

        <ol id="deliveryIdArray">
        </ol>
    </body>
</html>

Any and all help is greatly appreciated, have a great night!

Erick
  • 2,488
  • 6
  • 29
  • 43
  • 2
    Try moving the script to the end of the body tag for starters. – lotophage May 22 '14 at 06:20
  • 1
    If I had to guess, I would assume that JSFiddle automatically has the script tags added after the HTML (this allows the html to load before the javascript occurs) or it has some sort of `window.onload = function() { ... }` wrapping the javascript which does the same thing. – MitulP91 May 22 '14 at 06:20
  • http://jsbin.com/pakic/1/edit – elclanrs May 22 '14 at 06:21
  • jsFiddle uses a DOM ready handler by default, so if it works in a fiddle but not in your site, you're running the script before the DOM is ready, which can be verified by setting "no wrap - in head" in jsFiddle, and you get the same issue. Move the script right before the closing body tag, and it works just fine. – adeneo May 22 '14 at 06:21
  • Why is `list` global? You're only using it in `renderList`. – Andreas May 22 '14 at 06:23
  • @Andreas I also use it elsewhere, just not shown there. – Erick May 22 '14 at 06:26

4 Answers4

1

You have a bad html format. Also you could code the javascript at last or you need the window.onLoad function to ensure thar document is loaded.

<html>
    <body>
        <b>Number </b>
        <input id = "deliveryIdentification" name = "deliveryIdentification" type = "text" size = "16" maxlength = "30">

        <!-- Array Area Creation -->
        <input type='button' onclick='changeText2()' value='Add' />

        <ol id="deliveryIdArray">
        </ol>
    </body>
    <script>
        var list = document.getElementById('deliveryIdArray');
        var names = [];

        window.changeText2 = function() {
            var deliveryIdentification = document.getElementById('deliveryIdentification').value;
            names.push(deliveryIdentification);//simply add new name to array;
            //array changed re-render list
            renderList();
        }

        window.renderList = function(){
            while (list.firstChild) {
                list.removeChild(list.firstChild);
            }
            //create each li again
            for(var i=0;i<names.length;i++){
                var entry = document.createElement('li');
                entry.appendChild(document.createTextNode(names[i]));
                var removeButton = document.createElement('button');
                removeButton.appendChild(document.createTextNode("X"));
                removeButton.setAttribute('onClick','removeName('+i+')');
                entry.appendChild(removeButton);
                list.appendChild(entry);
            }
        }


        window.removeName = function(nameindex){
            names.splice(nameindex,1);
            //array changed re-render list
            renderList();
        }

        window.getDeliveries = function(){
            return names;
        }
    </script>
</html>

Or you could register handler event when document is loaded:

<html>
    <head>
        <meta charset="utf-8">
        <script>
            document.addEventListener("DOMContentLoaded", init);
            function init(){
                var list = document.getElementById('deliveryIdArray');
                var names = [];

                window.changeText2 = function() {
                    var deliveryIdentification = document.getElementById('deliveryIdentification').value;
                    names.push(deliveryIdentification);//simply add new name to array;
                    //array changed re-render list
                    renderList();
                }

                window.renderList = function(){
                    while (list.firstChild) {
                        list.removeChild(list.firstChild);
                    }
                    //create each li again
                    for(var i=0;i<names.length;i++){
                        var entry = document.createElement('li');
                        entry.appendChild(document.createTextNode(names[i]));
                        var removeButton = document.createElement('button');
                        removeButton.appendChild(document.createTextNode("X"));
                        removeButton.setAttribute('onClick','removeName('+i+')');
                        entry.appendChild(removeButton);
                        list.appendChild(entry);
                    }
                }


                window.removeName = function(nameindex){
                    names.splice(nameindex,1);
                    //array changed re-render list
                    renderList();
                }

                window.getDeliveries = function(){
                    return names;
                }
            };
        </script>
    </head>
    <body>
        <b>Number </b>
        <input id = "deliveryIdentification" name = "deliveryIdentification" type = "text" size = "16" maxlength = "30">

        <!-- Array Area Creation -->
        <input type='button' onclick='changeText2()' value='Add' />

        <ol id="deliveryIdArray">
        </ol>
    </body>
</html>

You could see more about window.onload here.

You could see more about DOMContentLoadhere.

NEOLPAR
  • 382
  • 1
  • 6
  • Wouldn't the second approach (DOMContentLoad) make the two global variables local? Thus disabling them being used in other functions? – Erick May 22 '14 at 07:05
  • You could make `names` global valiable, but `list` have to stay into the `init()` function, because you need to have the document loaded. – NEOLPAR May 22 '14 at 07:15
1

If you look at the left of the JSFiddle page, you'll see that the second dropdown is set to "onLoad".

What this means is that your JavaScript is not executing until the onload event has fired.

However, when you include it as an HTML page, the JavaScript executes immediately. This breaks, because the DOM is not ready and so your getElement functions do not return the values you expect.

You can reproduce the problem in the JsFiddle by changing the value of the dropdown to "no wrap - in head" and re-running.

There are great answers already on how to wire up JavaScript to run after the DOM has loaded.

You can also use the document ready event instead of the onload event (which waits for things like images to load) for your purpose. The advantage is that your JavaScipt will likely execute sooner.

window.onload vs $(document).ready()

Community
  • 1
  • 1
Doug Moscrop
  • 4,479
  • 3
  • 26
  • 46
1

There is a script error:

Unable to get value of the property 'firstChild': object is null or undefined 

Place your list inside the window.renderList function and it will solve the problem.

window.renderList = function(){

var list = document.getElementById('deliveryIdArray');

    while (list.firstChild) {
        list.removeChild(list.firstChild);
    }
    //create each li again
    for(var i=0;i<names.length;i++){
        var entry = document.createElement('li');
        entry.appendChild(document.createTextNode(names[i]));
        var removeButton = document.createElement('button');
        removeButton.appendChild(document.createTextNode("X"));
        removeButton.setAttribute('onClick','removeName('+i+')');
        entry.appendChild(removeButton);
        list.appendChild(entry);
    }
}
jhyap
  • 3,779
  • 6
  • 27
  • 47
1

this is interesting question,

although this is the first time I saw people put script tag out html tag.

web browser engine in my case (webkit) render the html from top to bottom,

At the moment, the script engine execute var list = document.getElementById('deliveryIdArray'); the dom element deliveryIdArray doesn't exists in the dom tree, so, it can not find that element.

after you put the script tag on the bottom of the file, you can see your sample running happily.

and that's why in js idiom, people like to put js on the bottom the html file, or you need to wireup the handler in DOMContentLoad event or use the jquery way $(function(){}) ;

Sean
  • 2,990
  • 1
  • 21
  • 31