locked
Using variable outside the function

    Question

  • Hello! I am already searching for this solution for weeks. I am writing a Windows Store App with Javascript and opening a folder with jsonfile, then I am reading some values from json. Next I want to make an array from these json values outside the function where they were read and to bind it to a ListView. Here is the code:

    (function () {
        "use strict";
    
            var feedItems;
            var someFolder = Windows.ApplicationModel.Package.current.installedLocation;
            var newString = "";
            someFolder.createFolderAsync("someFolder", Windows.Storage.CreationCollisionOption.openIfExists).then
                (function (someFile) {
                someFile.createFileAsync("myJson.json", Windows.Storage.CreationCollisionOption.openIfExists).then
                        (function (myJson) {
                      myJson.openAsync(Windows.Storage.FileAccessMode.read).then
                               (function (stream) {
                                   var size = stream.size;
                                   var inputStream = stream.getInputStreamAt(0);
                                   var reader = new Windows.Storage.Streams.DataReader(inputStream);
                                   reader.loadAsync(size).then
                                       (function () {
                                           var array = new Array(size);
                                           reader.readBytes(array);
                                           for (var i = 0; i < array.length; i++) {
                                               if (array[i] >= 32 && array[i] <= 126) {
                                                   var c = String.fromCharCode(array[i]);
                                                   newString += c;
                                               }
                                           }
    
                                           var feedItems = newString;
                                           
                                           /*var obj = JSON.parse(newString);
                                           
                                           var title = obj.title.de;
                                           var description = obj.description.de;*/
    
                                           //document.getElementById('Div1').innerHTML = title;
                                           //document.getElementById('Div2').innerHTML = description;
                                          
                                       })
                               })
                        });
                });
    
        WinJS.Namespace.define(
           "DataUtils", {
               feedItems: feedItems
           }
         );
        console.log(feddItems);
        var dataArray = new Array;
    
        var dataList = new WinJS.Binding.List(dataArray);
    
        var publicMembers =
             {
                 itemList: dataList
             };
        WinJS.Namespace.define("DataExample", publicMembers);
      
        /*var dataArray = [
        { title: "Basic banana", text: "Low-fat frozen yogurt", picture: "images/60banana.png" },
        { title: "Banana blast", text: "Ice cream", picture: "images/60banana.png" },
        { title: "Brilliant banana", text: "Frozen custard", picture: "images/60banana.png" },
        { title: "Orange surprise", text: "Sherbet", picture: "images/60orange.png" },
        ];*/
    
         //var dataList = new WinJS.Binding.List(dataArray);
    
                    
    })();
    
    My variable feedItems is undefined. Please help!

    Thursday, July 18, 2013 2:44 PM

