Why am I getting null for document.getElementById here? - javascript

Codepen: http://codepen.io/leongaban/pen/YyWmOo?editors=101
In my app, I have a simple array of div id names media = ["twitter", "news", "blogs"]
I then have a simple for loop which attempts to grab the #ids from the markup and add them into my vm.parameters object.
Currently the div's are showing as null:
Code in my Controller:
var vm = this;
vm.parameters = {};
var media = ['twitter', 'news', 'blogs'];
console.log(vm);
console.log(media);
initParameters();
function initParameters() {
console.log('initParameters called...');
for (var i=0; i<media.length; i++) {
vm.parameters[media[i]] = {};
vm.parameters[media[i]].start = 0;
vm.parameters[media[i]].limit = 10;
vm.parameters[media[i]].total = 0;
vm.parameters[media[i]].div = document.getElementById(media[i]);
console.log('getElementById =',document.getElementById(media[i]));
}
console.log('media = ',media);
console.log('vm.parameters = ',vm.parameters);
}
Markup (Using the Ionic framework):
<ion-slide id="twitter">
<h3>Twitter stream</h3>
<p>Here is content for Twitter</p>
</ion-slide>
<ion-slide id="news">
<h3>News stream</h3>
<p>Here is content for News</p>
</ion-slide>
<ion-slide id="blogs">
<h3>Blogs</h3>
<p>Here is content for blogs</p>
</ion-slide>

You will need to do an onload event before trying to get the id's. This is because the controllers run before the DOM loads:
window.onload = function(){
initParameters();
}

DOM code doesn't belong in controllers. The controller will run before the route template is even loaded so you are looking for elements that don't exist yet.
Generally there shouldn't be any need to look for elementById . Normally you would let your data model drive creation of the view
If you must do dom manipulation like this you need to do it in a directive to be assured the element(s) exist when your code runs

You can also use
$rootScope.$on('$viewContentLoaded', function(){
// your code
});
in your angular.run(), will execute once content loaded.

Related

Dynamically create a div with dynamic onclick function

