Why is childNodes not working in this script? - dom

I am new to JS and trying to learn some basic stuff. I have spent hours on this but i don't think it should be that difficult. For some reason, my computer is not recognizing that I am asking for childNodes.
This is a simply script that is only trying to count the number of li tags I have. I know there are other ways to do this but i am trying to learn this way.
<title>To-Do List</title>
<script>
function findComments(){
var bodyTag = document.getElementsByTagName("ol");
var count = 0;
for(var i=0;i<bodyTag.childNodes.length;i++){
if(bodyTag.childNodes[i].nodeType == 1){
count++;
}
}
alert(count);
}
window.onload = findComments;
</script>
<!--List is declared-->
<ol id="toDoList">
<!--List Items are created-->
<li>Mow the lawn</li>
<li>Clean the windows</li>
<li>Answer your email</li>
</ol>
<!--Main paragraph-->
<p id="toDoNotes">Make sure all these are completed by 8pm so you can watch the game on TV!</p>
<script>
</script>

getElementsByTagName returns an array, you need to retrieve its first element (and change the name from bodyTag to olTag or something since it's not the body tag and confused the heck out of me trying to make sense of your code)
function findComments(){
var ol = document.getElementsByTagName("ol")[0];
var count = 0;
for(var i=0;i<ol.childNodes.length;i++){
if(ol.childNodes[i].nodeType == 1){
count++;
}
}
alert(count);
}
And here's what you really should do now that you know what's wrong with your code
var ol = document.getElementsByTagName("ol")[0];
var liCount = ol.getElementsByTagName("li").length;

Related

How to display the value (sum) rather than count of markers in a dc.leaflet.js

I want to use crossfilter's reduceSum function dc.leaflet.js, and display the sum instead of the number of clustered markers.
The first example for dc.leaflet.js uses reduceCount. Additionally it doesn't use the reduced value; it just displays the number of markers in the cluster.
I want to use the sum of data using reduceSum.
Here is my data as tsv:
type geo say
wind 38.45330,28.55529 10
wind 38.45330,28.55529 10
solar 39.45330,28.55529 10
Here is my code:
<script type="text/javascript" src="../js/d3.js"></script>
<script type="text/javascript" src="../js/crossfilter.js"></script>
<script type="text/javascript" src="../js/dc.js"></script>
<script type="text/javascript" src="../js/leaflet.js"></script>
<script type="text/javascript" src="../js/leaflet.markercluster.js"></script>
<script type="text/javascript" src="../js/dc.leaflet.js"></script>
<script type="text/javascript">
/* Markers */
d3.csv("demo1.csv", function(data) {
drawMarkerSelect(data);
});
function drawMarkerSelect(data) {
var xf = crossfilter(data);
var facilities = xf.dimension(function(d) { return d.geo; });
var facilitiesGroup = facilities.group().reduceSum(function(d){return d.say});
dc.leafletMarkerChart("#demo1 .map")
.dimension(facilities)
.group(facilitiesGroup)
.width(1100)
.height(600)
.center([39,36])
.zoom(6)
.cluster(true);
var types = xf.dimension(function(d) { return d.type; });
var typesGroup = types.group().reduceSum(function(d){return d.say});
dc.pieChart("#demo1 .pie")
.dimension(types)
.group(typesGroup)
.width(200)
.height(200)
.renderLabel(true)
.renderTitle(true)
.ordering(function (p) {
return -p.value;
});
dc.renderAll();
}
</script>
I have rewritten the question because it was very unclear. I agree with #Kees that the intention was probably to display the sum in a clustered marker chart, rather than "reduceSum doesn't work".
#Kees also pointed out a Leaflet.markercluster issue which gives basic information about how to display a sum inside a marker cluster.
The question becomes, how to apply these customizations to dc.leaflet.js?
First, I've created a version of the example data with another column rnd:
type geo rnd
wind 43.45330,28.55529 1.97191
wind 43.44930,28.54611 3.9155
wind 43.45740,28.54814 3.9922
...
We can use reduceSum like this:
var facilitiesGroup = facilities.group().reduceSum(d => +d.rnd);
And annotate each marker with its value by overriding .marker(), wrapping the default callback:
const old_marker_function = marker.marker();
marker.marker(function(d, map) {
const m = old_marker_function(d, map);
m.value = d.value;
return m;
});
And we can specify a different rendering of the icon using .clusterOptions():
marker.clusterOptions({
iconCreateFunction: function(cluster) {
var children = cluster.getAllChildMarkers();
var sum = 0;
for (var i = 0; i < children.length; i++) {
sum += children[i].value;
}
sum = sum.toFixed(0);
var c = ' marker-cluster-';
if (sum < 10) {
c += 'small';
} else if (sum < 100) {
c += 'medium';
} else {
c += 'large';
}
return new L.DivIcon({ html: '<div><span>' + sum + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
}
});
The example given in the above issue was missing any styling, so I copied the implementation of _defaultIconCreateFunction from the Leaflet.markercluster sources, and modified it.
Demo fiddle
As expected, the numbers are close to 2.5 times the original numbers (since the new column is a random number from 0 to 5).
Putting numbers on the individual markers
marker.icon() allows you change the icon for individual markers, so you can use DivIcon with similar styling to display the numbers:
marker.icon(function(d, map) {
return new L.DivIcon({
html: '<div><span>' + d.value.toFixed(0) + '</span></div>',
className: 'marker-cluster-indiv marker-cluster',
iconSize: new L.Point(40, 40) });
});
This introduces a new class .marker-cluster-indiv to distinguish it from the others; in the new fiddle I've colored them blue with
.marker-cluster-indiv {
background-color: #9ecae1;
}
.marker-cluster-indiv div {
background-color: #6baed6;
}
The interaction is perhaps less clear since clicking blue dots brings up a popup instead of expanding. Perhaps a different icon should be used.
The reduceSum part should work fine, since that is just a different crossfilter function.
Are you sure that your data is getting read correctly? You state that it is a tsv file, and show code that looks like it is tab-separated, but then you use d3.csv to load it, which would have pretty bad effects considering there is a comma in the middle of the second field.
Please try console.log(data) after your data is loaded, and verify that it is loading correctly.
Also, you do not state what problem you encounter. "It doesn't work" does not help us help you.

Destroy leaflet map trouble

I use several leaflet maps at one time. They can be created and deleted dynamically. But when a map is destroyed by using the map.remove() there is a memory leak. Detached DOM trees appears. You can see it in Chrome Dev Tools.
Screenshot with a leak.
Example function I use to recreate div and map:
var map, mapDiv;
recreateMap = function(){
// destroy previous map and div
if(map) map.remove();
if(mapDiv) mapDiv.parentNode.removeChild(mapDiv);
// create new map div
var randomDivId = 'mapId' + new Date().getTime();
mapDiv = document.createElement('div');
mapDiv.id = randomDivId;
mapDiv.style.height = '200px';
mapDiv.style.width = '200px';
document.getElementsByTagName('body')[0].appendChild(mapDiv);
// attach map to div
map = L.map(randomDivId).setView([51.505, -0.09], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
map.invalidateSize();
};
Working example here.
How to properly destroy the leaflet maps?
Assume you create a leaflet map with some thing like the following
var lat =39, long = 40;
var coords=[lat,long];
var zoomLevel=13;
var mapInstance = leafLet.map(mapContainerId).setView(coords, zoomLevel);
You can remove it using the following code
if (mapInstance && mapInstance.remove) {
mapInstance.off();
mapInstance.remove();
}
If you are working with non blocking javascript code or async calls, if required you can use a timer to ensure that your code does not error out. Following is a sample implementation of the same
var timeoutIndex=0;
var watcher=window.setInterval(function(){
timeoutIndex++;
if (mapInstance && mapInstance.remove) {
mapInstance.off();
mapInstance.remove();
window.clearInterval(watcher);
}
if(timeoutIndex >50) { //wait for 5 seconds before giving up
window.clearInterval(watcher);
}
},100);
It worked for me. Guess it helps you too
If you have several maps, you need to put each map into diffents var.
var map1 = L.map(mapDiv1);
var map2 = L.map(mapDiv2);
You can also create a function to control map initialisation and container :
function BoolMapInit(map, mapDiv) {
return (map != null && map._container.id == divMap);
}
And the fonction to remove existing map :
function RemoveExistingMap(map) {
if (map != null) {
map.remove();
map = null;
}
}
Hope this help ;)
I had the same problem, and after spending much time on it, the best solution for this problem is to put the map container in a div, and when you want to regenerate the map, remove all of the div's HTML and create a new map container:
<div id="map-bx">
<div id="map"></div>
</div>
<script type="text/javascript">
// Map init
</script>
and when you want to regenerate (destroy) the map like so:
<script type="text/javascript">
$("#map-box").html("");
$("#map-box").html('<div id="map"></div>');
// map init code
</script>
in my case, map.remove() or map.unload() do not work.
maybe try to unload map first? (documentation says that map is being unloaded automatically when using remove method but it's worth to give an additional shot and try unload it manually first)

Class prefix as selector for each function

I am able to do this using an ID prefix as the selector, but I need to be able to do it with classes instead. It's an each function for opening up different modal windows on the same page. I need to avoid using ID names because I have some modal windows that will have multiple links on the same page, and when using IDs, only the first link will work.
So here's the function as it works with IDs:
$('div[id^=ssfamodal-help-]').each(function() {
var sfx = this.id,
mdl = $(this),
lnk = $('.link-' + sfx),
cls = $('.ssfamodal-close'),
con = $('.ssfamodal-content');
lnk.click(function(){
mdl.show();
});
cls.click(function(){
mdl.hide();
});
mdl.click(function() {
mdl.hide();
});
con.click(function() {
return false;
});
});
and I'm trying to change it to classes instead, like:
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = this.attr('class'),
etc.
But I cannot get it to work without using IDs. Is it possible?
EDIT Fixed error with semi-colon at end of Vars, and updated Fiddle with the fix. Still not working though.
Here's a Fiddle
** UPDATE **
To be clearer, I need to be able to refer to the same modal more than once on the same page. E.g.:
MODAL 1
MODAL 2
MODAL 3
MODAL 4
LINK TO MODAL 1
LINK TO MODAL 2
LINK TO MODAL 3
LINK TO MODAL 4
OTHER STUFF
LINK TO MODAL 1
LINK TO MODAL 4
LINK TO MODAL 3
OTHER STUFF
LINK TO MODAL 2
ETC.
When using classes get rid of the ID habit :
className1, className2, className3 ... etc
simply use
className
HTML:
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy
</div>
</div>
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy Ho
</div>
</div>
<span class="link-ssfamodal-help-base">One</span>
<span class="link-ssfamodal-help-base">Two</span>
LIVE DEMO
var $btn = $('.link-ssfamodal-help-base'),
$mod = $('.ssfamodal-help-base'),
$X = $('.ssfamodal-close');
$btn.click(function(i) {
var i = $('[class^="link-"]').index(this); // all .link-** but get the index of this!
// Why that?! cause if you only do:
// var i = $('.link-ssfamodal-help-base').index();
// you'll get // 2
// cause that element, inside a parent is the 3rd element
// but retargeting it's index using $('className').index(this);
// you'll get the correct index for that class name!
$('.ssfamodal-help-base').eq(i).show() // Show the referenced element by .eq()
.siblings('.ssfamodal-help-base').hide(); // hide all other elements (with same class)
});
$X.click(function(){
$(this).closest('.ssfamodal-help-base').hide();
});
From the DOCS:
http://api.jquery.com/eq/
http://api.jquery.com/index/
http://api.jquery.com/closest/
Here I created a quite basic example on how you can create a jQuery plugin of your own to handle modals: http://jsbin.com/ulUPIje/1/edit
feel free to use and abuse.
The problem is that class attributes can consist of many classes, rather than IDs which only have one value. One solution, which isn't exactly clean, but seems to work is the following.
$('div').filter(function () {
var classes = $(this).attr('class').split(/\s+/);
for (var i = 0; i < classes.length; i++)
if (classes[i].indexOf('ssfamodal-help-') == 0)
return true;
return false;
}).each(function() {
// code
});
jsFiddle
Or, equivalently
$('div').filter(function () {
return $(this).attr('class').split(/\s+/).some(function (e) {
return e.indexOf('ssfamodal-help-') == 0;
});
}).each(function() {
// code
});
jsFiddle
If there is one-to-one relationship between the modal helps and the modal links which it appears there is...can simplfy needing to match class values by using indexing.
For this reason you don't need unique class names, rather they just overcomplicate things. Following assumes classes stay unique however
var $helps=$('div[id^=ssfamodal-help-]');
var $help_links=$('div[id^=link-ssfamodal-help-]');
$help_links.click(function(){
var linkIndex= $help_links.index(this);
$helps.hide().eq( linkIndex ).show();
});
/* not sure if this is what's wanted, but appeared original code had it*/
$helps.click(function(){
$(this).hide()
})
/* close buttons using traverse*/
$('.ssfamodal-close').click(function(){
$(this).closest('div[id^=ssfamodal-help-]' ).hide();
});
Also believe that this code is a little more readable than original apporach
DEMO
Can you try this,
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = $(this).attr('class');
console.log(sfx);
/*console log:
ssfamodal-help-base ssfamodal-backdrop
ssfamodal-help-base2 ssfamodal-backdrop
*/
});
Demo: http://jsfiddle.net/xAssR/51/
why don't you write like
$('div.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
or
$('.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
where classname is the name of the class used for the div in html
Thanks.

codemirror 3.0 format preloaded textarea code

I am using CodeMirror to create an editor in an HTML5 based presentation. In it's simplest form, the html looks something like this.
<section class="pattern">
<textarea id='pattern-view' class='codemirror' data-mode='javascript'>
var myModule = function(){
//code goes here
}
</textarea>
</section>
and down in a document ready I have the code
$(function(){
var tAreas = document.querySelectorAll('.codemirror');
for (var i = 0; i < tAreas.length; i++) {
CodeMirror.fromTextArea(tAreas[i], { theme: 'monokai', mode: tAreas[i].dataset.mode });
}
});
This works as expected, the textarea is replaced with the editor. The trouble is the indentation is maintained and not properly formatted. It only highlights the code, does not re-format the contents.
Is there something I need to add to this? I did find code for formatting.js addon, which is no longer part of codemirror 3.0.
Is there someway to auto-format the code inside the textarea?
Answering my own question. For those who might chance upon this. Get the formatting.js from the old codemirror and put it where you like. Add this to your dom ready function
var tAreas = document.querySelectorAll('.codemirror'); //assuming all textareas have the class codemirror
for (var i = 0; i < tAreas.length; i++) {
var editor = CodeMirror.fromTextArea(tAreas[i], {theme: 'monokai',mode: tAreas[i].dataset.mode, tabMode: 'indent' });
CodeMirror.commands["selectAll"](editor);
autoFormatSelection(editor);
$(tAreas[i]).trigger({type: 'keypress', which: 13});
}
function getSelectedRange(editor) {
return { from: editor.getCursor(true), to: editor.getCursor(false) };
}
function autoFormatSelection(editor) {
var range = getSelectedRange(editor);
editor.autoFormatRange(range.from, range.to);
CodeMirror.commands['goPageUp'](editor);
}

Are global variables required for some types of DOM manipulation with javascript?

In the example code below, I want to know why the variable called child must be global ( no var) in order for the code to work. I also want to know if the code below is considered bad practice due to having a global variable and how a better practiced rendition of the code below might look. Thanks.
<!DOCTYPE html>
<meta charset="UTF-8">
<title>dom</title>
<div class="product">
<h2> Product Name </h2>
<img src="pic.jpg" />
<p> Description </p> </div>
<script>
var products = document.getElementsByClassName("product"),
child; // how come var breaks the code ?
for ( i = 0; i < products.length; i++) {
child = products[i].firstChild;
while (child.nodeType !== 1) {
child = child.nextSibling;
}
console.log(child);
}
</script>
You already have a var, as there is a comma before child. Hence adding var would give you
var product, var child
which is illegal.
child is not global, because the var in
var product, child
applies to the whole list of variables following var. (Well, child is global anyway since it is not nested in a function. But that does not have to do with var or not var.)
If you insist on having var twice, write
var product = ... ;
var child;