Answers

  • You are declaring the feedItems variable twice, once outside the whole async chain, and again inside the innermost completed handler. So your assignment var feedItems = string; is working on a local variable inside that completed handler (a function), which then immediately goes out of scope, leaving the original feedItems undefined.

    Just remove the "var" from the inner feedItems and it should assign to the first one you declared.

    Be aware, however, that because you're assigning it inside an async chain, feedItems won't be initialized until all that work has completed. You'll need to check for that.

    Finally, your console.log call refers to "fedditems" which is undefined anyway.

    • Marked as answer by jag0 Friday, July 19, 2013 11:38 AM
    Thursday, July 18, 2013 3:58 PM
  • You need to make whatever processing is depending on feedItems dependent upon the async operations completing. Looking at your code further, it looks like you want to have feedItems available through the DataUtils.feedItems member. What's happening is that the WinJS.Namespace.define statement is executed well before the async operation is complete, and so DataUtils.feedItems is being initialized from the var feedItems, which will have an undefined value at that point.

    Inside your async operation then, try assigning DataUtils.feedItems = newString. That should get the right value in your namespace variable. However, you won't be able to make use of that until the assignment happens. That means that whatever else in your code depends on it will need to be triggered from inside the async operation once the assignment has taken place.

    In other words, be very aware that whenever you hit an async API-and you have four in the chain--that operation might start right away but also returns its promise right away. In your initial path of execution, someFolder.createFolderAsync will return a promise right away, after which your WinJS.Namespace.define statement executes and everything after it. Later, when createFolderAsync returns, your first completed handler will execute, starting another async operation and thus yielding the UI thread again.

    I also want to suggest that you learn to chain async operations together rather than nesting them, because it makes the code structure a bit cleaner. For details on that see Chapters 2 and 3 of my free ebook linked below (primarily the second edition where I do a better job of explaining promises and chaining).

    Kraig

    Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press
    Also see Second edition preview for Windows 8.1

    • Marked as answer by jag0 Tuesday, July 23, 2013 9:45 AM
    Friday, July 19, 2013 3:16 PM
  • If you're getting an exception for itemList, then DataExample.itemList must be null when the basicListView is processed. Looking at your script.js (the modified one), you can see that DataExample.itemList isn't initialized until all the async work is complete, which obviously comes later than the creation of the ListView. It works when you're debugging because you're pausing long enough before stepping through to allow the async work to complete.

    The solution in this case is to make sure that you create an empty WinJS.Binding.List synchronously in Page 2's init method, because that's called before WinJS.UI.processAll for the page. This way the ListView can be created against an empty data source, and it will automatically update itself as you populate the List in your async code.

    Make sense?

    Also, your template here doesn't have any data-win-bind attributes on the h4 and h6 elements, so they won't have any real data in them. Maybe you omitted them, but if this is the template you're using, you'll need:

    <h4 data-win-bind="innerText: title"></h4>
    <h6 data-win-bind="innerText: description"></h6>
    

    Where title and description have to match properties of the items in your data source. Your dataArray needs to be an array of objects with these properties, e.g. [ {title: <title>, description: <description>}, { ... }, { ... }]. From the looks of it, you're just creating a flat array, and you'll need to change that bit of code to create an array of objects.

    • Marked as answer by jag0 Friday, August 02, 2013 9:30 AM
    Tuesday, July 30, 2013 4:47 PM
  • Create the Binding.List in the page's init method, not ready, because you need it in place before the ListView is instantiated, which happens after init but before ready. You can use an empty inline array as you wrote, no problem. More than anything, you just need to make sure that your DataExample.itemList object is initialized before the ListView processes the options that reference it, so the init method is the right place to do that.

    By having it in a namespace, by the way, DataExample.itemList is globally accessible anywhere in your app.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Wednesday, July 31, 2013 9:11 PM
  • Not quite. Create the List and assign it the namespace variable within init:

    WinJS.UI.Pages.define("/pages/page2/page2.html", {
        init: function (element, options) {
            dataList = new WinJS.Binding.List(); //An empty array isn't actually needed; it'll create one by default
    
            publicMembers = {
                itemList: dataList
            };
    
            WinJS.Namespace.define("DataExample", publicMembers);
        },
    
    

    Now you can use DataExample.itemList anywhere, including the data-win-options string of your ListView.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Thursday, August 01, 2013 2:48 PM
  • Sorry, inside the init function just put var in from of dataList and publicMember, because you don't need them outside that function (you get to the List through DataExample).

    When you parse the JSON, don't use dataArray.push because dataArray is no longer tied to the binding List nor the ListView. Instead, using the List's push method directly, that is DataExample.itemList.push in your case. That is, replace all of this:

    dataArray.push({title: ttl, description:  dscrptn});
    console.log(dataArray);
    
    dataList = new WinJS.Binding.List(dataArray);
    publicMembers = {
         itemList: dataList
    };
    
    WinJS.Namespace.define("DataExample", publicMembers);
    })

    with just this:

    DataExample.itemList.push({title: ttl, description:  dscrptn});
    

    You always need to just have one Binding.List around to which the ListView is bound, and always be manipulating data inside of it.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Friday, August 02, 2013 3:38 AM