Context: I'm lazy, and I'm trying to dynamically/automatically create menu buttons which are hyperlinked to the headers of a page with raw JavaScript.
My site loads the content of the body from an external file located at the same folder with document.onload, and I'm trying to set up a menu function, which then should load the default page's menu items. Loading the menu manually each time I change from one page to another works, as I've included loadMenues(thispage) on the end loadContents(), but it doesn't work as soon as the page is loaded, as loading the body content does. I don't understand this behaviour.
function setVisible(thisdiv){
var alldivs = document.getElementsByClassName("container");
[].forEach.call(alldivs, function(uniquediv){
document.getElementById(uniquediv.id).style.display = "none";
return;
});
document.getElementById(thisdiv).style.display = "block";
window.scrollTo(0,0);
loadMenues(thisdiv);
}
window.onload = function(){
loadContent("personalinfo");
loadContent("contactdetails");
setVisible("personalinfo");
loadMenues("personalinfo");
}
I'm explaining this, secondary question, in order to contextualize my main problem.
loadContents(file) is a function which extracts the contents from the requested file. The layout of each of these files is the same, pretty much, with each section of the file being separated by a custompadding div, where its first child is a h1 element as shown below:
<html>
<div class="custompadding">
<h1 id="headerpersonaldetails">Personal details</h1>
<p>Lorem ipsum</p>
</div>
<div class="custompadding">
<h1 id="headercontactdetails">Contact details</h1>
<p>Lorem ipsum</p>
</div>
</html>
I'm trying to set up a menu item for each of these headings, which scrolls to the clicked-on header. Setting up each menu-item manually works as expected, but I want to automatize it, so changing any file will automatically add the menu items to whichever page we change to. Following is my code which adds these elements to the divisor, but I'm having issues handling the onclick function.
function loadMenues(file) {
var rightmenu = document.getElementById("right-menu");
while(rightmenu.firstChild){
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children, function(custompaddingchild) {
console.log(custompaddingchild);
headerelement = custompaddingchild.getElementsByTagName("h1")[0]
newbutton = document.createElement("div");
newbutton.setAttribute("class", "menu-item");
let movehere = function() { location.href="#"+headerelement.id; console.log(headerelement.id); }
newbutton.onclick = movehere;
/*rightmenu = document.getElementById("right-menu");*/
buttonspanner = document.createElement("span");
buttoncontent = document.createTextNode(headerelement.innerHTML);
buttonspanner.appendChild(buttoncontent);
newbutton.appendChild(buttonspanner);
rightmenu.appendChild(newbutton);
});
}
The first part of the function deletes all the nodes which already are in the menu, in order to add the new ones when changing pages.
Trying to define newbutton.setAttribute() with onclick results in a SyntaxError (fields are not currently supported) in Firefox. It doesn't work if I set a static string as newbutton.setAttribute("onclick", "location.href=#headerpersonalinfo"); either.
Trying to set a static anchor link with newbutton.onclick set to a function, instead, works, such that
newbutton.onclick = function() {
location.href = "#headerpersonalinfo";
}
and this is pretty much how my current code is set up, except that I have given this function a unique variable, which I then call.
The problem I have is this, as I see it: The variable is redefined each time it finds a new header, so calling the function sends the user to the last header, and not the expected one. How can I set the function to be parsed at the moment I define onclick with it, and not call the function when the user presses the button?
PS: I'm using my own internal naming convention of files, headers, and items, in order to modularize my site as much as I can. Since this is a website only intended for my Curriculum Vitae, I'm its only developer.
The issue occurs because the you are hoisting "variables" to the global scope (newbutton and headerelement).
Set them to block scoped variables (const or let) and you will see that it works:
https://codesandbox.io/s/rm4ko35vnm
function loadMenues(file) {
var rightmenu = document.getElementById("right-menu");
while (rightmenu.firstChild) {
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children, function(
custompaddingchild
) {
console.log(custompaddingchild);
const headerelement = custompaddingchild.getElementsByTagName("h1")[0];
console.log(headerelement.innerHTML);
const newbutton = document.createElement("div");
newbutton.setAttribute("class", "menu-item");
console.log(headerelement.id);
let movehere = function() {
location.href = "#" + headerelement.id;
console.log(headerelement.id);
};
newbutton.addEventListener('click', movehere);
const rightmenu = document.getElementById("right-menu");
const buttonspanner = document.createElement("span");
buttoncontent = document.createTextNode(headerelement.innerHTML);
buttonspanner.appendChild(buttoncontent);
newbutton.appendChild(buttonspanner);
rightmenu.appendChild(newbutton);
});
}

Adding css after creating elements

I want to add some icons to elements I created with angularJS directly after creating them.
So I am calling the function to set the icons at the same time the elements were created.
data-ng-click="opeTab($event); getObjects($event); loadObj($event); setIcons();"
The problem is, I can get the elements with:
$scope.setIcons = function(){
var tbs = document.getElementsByClassName("tabTr");
for(let i = 0; i < tbs.length; i++){
console.log(i);
tbs[i].style.backgroundImage = "url('../ICONS\Icons_24\'" + tbs[i].id + "')";
}
}
And the list in the console is filled, but the length of the array is 0.
So what possibility do I have to "wait" for the creation except setting a timeout?
You should try to avoid creating elements yourself from your controllers. Maybe you have a good reason for doing this, but I can't see that from the example you have given.
Somewhere in your template you should have an ng-repeat which renders your tabs. Each tab should have an ng-style. Lets say:
// template.html
<div class="tabs" ng-repeat="tab in tabs">
<div
class="tab"
ng-style="getBackgroundImageStyle(tab.id)">
tab {{ tab.id }}
</div>
</div>
// controller.js
$scope.tabs = [];
$scope.getBackgroundImageStyle = tabId => `{
'background-image': 'url('../ICONS/Icons_24/${tabId}')'
}`
$scope.openTab = () => {
$scope.tabs.push(new Tab(nextTabId)); // or however you create your tabs
}
If you have a good reason for accessing the dom directly like this, then there is no problem using $timeout with a delay of 0 and wrapping your dom modification inside this. Everything should be rendered before the code inside your $timeout runs.

