Answered by:
Where or how can I see DOM elements created on the fly by JavaScript (for windows Store apps in Visual Studio)

Question
-
Where or how can I see DOM elements that are created by JavaScript on the fly? In Visual studio project? Or in Blend? Where should I look when debugging a project in either of these tools? (Is it possible to see them in the code, at all?)
thanks
Tuesday, April 30, 2013 10:09 PM
Answers
-
What's happening is that with WinJS page controls (which is what you get with the Nav App template, as well as Grid App), you're actually always in the context of default.html and won't see the other .html files loaded as discrete pages.
If you look at default.html in your project, you'll see this:
<div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home: '/pages/home/home.html'}"></div>
This one div is where the contents of the other "pages" will be dynamically loaded using DOM replacement like single-page web apps use. The code that does this is in js/navigator.js and uses the API in WinJS.UI.Pages. (I explain this mechanism in Chapter 3 of my book if you want to know more.)
What this means, then, is that when you look in the DOM Explorer or Live DOM, you need to expand the contenthost div along with its first child div. Inside that you'll then see your page contents. For example, from the nav app template, the highlighted line in the image below corresponds to the "page" from html/pages/home.html:
(In fact, even the comment on the line before comes from home.html.)
In short, because your "pages" are being loaded into the DOM as fragments rather than navigated to as distinct/separate pages, you'll see them within default.html.
Again, see chapter 3 in my book, in the section entitled "Page Controls and Navigation." I specifically talk about the nav app template in there.
Kraig
Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press
- Marked as answer by SonalMac Wednesday, May 1, 2013 6:45 PM
Wednesday, May 1, 2013 6:25 PM
All replies
-
You can see them in both tools.
In Visual Studio, while debugging, there will be a tab called DOM Explorer (go to Debug > Windows > DOM Explorer if you don't see it). In that you can select elements and see all their styles, layout, attributes, etc. There's even an option that lets you go select an element in the app directly. For details, see, http://msdn.microsoft.com/en-us/library/windows/apps/hh441474.aspx#InteractiveDebugging.
In Blend, once you load a project there will be a tab on the lower left pane called Live DOM, where you can expand all the nodes and see everything that's created dynamically. You can select elements within this tree, or if you select elements in the artboard they'll be selected in the Live DOM. You can also switch into Interactive Mode, manipulate the app into the state you want (including navigation, etc.), then switch out of that and the current DOM will be in the artboard and LiveDOM both. See http://msdn.microsoft.com/en-us/library/windows/apps/jj129409.aspx and topics around it for more.
Kraig
Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press
- Proposed as answer by Chuck Hays Wednesday, May 1, 2013 1:03 AM
Tuesday, April 30, 2013 10:45 PM -
I expected it that way and tried everything as I found in that article. But I can see DOM Explorer working only for default.html page. I'm using a navigation app template. Trying to learn about navigation, appbar and other things. When I run the app, the DOM Explorer in Visual Studio shows only default.html page.
I can't see that working either in Blend. Also Interactive mode is not being interactive for this project.
- Edited by SonalMac Wednesday, May 1, 2013 5:20 AM
Wednesday, May 1, 2013 4:57 AM -
What's happening is that with WinJS page controls (which is what you get with the Nav App template, as well as Grid App), you're actually always in the context of default.html and won't see the other .html files loaded as discrete pages.
If you look at default.html in your project, you'll see this:
<div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home: '/pages/home/home.html'}"></div>
This one div is where the contents of the other "pages" will be dynamically loaded using DOM replacement like single-page web apps use. The code that does this is in js/navigator.js and uses the API in WinJS.UI.Pages. (I explain this mechanism in Chapter 3 of my book if you want to know more.)
What this means, then, is that when you look in the DOM Explorer or Live DOM, you need to expand the contenthost div along with its first child div. Inside that you'll then see your page contents. For example, from the nav app template, the highlighted line in the image below corresponds to the "page" from html/pages/home.html:
(In fact, even the comment on the line before comes from home.html.)
In short, because your "pages" are being loaded into the DOM as fragments rather than navigated to as distinct/separate pages, you'll see them within default.html.
Again, see chapter 3 in my book, in the section entitled "Page Controls and Navigation." I specifically talk about the nav app template in there.
Kraig
Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press
- Marked as answer by SonalMac Wednesday, May 1, 2013 6:45 PM
Wednesday, May 1, 2013 6:25 PM -
Thank you very much, Kraig. That makes it so clear and now I can see my HTML page rendering in the CODE. That's great!!
Now I'm wondering why can't I get reference to those buttons I created there?
Wednesday, May 1, 2013 6:46 PM -
If you're trying to use getElementById or querySelector for page contents, you need to do it within the ready method of the page control (see pages/home/home.js). If you try to do it in the activated handler of default.js, the page contents won't be available yet, plus the contents will vary from page to page. That's what the ready method is for.
Wednesday, May 1, 2013 6:53 PM -
Yes, I'm doing it within the ready method of the page control itself. I tried to use both methods (getElementById and querySelector) to verify my code, but still the result is zero.Wednesday, May 1, 2013 7:30 PM
-
So I imagine you're trying to see if those controls are actually in the DOM, then, yes? Are they there, or are they missing? Feel free to paste the page's .html and your ready method code here and I can take a look.Wednesday, May 1, 2013 7:37 PM
-
YEs, controls are there in the page. I'm trying to paste .html and js files here. I'm trying to know many things and this code is getting complex. I hope you'll get it right. OR if its not, then let me know I can upload that project into skyDrive. First, I'll try to copy-paste here:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>timer</title> <!-- WinJS references --> <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" /> <script src="//Microsoft.WinJS.1.0/js/base.js"></script> <script src="//Microsoft.WinJS.1.0/js/ui.js"></script> <link href="/pages/timer/timer.css" rel="stylesheet" /> <script src="/pages/timer/timer.js"></script> </head> <body> <div id="mainContent" class="timer fragment"> <header id="header" aria-label="Header content" role="banner"> <button class="win-backbutton" aria-label="Back" disabled type="button"></button> <h1 class="titlearea win-type-ellipsis"> <span class="pagetitle">Timer</span> </h1> </header> <section id="section" aria-label="Main content" role="main"> <div id="timerList" data-win-control="WinJS.Binding.Template"> <Div id="timerDataSource" class="templateItem"> <span id="name" data-win-bind="textContent:tName"></span> <span id="time" data-win-bind="textContent:tInterval"></span> <!--<span id="sound" data-win-bind="textContent:tSound"></span> <span id="Span1" data-win-bind="textContent:tRepeating"></span>--> <!--<span id="indTimerStartButton"><input class="buttonStyle" type="button" id="stBt" data-win-bind="onclick:bindingFunction" value="START" /></span>--> </div> </div> <div id="templateControlRenderTarget"></div> <div id="showRunningTimer" class="hidden"> <div id="timeNow"></div> <div id="startAndStopBt"><input type="button" id="startBt" value="start" /><input type="button" id="stopBt" value="Stop" /> </div> <div id="cancelBt"><input type="button" id="cancel" value="cancel" /></div> <div id="soundOp"><select id="sounds"></select></div> </div> <!--<input id="create" type="button" value="Create" />--> <div id="timer" class="hidden"> <div id="timeDiv" data-win-control="WinJS.UI.TimePicker" data-win-options="{ clock : '24HourClock', current: '00:00:00'}" ></div> <div id="timerName"><label for="label">Label</label><input type="text" id="label" /></div> <div id="soundsList"><label for="listOfSounds">Sound</label><select id="listOfSounds"> <option value="sound1">Sound1</option> <option value="sound2">Sound2</option> <option value="sound3">Sound3</option> <option value="sound4">Sound4</option> </select></div> <div id="repeated"><label for="repeatingSounds">Repeat Sounds?</label><input type="radio" id="repeatingSounds" value="Yes"/><input type="radio" id="repeatingSounds" value="No" /></div> <div id="beginBtDiv"><input id="begin" type="button" value="Begin" /></div> </div> <div id="start" class="buttonStyle hidden"><input class="buttonStyle" type="button" id="startButton" value="Start" /></div> <div id="audioDiv" class="hidden"><audio id="audSound" controls> <source src="sounds/segment1.mp3"/> </audio> </div> </section> </div> </body>
timer.js
// For an introduction to the Page Control template, see the following documentation: // http://go.microsoft.com/fwlink/?LinkId=232511 (function () { "use strict"; var started = false; //global varible. To keep track whether this timer has already started. var mediaControls; WinJS.UI.Pages.define("/pages/timer/timer.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { // TODO: Initialize the page here. var startButton = document.getElementById("startButton"); startButton.addEventListener("click", this.startTimer.bind(this), false); this.timeDiv = element.querySelector("#timeDiv"); //var timePickerControl = this.timeDiv.winControl; //var timePickerControl = this.timePicker; this.timeDiv.addEventListener("change", this.timerChanged.bind(this), false); //WinJS.Utilities.query("#create", element) // .listen("click", this.showTimer.bind(this)); WinJS.Utilities.query("#begin", element) .listen("click", this.addNewTimer.bind(this)); //var buttons = document.querySelector("#timerDataSource"); var buttons = document.querySelector("#templateControlRenderTarget"); console.log("timer: " + buttons.id); //var allButtons = buttons.querySelectorAll(".buttonStyle"); //var allButtons = buttons.querySelectorAll("span"); var allButtons = buttons.getElementsByTagName("div"); var bFound = (allButtons == null) ? "No matches found" : allButtons.length; console.log(bFound); for (var i = 0; i < bFound; i++) { console.log(allButtons[i].id); allButtons[i].addEventListener("click", function (e) { console.log(e.target.textContent); }, false); } // Restore app data. var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings; //application state: var saveBtn = document.getElementById("begin"); saveBtn.onblur = function (e) { app.sessionState.storageData = dataArray; roamingFolder.createFileAsync(filename, Windows.Storage.CreationCollisionOption.openIfExists) .then(function (filename) { var savedData = JSON.stringify(dataArray); console.log("Saved Data: " + savedData); //return Windows.Storage.FileIO.appendTextAsync(filename, savedData); return Windows.Storage.FileIO.writeTextAsync(filename, savedData); }).done(function () { // that.filesDisplayOutput(); }); } this.filesDisplayOutput(); }, unload: function () { // TODO: Respond to navigations away from this page. //AppBarUtils.removeAppBars(); }, updateLayout: function (element, viewState, lastViewState) { /// <param name="element" domElement="true" /> // TODO: Respond to changes in viewState. }, // addTimer: function() { // //create control // var timePicker = new WinJS.UI.TimePicker(this.timeDiv, { current: '00:00:00' }); // timePicker.clock = "24HourClock"; //set 24 hour clock setting // var newTime, newHour, newMS, newTimer; // //connect event listeners: // timePicker.element.addEventListener("change", function () { // //console.log("timerpicker changed, new value is:\n" + timePicker.current.toLocaleTimeString()); // newTime = timePicker.current.getMinutes(); // newHour = timePicker.current.getHours(); // newTimer = newTime * 1000; // return newTimer; // }); // console.log("Minutes:" + newTimer); // console.log(this.newTimer); // //return this.newTimer; //}, //End of addTimer function addNewTimer: function() { var newLabel = document.getElementById("label"); var newlyaddedTimer = this.timerChanged(); var newSound = document.getElementById("listOfSounds"); var newRepeated = document.getElementById("repeatingSounds"); var that = this; var timerToSave = { "timerName": '"' + newLabel.value + '"', "timerInterval": newlyaddedTimer, "timerSound": '"' + newSound.value + '"', "timerRepeated": '"' + newRepeated.value + '"' }; dataArray[counter] = timerToSave; counter = counter + 1; }, showTimer: function(event) { var timer = document.getElementById("timer"); var timePk = document.getElementById("timeDiv").winControl; timePk.element.style.display = 'inline'; timer.className = 'show'; var st = document.getElementById("start"); st.className = 'show'; }, timerChanged: function(event) { var timerMinutes, timerHour, newTimer; var timePk = document.getElementById("timeDiv"); timerHour = timePk.winControl.current.getHours(); timerMinutes = timePk.winControl.current.getMinutes(); if (parseInt(timerHour) == 0) { if (parseInt(timerMinutes) == 0 || parseInt(timerMinutes) == '00') { return; } else { newTimer = (timerMinutes*60) * 1000; console.log("Minutes" + newTimer); return newTimer; } } else { timerHour = (timerHour * 60) * 1000; console.log("Hour" + timerHour); newTimer = ((timerMinutes * 60) * 1000) + timerHour; console.log("Minutes" + timerMinutes); return newTimer; } }, timer: function(time, update, complete) { var start = new Date().getTime(); var interval = setInterval(function () { var now = time - (new Date().getTime() - start); if (now <= 0) { clearInterval(interval); complete(); } else { update(Math.floor(now / 1000)); } }, 100); //the smaller this number, the more accurate the timer will be. }, //End of time function playaudSound: function() { mediaControls = Windows.Media.MediaControl; //mediaControls.isPlaying = true; document.getElementById("audSound").play(); }, startTimer: function () { var that = this; if (started) return; else { started = true; var startButton = document.getElementById("startButton"); startButton.style.display = 'none'; var timerSeconds = this.timerChanged(); console.log(timerSeconds); this.timer(timerSeconds, //milliseconds function (timeleft) { document.getElementById('timer').innerText = "Time Left " + timeleft + " Seconds"; }, function () { console.log("Timer complete"); //document.getElementById("audSound").play(); that.playaudSound(); } ); } }, //complete function startTimer. createNewDiv: function() { //var listDiv = document.getElementById("timerList"); }, filesReadCounter: function () { roamingFolder.getFileAsync(filename) .then(function (filename) { return Windows.Storage.FileIO.readTextAsync(filename); }).done(function (data) { var dataToRead = JSON.parse(data); dataArray = dataToRead; counter = dataArray.length; if (dataArray[counter] == 0 || dataArray[counter] == null) { dataArray = dataToRead; } var message = "My Saved Data :" + "\n"; var listDiv = document.getElementById("timerList"); //DOM MANIPULATION var templateElement = document.getElementById("timerList"); var renderElement = document.getElementById("templateControlRenderTarget"); renderElement.innerHTML = ""; var timerDataSource = WinJS.Binding.define({ tName: "", tInterval: 0, tSound: "", tRepeating: "", tButton: bindingFunction }); //converter function to use with data-win-bind property of a button function buttonClick(e) { var target = e.target; var targetId = target.id; target.onclick = function () { console.log("button Click to startTimer"); } } var bindingFunction = WinJS.Binding.bind(timerDataSource.tButton, buttonClick); var arrayDS = []; var str, str1, str2, str3, str4; if (counter > 0) { for (var i = 0; i < counter; i++) { str = dataArray[i].timerName; str1 = str.slice(1, str.length - 1); str2 = dataArray[i].timerInterval; str3 = dataArray[i].timerSound; str4 = dataArray[i].timerRepeated; message += str1 + "\n"; //ARRAY OF OBJECTS: arrayDS[i] = new timerDataSource({tName: str1, tInterval: str2, tSound: str3, tRepeating: str4, tButton:onclick}); //templateElement.winControl.render(arrayDS[i], renderElement); templateElement.winControl.render(arrayDS[i]) .done(function doneResult(result) { var resultId = "result" + i; renderElement.appendChild(result); var newResBt = document.createElement("button"); newResBt.id = resultId; renderElement.appendChild(newResBt); }, function showError(error) { console.log(error); }); } } }, function () { //getFileAsynce failed. }); }, //End of filesRead filesDisplayOutput: function () { this.filesReadCounter(); }, buttonsClickHandler: function () { var buttons = document.getElementsByTagName("button"); var buttonsLength = buttons.length; //console.log(buttonsLength); for (var i = 0; i < buttonsLength; i++) { buttons[i].addEventListener("click", function () { //console.log(buttons[i].value); }, false); } } }); //roaming data app global vars: var roamingFolder = Windows.Storage.ApplicationData.current.roamingFolder; var filename = "timers.txt"; var counter = 0; var dataArray = []; var app = WinJS.Application; })()
Inside filesReadCounter function I'm creating a template and html button. Inside ready method, I want to get reference to these buttons So I can fire a click event for them. In html, first two divs are for rendering template and buttons.Wednesday, May 1, 2013 9:17 PM -
Can you be more specific on what, exactly, is failing? I'm not sure looking at all this.Thursday, May 2, 2013 4:57 PM
-
WinJS.UI.Pages.define("/pages/timer/timer.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { // TODO: Initialize the page here. //var buttons = document.querySelector("#timerDataSource"); var buttons = document.querySelector("#templateControlRenderTarget"); console.log("timer: " + buttons.id); //var allButtons = buttons.querySelectorAll(".buttonStyle"); //var allButtons = buttons.querySelectorAll("span"); var allButtons = buttons.getElementsByTagName("button"); var bFound = (allButtons == null) ? "No matches found" : allButtons.length; console.log(bFound); for (var i = 0; i < bFound; i++) { console.log(allButtons[i].id); allButtons[i].addEventListener("click", function (e) { console.log(e.target.textContent); }, false); } } //End of ready function. //Function to read data from file and populate html template: //DOM MANIPULATION var templateElement = document.getElementById("timerList"); var renderElement = document.getElementById("templateControlRenderTarget"); renderElement.innerHTML = ""; var timerDataSource = WinJS.Binding.define({ tName: "", tInterval: 0, tSound: "", tRepeating: "", tButton: bindingFunction } var bindingFunction = WinJS.Binding.bind(timerDataSource.tButton, buttonClick); //READING dataArray FROM FILE: var arrayDS = []; var str, str1, str2, str3, str4; //ARRAY OF OBJECTS: arrayDS[i] = new timerDataSource({tName: str1, tInterval: str2, tSound: str3, tRepeating: str4, tButton:onclick}); //templateElement.winControl.render(arrayDS[i], renderElement); templateElement.winControl.render(arrayDS[i]) .done(function doneResult(result) { var resultId = "result" + i; renderElement.appendChild(result); var newResBt = document.createElement("button"); newResBt.id = resultId; renderElement.appendChild(newResBt); }, function showError(error) { console.log(error); }); } }
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>timer</title> <!-- WinJS references --> <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" /> <script src="//Microsoft.WinJS.1.0/js/base.js"></script> <script src="//Microsoft.WinJS.1.0/js/ui.js"></script> <link href="/pages/timer/timer.css" rel="stylesheet" /> <script src="/pages/timer/timer.js"></script> </head> <body> <div id="mainContent" class="timer fragment"> <header id="header" aria-label="Header content" role="banner"> <button class="win-backbutton" aria-label="Back" disabled type="button"></button> <h1 class="titlearea win-type-ellipsis"> <span class="pagetitle">Timer</span> </h1> </header> <section id="section" aria-label="Main content" role="main"> <div id="timerList" data-win-control="WinJS.Binding.Template"> <Div id="timerDataSource" class="templateItem"> <span id="name" data-win-bind="textContent:tName"></span> <span id="time" data-win-bind="textContent:tInterval"></span> <span id="Span1" data-win-bind="textContent:tRepeating"></span>--> <!--<span id="indTimerStartButton"><input class="buttonStyle" type="button" id="stBt" data-win-bind="onclick:bindingFunction" value="START" /></span>--> </div> </div> <div id="templateControlRenderTarget"></div> </div> </body>
Above is html part where I want to render JS-Created elements. (With template data binding)
In JS I tried to include a part where I want to get reference to the elements created in ready function. And another function where I'm creating those elements in a data-binding template. I'm reading data from a text file containing JSON format data. But I've tried to omit all those details here.
Thursday, May 2, 2013 5:26 PM -
Thanks for the simplification. It’s easier to see what appears to be going on here.
First, all the work to create the buttons within filesReadCounter is within a series of async operations: first roamingFolder.getFileAsync, then Windows.Storage.FileIO.readTextAsync, then templateElement.winControl.render, which is also async. This means that the code yields to the UI thread as each operation is kicked off. So even when filesReadCounter returns, only the first operation is probably underway, and the readTextAsync and the render calls will happen at a later time.
Next, you’re calling filesReadCounter, it appears, at the end of the ready method, which means that none of the async work in that function has been started by that time. So the code you have in the ready method with querySelector and all that is executing well before the buttons have actually been created. So that you see null there is expected.
To solve this, you need to have everything within the ready method that depends on those elements wait on the completion of the template rendering. The way to do this is to have filesReadCounter return the promise from the render call, and then you have a completed handler in the ready method in which you query for the elements.
Let me condense the code so you can see the structure. First, in filesReadCounter, make it one long promise chain and return the promise from the final then (don’t use done here as it returns undefined):
filesReadCounter: function () {
return roamingFolder.getFileAsync(filename)
.then(function (filename) {
return Windows.Storage.FileIO.readTextAsync(filename);
}).then(function (data) {
//...
return templateElement.winControl.render(arrayDS[i]);
}).then(function Result(result) {
//appendChild/createElement
//no return value neededhere
});
This promise from the series of .then calls will be fulfilled then the past completed handler is finished. That last completed handler is where you’re creating the elements, so ultimately the promise returned here will be fulfilled when all those elements are real.
Then be sure to return that promise from filesDisplayOutput:
//This now returns a promise
filesDisplayOutput: function () {
return this.filesReadCounter();
},
Finally, in the ready method, attach a completed handler (using done now, because you’re not doing any more chaining), and in that handler you can query the elements:
//In the ready method
this.filesDisplayOutput.done(function () {
//Now you can get to the buttons
var buttons = document.querySelector("#templateControlRenderTarget");
//...
});
In short, you just need to make sure that all the async work is happening when you expect it to, and you have the right dependencies on completion of that async work for other steps you need to perform.
Friday, May 3, 2013 3:09 AM -
Thank you very much for explaining all these, Kraig. I appreciate. This explanation will help me a lot all the way.Friday, May 3, 2013 6:46 PM
-
You're very welcome :)Friday, May 3, 2013 7:35 PM
-
Hi Kraig, Sorry to bother you again. Got into one more question );
I updated my code as per your explanation. The chaining of functions in filesReadCounter works just file. But calling done method in ready function
//In the ready method
this.filesDisplayOutput.done(function () {
//Now you can get to the buttons
var buttons = document.querySelector("#templateControlRenderTarget");
//...
});
throws error: object doesn't support property or method "done".. ...
Friday, May 3, 2013 10:15 PM -
My mistake. It should be this.filesDisplayOutput().done(...)--it's a function and we need the () to call it and get the promise back.Friday, May 3, 2013 11:36 PM
-
great !! that worked and now I see, that I missed that () too..... THANKS!!!!Saturday, May 4, 2013 1:30 AM