All replies

  • You are declaring the feedItems variable twice, once outside the whole async chain, and again inside the innermost completed handler. So your assignment var feedItems = string; is working on a local variable inside that completed handler (a function), which then immediately goes out of scope, leaving the original feedItems undefined.

    Just remove the "var" from the inner feedItems and it should assign to the first one you declared.

    Be aware, however, that because you're assigning it inside an async chain, feedItems won't be initialized until all that work has completed. You'll need to check for that.

    Finally, your console.log call refers to "fedditems" which is undefined anyway.

    • Marked as answer by jag0 Friday, July 19, 2013 11:38 AM
    Thursday, July 18, 2013 3:58 PM
  • Hi and thank you for the fast reply! I removed the "var" from the inner feedItems and my typing error in feddItems but the variable is still undefined. I am a newbie and still learning, so how can I check when the variable will be initialized?

    Thnx

    Friday, July 19, 2013 11:38 AM
  • You need to make whatever processing is depending on feedItems dependent upon the async operations completing. Looking at your code further, it looks like you want to have feedItems available through the DataUtils.feedItems member. What's happening is that the WinJS.Namespace.define statement is executed well before the async operation is complete, and so DataUtils.feedItems is being initialized from the var feedItems, which will have an undefined value at that point.

    Inside your async operation then, try assigning DataUtils.feedItems = newString. That should get the right value in your namespace variable. However, you won't be able to make use of that until the assignment happens. That means that whatever else in your code depends on it will need to be triggered from inside the async operation once the assignment has taken place.

    In other words, be very aware that whenever you hit an async API-and you have four in the chain--that operation might start right away but also returns its promise right away. In your initial path of execution, someFolder.createFolderAsync will return a promise right away, after which your WinJS.Namespace.define statement executes and everything after it. Later, when createFolderAsync returns, your first completed handler will execute, starting another async operation and thus yielding the UI thread again.

    I also want to suggest that you learn to chain async operations together rather than nesting them, because it makes the code structure a bit cleaner. For details on that see Chapters 2 and 3 of my free ebook linked below (primarily the second edition where I do a better job of explaining promises and chaining).

    Kraig

    Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press
    Also see Second edition preview for Windows 8.1

    • Marked as answer by jag0 Tuesday, July 23, 2013 9:45 AM
    Friday, July 19, 2013 3:16 PM
  • Hello Kraig,
    Thank you very much for a throrough answer. It helped me a lot. I rewrote my code and now my async operations are chained instead of being nested.

    (function () {
        "use strict";
    
            var someFolder = Windows.ApplicationModel.Package.current.installedLocation;
            var newString = "";
            var size = null;
            var reader = null;
            var obj = null;
            var ttl = null;
            var dscrptn = null;
            var dataArray = null;
            var dataList = null;
            var publicMembers = null;
           
            someFolder.createFolderAsync("someFolder", Windows.Storage.CreationCollisionOption.openIfExists)
                .then(function (someFile) {
                    return someFile.createFileAsync("someFile.json", Windows.Storage.CreationCollisionOption.openIfExists);
                })
                .then(function (myFile) {
                    return fibuliFile.openAsync(Windows.Storage.FileAccessMode.read);
                })
                .then(function (stream) {
                    size = stream.size;
                    var inputStream = stream.getInputStreamAt(0);
                    reader = new Windows.Storage.Streams.DataReader(inputStream);
                    reader.loadAsync(size);
                    return reader;
                })
                .done(function (result) {
                    var array = new Array(size);
                    reader.readBytes(array);
                    for (var i = 0; i < array.length; i++) {
                        if (array[i] >= 32 && array[i] <= 126) {
                            var c = String.fromCharCode(array[i]);
                            newString += c;
                        }
                    }
                    obj = JSON.parse(newString);
                    ttl = obj.title.de;
                    dscrptn = obj.description.de;
                    dataArray = new Array;
                    dataArray.push(ttl, dscrptn);
                    dataList = new WinJS.Binding.List(dataArray);
                    publicMembers = {
                        itemList: dataList
                    };
                    WinJS.Namespace.define("DataExample", publicMembers);
                })
    })();


     Unfortunately I still don't get the values of dataArray shown in a ListView. How can I resolve this?
    Tuesday, July 23, 2013 9:45 AM
  • First, have you confirmed that dataArray is actually getting populated? (Either in the debugger or with console output?) Have you also confirmed that each step of the chain is acting as you expect? This is where you want to set breakpoints at the top of each completed handler in the chain, and step through those parts.

    Looking at your code, I see two potential problems. First, in this part:

                .then(function (someFile) {
                   
    return someFile.createFileAsync("someFile.json", Windows.Storage.CreationCollisionOption.openIfExists);
               
    })
               
    .then(function (myFile) {
                   
    return fibuliFile.openAsync(Windows.Storage.FileAccessMode.read);
               
    })

    The last line should be return myFile.openAsync--fibuliFile doesn't seem to exist.

    Next, there's one error in your chain here:

                    reader = new Windows.Storage.Streams.DataReader(inputStream);
                    reader
    .loadAsync(size);
                   
    return reader;
               
    })
               
    .done(function (result) {

    You should be returning the promise from reader.loadAsync, not reader. What might be happening here is that loadAsync is not completing before the completed handler to done is being called. By returning the synchronous value reader (which passes through as "result" in the handler to done), you're not quite chaining the async operations.

    Beyond that, also show me how you're setting up the ListVIew, but my suspicion is that the dataArray isn't getting built.

    Tuesday, July 23, 2013 3:09 PM
  • Hello and thanks again.

    The dataArray is being populated. I get the console output when I step through the code while debugging.

    The problem with myFile.openAsync was my typing error, sorry. It functions though. The folder and then the file are being opened and read.

    Now I have changed the return value from reader to reader.loadAsync(size);

    And my ListView code looks like this:

    <div id="basicListView" 
                data-win-control="WinJS.UI.ListView"
                data-win-options="{ itemDataSource : DataExample.itemList.dataSource }"> 
            </div>
    Best regards

    Wednesday, July 24, 2013 9:05 AM
  • Is your code working in the app then, or are there still issues?
    Wednesday, July 24, 2013 1:55 PM
  • No, the code is still not working. The basicListView is not being filled with data from json.

    What is also strange is that when I don't set BP, then the debugger will stop in the base.js and not execute the app. But when I set the BP and step through the code, then it goes further and I get console outputs where the dataArray is being filled. But still nothing in the basicListView div.

    Wednesday, July 24, 2013 6:59 PM
  • What are you using for the item template in the ListView? The markup you gave here doesn't specify a template, so perhaps you're doing it in code? Without that, nothing will render.
    Friday, July 26, 2013 4:23 PM
  • Hello!

    I did it like in this exmaple: http://msdn.microsoft.com/en-us/library/windows/apps/hh465496.aspx. There it also functions without defining an item template. That's why I don't have any yet in my code.

    I used WinJS.Namespace.define to create a namespace named DataExample that exposes a public member named itemList that returns my List. To connect my data, I set the ListView control's itemDataSource property to DataExample.itemList.dataSource in the Markup.

    I don't understand why I don't get the output like in the example above... :(((

    Sunday, July 28, 2013 4:22 PM
  • I'm not clear whether you have a template in place yet. One way or the other, you must have something assigned to the ListView's itemTemplate property, or nothing is going to appear because there's nothing creating elements in the DOM for each item. So you need either a declarative template or a rendering function. Which one are you using?
    Monday, July 29, 2013 2:18 PM
  • The error I'm getting is (translated):

    0x800a138f - Runtime error in JavaScript: The property "itemList" of an undefined or a zero reference could not be retrieved.

    I don't understand why it functions, when I set the BP and go through the code step by step, but when I don't set the BP I get the error...

    Monday, July 29, 2013 2:42 PM
  • Likely a timing issue of some kind. Probably easiest at this point if I can see the full project...
    Monday, July 29, 2013 4:30 PM
  • Ok, it's like this. My Project-Explorer has these directories among the standard ones:

    • css
    • folderWithJson
    • images
    • js (default.js, navigator.js)
    • pages (page1, page2, page3)
    • default.html

    page1, page2 and page3 are PageControls, meaning they consist of three files: html, css, js. From page1 I can go to page2 and page3. Page2 is where I want to make a ListView of json-Data. It consists of page2.html, page2.css and page2.js plus script.js, which I posted above and which doesn't want to function. I didn't change anything in the standard page2.js file. My page2.html has now an item template:

    <div id="mediumListIconTextTemplate" data-win-control="WinJS.Binding.Template">
                <div style="width: 150px; height: 100px;">
                    <!-- Displays the "title" field. -->
                    <h4></h4>
    
                    <!-- Displays the "decription" field. --> 
                    <h6></h6>
                </div>
            </div>
                
                 
            <div id="basicListView"
                data-win-control="WinJS.UI.ListView"
                data-win-options="{ itemDataSource : DataExample.itemList.dataSource, 
                itemTemplate: select('#mediumListIconTextTemplate')}">
            </div>

    My dataArray from the script.js looks like this: [some Title, some description]... 

    Tuesday, July 30, 2013 11:06 AM
  • If you're getting an exception for itemList, then DataExample.itemList must be null when the basicListView is processed. Looking at your script.js (the modified one), you can see that DataExample.itemList isn't initialized until all the async work is complete, which obviously comes later than the creation of the ListView. It works when you're debugging because you're pausing long enough before stepping through to allow the async work to complete.

    The solution in this case is to make sure that you create an empty WinJS.Binding.List synchronously in Page 2's init method, because that's called before WinJS.UI.processAll for the page. This way the ListView can be created against an empty data source, and it will automatically update itself as you populate the List in your async code.

    Make sense?

    Also, your template here doesn't have any data-win-bind attributes on the h4 and h6 elements, so they won't have any real data in them. Maybe you omitted them, but if this is the template you're using, you'll need:

    <h4 data-win-bind="innerText: title"></h4>
    <h6 data-win-bind="innerText: description"></h6>
    

    Where title and description have to match properties of the items in your data source. Your dataArray needs to be an array of objects with these properties, e.g. [ {title: <title>, description: <description>}, { ... }, { ... }]. From the looks of it, you're just creating a flat array, and you'll need to change that bit of code to create an array of objects.

    • Marked as answer by jag0 Friday, August 02, 2013 9:30 AM
    Tuesday, July 30, 2013 4:47 PM
  • This makes sense! Am I understanding this correctly: I have to create an empty WinJS.Binding.List in Page2.js ready: function()? Can I do this by defining: var itemList = new WinJS.Binding.List([]); But then I will have the similar problem as before when I try to use it in script.js.

    Wednesday, July 31, 2013 8:21 PM
  • Create the Binding.List in the page's init method, not ready, because you need it in place before the ListView is instantiated, which happens after init but before ready. You can use an empty inline array as you wrote, no problem. More than anything, you just need to make sure that your DataExample.itemList object is initialized before the ListView processes the options that reference it, so the init method is the right place to do that.

    By having it in a namespace, by the way, DataExample.itemList is globally accessible anywhere in your app.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Wednesday, July 31, 2013 9:11 PM
  • Sorry, it all makes sense to me, but I am a newbie and need more details..

    Ok, so I created the Binding.List in the page2.js init method like this:

    (function () {
        "use strict";
        
        WinJS.UI.Pages.define("/pages/page2/page2.html", {
                init: function (element, options) {
                    var itemList = new WinJS.Binding.List([]);
            },
    ...

    But in the script.js it won't be recognized, because it's not a global variable. How can I update the itemList then?

    Thursday, August 01, 2013 9:56 AM
  • Not quite. Create the List and assign it the namespace variable within init:

    WinJS.UI.Pages.define("/pages/page2/page2.html", {
        init: function (element, options) {
            dataList = new WinJS.Binding.List(); //An empty array isn't actually needed; it'll create one by default
    
            publicMembers = {
                itemList: dataList
            };
    
            WinJS.Namespace.define("DataExample", publicMembers);
        },
    
    

    Now you can use DataExample.itemList anywhere, including the data-win-options string of your ListView.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Thursday, August 01, 2013 2:48 PM
  • Ok, thank you! I think I am nearing my goal. :) I had to define two variables in the page2.js, because without them I got a runtime error:

    var publicMembers = null;
    var dataList = null;
    (function () {
        "use strict";
        
        WinJS.UI.Pages.define("/pages/page2/page2.html", {
                init: function (element, options) {
                    dataList = new WinJS.Binding.List(); 
    
                    publicMembers = {
                        itemList: dataList
                    };
    
                    WinJS.Namespace.define("DataExample", publicMembers);
            },
    ...


    Then I removed these two variables from script.js:

    (function () {
        "use strict";
    
            var folderWith Json= Windows.ApplicationModel.Package.current.installedLocation;
            var newString = "";
            var size = null;
            var reader = null;
            var obj = null;
            var ttl = null;
            var dscrptn = null;
            var dataArray = null;
            //var dataList = null;
            //var publicMembers = null;
    ...

    After parsing the Json and creating a dataArray, it looks like this:

    obj = JSON.parse(newString);
    ttl = obj.title.de;
    dscrptn = obj.description.de;
    console.log(ttl);
    console.log(dscrptn);
    dataArray = new Array;
    dataArray.push({title: ttl, description:  dscrptn});
    //dataArray = [{title: "Title"}, {author: "Author"}, {description: "Description"}];
    console.log(dataArray);
    
    dataList = new WinJS.Binding.List(dataArray);
    publicMembers = {
         itemList: dataList
    };
    
    WinJS.Namespace.define("DataExample", publicMembers);
    })

    The console puts this [object Object] for dataArray out.

    The ListView Template looks like this:

    <div id="mediumListIconTextTemplate" data-win-control="WinJS.Binding.Template">
                <div style="width: 150px; height: 100px;">
                    <!-- Displays the "title" field. -->
                    <h4 data-win-bind="innerText: title"></h4>
    
                    <!-- Displays the "decription" field. --> 
                    <h6 data-win-bind="innerText: description"></h6>
                </div>
            </div>

    Still no output in the app. Can it be because my dataArray only consists of one Object: {title: Title, description: Description}?

    Thursday, August 01, 2013 4:06 PM
  • Sorry, inside the init function just put var in from of dataList and publicMember, because you don't need them outside that function (you get to the List through DataExample).

    When you parse the JSON, don't use dataArray.push because dataArray is no longer tied to the binding List nor the ListView. Instead, using the List's push method directly, that is DataExample.itemList.push in your case. That is, replace all of this:

    dataArray.push({title: ttl, description:  dscrptn});
    console.log(dataArray);
    
    dataList = new WinJS.Binding.List(dataArray);
    publicMembers = {
         itemList: dataList
    };
    
    WinJS.Namespace.define("DataExample", publicMembers);
    })

    with just this:

    DataExample.itemList.push({title: ttl, description:  dscrptn});
    

    You always need to just have one Binding.List around to which the ListView is bound, and always be manipulating data inside of it.

    • Marked as answer by jag0 Friday, August 02, 2013 9:29 AM
    Friday, August 02, 2013 3:38 AM
  • Now it functions! Thank you so very much for your help and patience! I have learned a lot!

    Best regards!!!

    Friday, August 02, 2013 9:31 AM
  • Perfect. Glad I could help.
    Friday, August 02, 2013 4:06 PM
  • I just have one more question: how can I make the list stay there when I go back and then return to the page again?
    Saturday, August 03, 2013 9:18 PM
  • Could you ask this as a new question on a new thread in the forum? We're straying quite far from the original thread and it's helpful for others (especially in search) to keep each thread focused on a particular topic. Thanks.

    .Kraig

    Monday, August 05, 2013 3:17 PM
  • Sorry, You are right! :) I posted it here now: http://social.msdn.microsoft.com/Forums/windowsapps/de-DE/f542a1f4-7eed-4013-b0a7-ab5c9ce2c096/maintain-the-itemlist-state-after-leaving-the-page
    Tuesday, August 06, 2013 5:23 PM