AngularJS - Repeating a div and a button

I want to use ng-repeat to repeat a div. This div also has a button within it.
Currently, I'm doing it by creating the div and the button in the javascript part and pushing the final result in an array :
var newDiv = document.createElement('div');
var newButton = document.createElement('button');
newDiv.appendChild(newButton);
$scope.arrayDiv.push(newDiv);
I have 2 questions :
1) What should be the html syntax ? Is the following correct ?
<div id='main_chart_div' ng-repeat="x in arrayDiv" value={{x}}></div>
2) Is there a way to do that without manipulating the DOM ?
You can have the button in your template:
<div id='main_chart_div' ng-repeat="x in arrayDiv" value={{x}}>
<button></button>
</div>
By the way, you shouldn't repeat an element with a static id.
One possible way is to use ng-bind-html within ng-repeat.
ng-bind-html evaluates the expression and inserts the resulting HTML into the element in a secure way.
Secure way is with either ngSanitize or make use of $sce. Demo
Filter directive for safe binding:
app.filter('safeHtml', function ($sce) {
return function (val) {
return $sce.trustAsHtml(val);
};
});
View :
<div id='main_chart_div' ng-repeat="x in arrayDiv">
<p ng-bind-html="x.html | safeHtml"></p>
</div>
Controller:
$scope.arrayDiv = [{html: '<h1>Heading</h1>'}];
var newDiv = document.createElement('div');
var newButton = document.createElement('button');
newButton.innerHTML = "Button";
newDiv.appendChild(newButton);
$scope.arrayDiv.push({html : newDiv.outerHTML});

In Angularjs, how do I review the DOM after a route change?

In my app I use ng-view to switch out my views. I need to manipulate elements such as change width and position. When I try to do it with on my controller I'm getting, as expected, Unable to get property 'setAttribute' of undefined or null reference.
I know this is happening since JS doesn't know the DOM has changed.
Since I'm running some heavy plugins to work with SQLite in Windows 8 I can't post too much code.
This is my template being loaded into the ng-view
<div id="productslist" class="container">
<div class="product" ng-repeat="product in products">
<div class="details">
<img src="img/detail-list-back.png" class="texture" />
<div class="heading">
<p class="title">{{product.name}}</p>
<img src="img/products/title-shadow.png" class="title-shadow"/>
</div>
<div class="text">
<div class="text-container">
<p class="intro" ng-hide="product.intro == null">{{product.intro}}</p>
<p class="title">Curves</p>
{{product.prodText}}
</div>
</div>
</div>
<div class="image" style="background-image:url('img/products/nanoslim-bkg.jpg')"></div>
</div>
</div>
some of my angular. the for loop is breaking since it doesn't know the sections exist:
var totalProd = res.rows.length;
var windowW = window.innerWidth;
var sections = document.querySelectorAll('#productslist .product');
var textArea = document.querySelectorAll('#productslist .text');
document.getElementById('productslist').setAttribute('style', 'width:' + (windowW * totalProd) + 'px');
for (var i = 0; i < sections.length; i++) {
sections[i].setAttribute('style', 'width:' + windowW + 'px');
}
Everything else works fine. I'm trying to bypass this by using ng-style but having issues there.
In angular, DOM manipulations should be done in directives where possible. Here's an example of setting the width to the window width:
.directive('fullWidth', function() {
function link(scope, element, attrs) {
var windowW = window.innerWidth;
element.css({width: windowW + 'px'});
}
return {
link: link
};
});
Now in your view
<div class="product" full-width ng-repeat="product in products">
Assuming that you have a different controller for each distinct view, there are possibilities that your DOM takes more time to load then your controller, leading to the issue Unable to get property 'setAttribute' of undefined or null reference.
You can instead put your DOM selector statement in a $timeout statement, with a delay of 1 or 2 seconds, that will give DOM enough time to load and prevent any such null reference issue. Something like this :
myapp.controller("mycontroller"), function($scope, $timeout) {
$timeout(function() {
$("your-dom-selector").setAttribute("height", 500px);
}, 2000);
});
Alternatively a better approach would be to use a broadcast model, in which you can track the route change event, and as soon as the route change event succeeds, you can broadcast an event, with necessary details, that can be captured by the respective controller.
Former approach is easy to use, latter one is standard and guaranteed to be error-free.
More details upon $timeout here

create multiple divs with the same class javascript

I am new to JavaScript and would like to know how I can create multiple divs dynamically with the same class name. I have the following code but it only creates one instance of the div.
<div id="wrapper">
<div id="container">
<div id="board">
<script>
var board = document.createElement('div');
board.className = "blah";
for(x=0; x<9;x++) {
document.getElementById('board').appendChild(board);
}
</script>
</div>
</div>
</div>
Right now, you're creating the element outside the loop, and appending that element to the DOM...again and again.
What you want to do is create a new element during every iteration of the loop. To do that, move the part where you create the new div inside the loop:
for(x=0; x<9;x++) {
var board = document.createElement('div');
board.className = "blah";
document.getElementById('board').appendChild(board);
}
Now, every time the loop runs, you'll create a new element, and append that element to the element with ID #board.
It's worth pointing out that the variable you created (board) now only has scope within this loop. That means that once the loop is done, you'll need to find a different way to access the new elements, if you need to modify them.
Only a single element is created.
<script>
var board = document.createElement('div');
board.className = "blah";
for(x=0; x<9;x++) {
document.getElementById('board').appendChild(board);
}
</script>
Should be written as:
<script>
for(x=0; x<9;x++) {
var board = document.createElement('div');
board.className = "blah";
document.getElementById('board').appendChild(board);
}
</script>
Others did answer the question in a nutshell; here is one approach which addresses some issues that are present in the your and proposed code snippets, and maybe gives your some insight for further exploration. I hope it helps :)
To extend a script a little bit, this solution creates every element by using function createDiv, and references to individual divs are stored in an array, so you can modify the content of each div by modifying array elements, which are referring to DOM elements. (in this example, I modify 6th div for demonstration sake)
Notes:
All of your code is thrown in a global object, it's good
practice to encapsulate your code, here in immediately invoked
anonymous function.
x would be thrown in a global object even if encapsulated, you need
always to declare your variables with a var keyword. Here I declare
all vars needed upfront in one statement, which is also a good
practice;
It is convention to use "i" for loop iterator variable.
Avoid "magic numbers" (9), rather create a variable that will
describe what you do in your code. It is good if the code describes what
it does.
Also in this example, we avoid declaring "board" on each loop
iteration (the element where your divs get appended.)
Test your code in JSLint - great tool to validate your scripts.
(this will pass the test, given that you set indentation to 2.
"use strict" - read here.
/*jslint browser:true */
(function () {
"use strict";
function createDiv() {
var boardDiv = document.createElement("div");
boardDiv.className = "new-div";
boardDiv.innerText = "I am new DIV";
return boardDiv;
}
function createAndModifyDivs() {
var board = document.getElementById("board"),
myDivs = [],
i = 0,
numOfDivs = 9;
for (i; i < numOfDivs; i += 1) {
myDivs.push(createDiv());
board.appendChild(myDivs[i]);
}
myDivs[5].className = "modified-div";
myDivs[5].innerText = "I'm modified DIV";
}
createAndModifyDivs();
}());
.new-div {
color: gray;
}
.modified-div {
color: red;
}
<!DOCTYPE html>
<html>
<head>
<title>Inserting Divs</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<div id="board">
</div>
</div>
</div>
</body>
</html>

Categories