binding event listeners to ES6 objects [duplicate] - dom

Do getElementsByClassName (and similar functions like getElementsByTagName and querySelectorAll) work the same as getElementById or do they return an array of elements?
The reason I ask is because I am trying to change the style of all elements using getElementsByClassName. See below.
//doesn't work
document.getElementsByClassName('myElement').style.size = '100px';
//works
document.getElementById('myIdElement').style.size = '100px';

Your getElementById code works since IDs have to be unique and thus the function always returns exactly one element (or null if none was found).
However, the methods
getElementsByClassName,
getElementsByName,
getElementsByTagName, and
getElementsByTagNameNS
return an iterable collection of elements.
The method names provide the hint: getElement implies singular, whereas getElements implies plural.
The method querySelector also returns a single element, and querySelectorAll returns an iterable collection.
The iterable collection can either be a NodeList or an HTMLCollection.
getElementsByName and querySelectorAll are both specified to return a NodeList; the other getElementsBy* methods are specified to return an HTMLCollection, but please note that some browser versions implement this differently.
Both of these collection types don’t offer the same properties that Elements, Nodes, or similar types offer; that’s why reading style off of document.getElements…(…) fails.
In other words: a NodeList or an HTMLCollection doesn’t have a style; only an Element has a style.
These “array-like” collections are lists that contain zero or more elements, which you need to iterate over, in order to access them.
While you can iterate over them similarly to an array, note that they are different from Arrays.
In modern browsers, you can convert these iterables to a proper Array with Array.from; then you can use forEach and other Array methods, e.g. iteration methods:
Array.from(document.getElementsByClassName("myElement"))
.forEach((element) => element.style.size = "100px");
In old browsers that don’t support Array.from or the iteration methods, you can still use Array.prototype.slice.call.
Then you can iterate over it like you would with a real array:
var elements = Array.prototype.slice
.call(document.getElementsByClassName("myElement"));
for(var i = 0; i < elements.length; ++i){
elements[i].style.size = "100px";
}
You can also iterate over the NodeList or HTMLCollection itself, but be aware that in most circumstances, these collections are live (MDN docs, DOM spec), i.e. they are updated as the DOM changes.
So if you insert or remove elements as you loop, make sure to not accidentally skip over some elements or create an infinite loop.
MDN documentation should always note if a method returns a live collection or a static one.
For example, a NodeList offers some iteration methods such as forEach in modern browsers:
document.querySelectorAll(".myElement")
.forEach((element) => element.style.size = "100px");
A simple for loop can also be used:
var elements = document.getElementsByClassName("myElement");
for(var i = 0; i < elements.length; ++i){
elements[i].style.size = "100px";
}
Aside: .childNodes yields a live NodeList and .children yields a live HTMLCollection, so these two getters also need to be handled carefully.
There are some libraries like jQuery which make DOM querying a bit shorter and create a layer of abstraction over “one element” and “a collection of elements”:
$(".myElement").css("size", "100px");

You are using a array as an object, the difference between getElementbyId and
getElementsByClassName is that:
getElementbyId will return an Element object or null if no element with the ID is found
getElementsByClassName will return a live HTMLCollection, possibly of length 0 if no matching elements are found
getElementsByClassName
The getElementsByClassName(classNames) method takes a string that
contains an unordered set of unique space-separated tokens
representing classes. When called, the method must return a live
NodeList object containing all the elements in the document that
have all the classes specified in that argument, having obtained the
classes by splitting a string on spaces. If there are no tokens
specified in the argument, then the method must return an empty
NodeList.
https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#getelementsbyclassname
getElementById
The getElementById() method accesses the first element with the specified id.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
in your code the lines:
1- document.getElementsByClassName('myElement').style.size = '100px';
will NOT work as expected, because the getElementByClassName will return an array, and the array will NOT have the style property, you can access each element by iterating through them.
That's why the function getElementById worked for you, this function will return the direct object. Therefore you will be able to access the style property.

ES6 provides Array.from() method, which creates a new Array instance from an array-like or iterable object.
let boxes = document.getElementsByClassName('box');
setTimeout(() => {
Array.from(boxes).forEach(v => v.style.background = 'green');
console.log(Array.from(boxes));
}, 500);
.box {
width: 50px;
height: 50px;
margin: 5px;
background: blue;
display: inline-block;
}
<div class='box'></div>
<div class='box'></div>
<div class='box'></div>
<div class='box'></div>
As you can see inside the code snippet, after using Array.from() function you are then able to manipulate over each element.
The same solution using **`jQuery`**.
$('.box').css({'background':'green'});
.box {
width: 50px;
height: 50px;
margin: 5px;
background: blue;
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='box'></div>
<div class='box'></div>
<div class='box'></div>
<div class='box'></div>

The following description is taken from this page:
The getElementsByClassName() method returns a collection of all elements in the document with the specified class name, as a NodeList object.
The NodeList object represents a collection of nodes. The nodes can be
accessed by index numbers. The index starts at 0.
Tip: You can use the length property of the NodeList object to determine the number of elements with a specified class name, then you can loop through all elements and extract the info you want.
So, as a parameter getElementsByClassName would accept a class name.
If this is your HTML body:
<div id="first" class="menuItem"></div>
<div id="second" class="menuItem"></div>
<div id="third" class="menuItem"></div>
<div id="footer"></div>
then var menuItems = document.getElementsByClassName('menuItem') would return a collection (not an array) of the 3 upper <div>s, as they match the given class name.
You can then iterate over this nodes (<div>s in this case) collection with:
for (var menuItemIndex = 0 ; menuItemIndex < menuItems.length ; menuItemIndex ++) {
var currentMenuItem = menuItems[menuItemIndex];
// do stuff with currentMenuItem as a node.
}
Please refer to this post for more on differences between elements and nodes.

In Other Words
document.querySelector() selects only the first one element of the specified selector. So it doesn't spit out an array, it's a single value. Similar to document.getElementById() which fetches ID-elements only, since IDs have to be unique.
document.querySelectorAll() selects all elements with the specified selector and returns them in an array. Similar to document.getElementsByClassName() for classes and document.getElementsByTagName() tags only.
Why use querySelector?
It's used merely for the sole purpose of ease and brevity.
Why use getElement/sBy?*
Faster performance.
Why this performance difference?
Both ways of selection has the purpose of creating a NodeList for further use.
querySelectors generates a static NodeList with the selectors thus it must be first created from scratch.
getElement/sBy* immediately adapts the existing live NodeList of the current DOM.
So, when to use which method it's up to you/your project/your device.
Infos
Demo of all methods
NodeList Documentation
Performance Test

You could get a single element by running
document.querySelector('.myElement').style.size = '100px';
but it's going to work for the first element with class .myElement.
If you would like apply this for all elements with the class I suggest you to use
document.querySelectorAll('.myElement').forEach(function(element) {
element.style.size = '100px';
});

It returns Array-like list.
You make that an Array as example
var el = getElementsByClassName("elem");
el = Array.prototype.slice.call(el); //this line
el[0].appendChild(otherElem);

/*
* To hide all elements with the same class,
* use looping to reach each element with that class.
* In this case, looping is done recursively
*/
const hideAll = (className, i=0) => {
if(!document.getElementsByClassName(className)[i]){ //exits the loop when element of that id does not exist
return;
}
document.getElementsByClassName(className)[i].style.visibility = 'hidden'; //hide element
return hideAll(className, i+1) //loop for the next element
}
hideAll('appBanner') //the function call requires the class name

With any browser supporting ES5+ (any browser basically above IE8) you can use the Array.prototype.forEach method.
Array.prototype.forEach.call(document.getElementsByClassName('answer'), function(el) {
el.style.color= 'red';
});
caniuse source

So I was told that this is a duplicate from my question and I should delete mine, which I will do so I can keep the forum clean and keep the right to make questions.
As I think mine and this question are really different I will point out the answer to mine, so I will complete the knowledge in this page and the information will not be lost.
Question
I have a code in the snippet that has a document.getElementsByClassName("close")[0], what the [0] is doing?
I never seen a square brackets being used in getElementsByClassName for what purpose is it used for?
Also, how can I convert it to jQuery?
Answer
The code in the snippet has a [0] it is actually being used as a array and as it is a 0 it is referring to the first time the appointed class is being used.
Same thing above.
I couldn't really do it and no one answered it. In the part of the code that is refering to event. target I can not use $("#myModal") instead of document.getElementById("myModal"), I think they should equivalent, but in this case the jQuery form substituting the standard one will not result in the desired effect.
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
var modal = document.getElementById("myModal");
var btn = document.getElementById("myBtn");
var span = document.getElementsByClassName("close")[0];
btn.onclick = function() {
modal.style.display = "block";
}
span.onclick = function() {
modal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
body {font-family: Arial, Helvetica, sans-serif;}
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
color: #aaaaaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
<h2>Modal </h2>
<button id="myBtn">Open Modal</button>
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<p>Some text in the Modal..</p>
</div>
</div>
update
It seems I can't really delete mine question and people are unsatisfied with it, I really don't know what I should do.

Super old school solution:
[].forEach.call(document.getElementsByClassName('myClass'), function (el) {
el.style.size = '100px';
});

An answer for Drenzii's specific case...
You could make a function that will work for any of the word elements and pass in the number of the one you want to transform, like:
// Binds `wordButtons` to an (array-like) HTMLCollection of buttons
const wordButtons = document.getElementsByClassName("word");
// Applies the `slantWord` function to the first word button
slantWord(1);
// Defines the `slantWord` function
function slantWord(wordNumber) {
const index = wordNumber - 1; // Collection index is zero-based
wordButtons[index].style.transform = "rotate(7deg)"; // Transforms the specified button
}
<div class="wordGameContainer">
<button class="word word1">WORD 1</button>
<button class="word word2">WORD 2</button>
<button class="word word3">WORD 3</button>
<button class="word word4">WORD 4</button>
</div>
<div>
<button onclick="moveWord()" class="playButton">PLAY</button>
</div>

Related

How to Get Group count in AgGrid

I have set rowgroup:true for one of the column in Aggrid.
I want to display number of groups it created and display it in the footer.
How can I do that?
Unfortunately, the ag-grid API does not provide any methods that can allow you to calculate the number of row groups within your group. However, this can be easily counted using Vanilla JavaScript, with the use of JavaScript's Set. First, we get the row nodes data from the grid itself, then we construct a Set that consists of that specific property (in this example, we assume that the row group property is 'country'). Since we can only add unique values to a Set, we can be sure that the size of the Set is the row group count.
countCountries() {
const rowData = [];
const countrySet = new Set();
this.gridApi.forEachNode(node => rowData.push(node.data));
rowData.map(obj => {
if (obj) {
countrySet.add(obj.country);
}
});
console.log(countrySet.size);
return countrySet.size;
}
Then, we can simply add the count to the footer by implementing pinned rows. You will have to specify the input property binding for pinnedBottomRowData, as well as the event binding for firstDataRendered, such that the country count will be calculated only after the data is rendered.
<ag-grid-angular
#agGrid
style="width: 100%; height: 420px;"
id="myGrid"
class="ag-theme-balham"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[animateRows]="true"
[enableRangeSelection]="true"
[rowData]="rowData"
[pinnedBottomRowData]="pinnedBottomRowData"
(firstDataRendered)="onFirstDataRendered($event)"
(gridReady)="onGridReady($event)"
></ag-grid-angular>
And on your component.ts itself,
onFirstDataRendered(event) {
this.pinnedBottomRowData = this.createData();
}
createData() {
const result = [];
result.push({
country:`count:${this.countCountries()}`,
});
return result;
}
I have created a demo for your reference. This should nudge you in the right direction.

Protractor scrollleft but class have multiple instance

I have an element with class="objbox" but this attribute have multiple instances.
The current code that I use for scrolling is browser.executeScript('$(".objbox").scrollLeft(' + strPixels + ')'); but since there are multiple instances, it seems like it is getting the first instance and scroll was not successfully done to the target element.
I am wondering if it is possible to include the parent element on my code, or if there is a different work around.
<div class="dhxgrid2-wrapper">
<div class="dhtmlxgrid-container gridbox">
<div class="objbox">
...
</div>
</div>
</div>
It's possible.
What you need to do is the following
// Define the elementfinder of your parent, pick option A or B
const elementFinderWithParentA = $('.dhtmlxgrid-container .objbox');
// Or
const elementFinderWithParentB = $('.dhtmlxgrid-container').$('.objbox');
// The amount to scroll
const scrollLeft = 50;
browser.executeScript('arguments[0].scrollLeft = arguments[1];', elementFinderWithParentA, scrollLeft);
// Or making it more readable, make a function for the scrolling
// and pass it to the browser.executeScript
function scrollToLeft(element, scrollAmount) {
element.scrollLeft = scrollAmount;
}
browser.executeScript(scrollToLeft, elementFinderWithParentA, scrollLeft);
Hope it helps

Setting the class of a particular element permanently from the group of elements created using foreach binding in knockout js

I just started using knockout js. I am creating group of images using foreach binding of knockout js. Initially all the images have same css class "imageUnvisited" and I am trying to change and set the class(permanently) of the particular image that I click to show the visited state. I am successful in changing the class on click event but as soon as I click on another image, the new added class of the previously clicked image gets remove. I am new it the IT filed so pardon me if you find anything wrong, please help.
Below is the code that I am using:
var vm = {
item: jsonData.items,
clickedImageIndex: ko.observable('')
}
ko.applyBindings(vm);
function getVisitedClass(data, index) {
if (index() == vm.clickedImageIndex()) {
return "imageVisited"
}
}
function imageClicked(data, e) {
var itemTarget = e.target || e.srcElement;
index = ko.contextFor(itemTarget).$index();
vm.clickedImageIndex(index);
}
.imageUnvisited {
border: solid 1px green;
}
.imageVisited {
border: solid 1px black;
}
<div data-bind="foreach: item" id="image_gallery">
<div id="image_wrapper">
<image data-bind="attr: { id: 'image' + $index(), src: $data.Src, class: getVisitedClass($data, $index)},click: imageClicked" class="imageUnvisited" role="button"></image>
</div>
</div>
Why your current code doesn't work:
You're storing one index in clickedImageIndex which is updated on every click. Therefore, the expression index() == vm.clickedImageIndex() can only be true for one image at a time.
A "quick fix":
Instead of storing an index in clickedImageIndex, you can store multiple inside an object.
In your vm:
clickedImageIndex: ko.observable({})
In your handler:
var clicked = vm.clickedImageIndex();
clicked[index] = true;
vm.clickedImageIndex(clicked);
In your getVisitedClass:
if (vm.clickedImageIndex()[index()]) {
return "imageVisited"
}
A better fix:
It's probably a good idea to follow knockouts MVVM architecture a bit stricter... This means:
Map your images to ImageViewModel instances
Add a clicked observable property to ImageViewModel
Add a ImageViewModel.prototype.onClick method that sets this.clicked(true)
Use the data-bind="click: onClick, css: { 'imageVisited': clicked }" data-bind to update state.

ng-class not evaluating the expression

I'm trying to implement ng-class in my angularJs application but for some reason expression in the ng-class is not getting applied, any thoughts?
<span>{{prod.item.count}}</span>
<div ng-class="{'show-error-box' : prod.item.length< 1}" class="hide-error-box">
prod is my controller alias in the view and item is the scope object in my controller
I want the class 'show-error-box' when the number of items is less than 1 otherwise apply the class hide-error-box. As I don't have any test data to test this with 0 length i'm replacing the expression with ng-class="{'show-error-box' : 0< 1}" in which case show-error-box should get applied, but it is not happening.
CSS:
.hide-error-box {
display:none;
}
.show-error-box {
float: left;
width: 100%;
background: #fff;
text-align: center;
padding: 10% 0;
height: 700px;
position: absolute;
}
ng-class does not serve as a replacement to "class". It will add any classes whose name is a key in your object if the associated value is true.
The only two options this div will have as a result are:
class="hide-error-box"
or
class="hide-error-box show-error-box"
To acheive the desired effect you would could put the opposite condition as a value of your hide class.
ng-class="{hide-error-box: prod.item.length >= 1, show-error-box: prod.item.length < 1}"
However, it is often recommended that you keep your templates as free from logic as possible. It may be worth considering placing this in a controller function:
HTML
<div class="{{getSizeClass()}}">
JS
this.getSizeClass = function() {
if (item.length >= 1) {
return 'hide-error-box'
}
return 'show-error-box'
}

How to: Fixed Table Header with ONE table (no jQuery)

I know, there are at least 3 dozen questions like this on stackoverflow and still, I could not make this happen:
A simple table where thead is sticked/fixed at the top, and the tbody is scrolled.
I tried so much in the past days and now I ended up here crying for help.
A solution should work in IE8+ and newest FF, Chrome & Safari.
The difference to other "possible duplicates like this one is that I don't want to use two nested tables or jQuery (plain javascript is fine though).
Demo of what I want:
http://www.imaputz.com/cssStuff/bigFourVersion.html.
Problem is it doesn't work in IE, and I would be fine to use some JS.
Ok i got it:
You need to wrap the table in two DIVs:
<div class="outerDIV">
<div class="innerDIV">
<table></table>
</div>
</div>
The CSS for the DIVs is this:
.outerDIV {
position: relative;
padding-top: 20px; //height of your thead
}
.innerDIV {
overflow-y: auto;
height: 200px; //the actual scrolling container
}
The reason is, that you basically make the inner DIV scrollable, and pull the THEAD out of it by sticking it to the outer DIV.
Now stick the thead to the outerDIV by giving it
table thead {
display: block;
position: absolute;
top: 0;
left: 0;
}
The tbody needs to have display: block as well.
Now you'll notice that the scrolling works, but the widths are completely messep up. That's were Javascript comes in.
You can choose on your own how you want to assign it. I for myself gave the TH's in the table fixed widths and built a simple script which takes the width and assigns them to the first TD-row in the tbody.
Something like this should work:
function scrollingTableSetThWidth(tableId)
{
var table = document.getElementById(tableId);
ths = table.getElementsByTagName('th');
tds = table.getElementsByTagName('td');
if(ths.length > 0) {
for(i=0; i < ths.length; i++) {
tds[i].style.width = getCurrentComputedStyle(ths[i], 'width');
}
}
}
function getCurrentComputedStyle(element, attribute)
{
var attributeValue;
if (window.getComputedStyle)
{ // class A browsers
var styledeclaration = document.defaultView.getComputedStyle(element, null);
attributeValue = styledeclaration.getPropertyValue(attribute);
} else if (element.currentStyle) { // IE
attributeValue = element.currentStyle[vclToCamelCases(attribute)];
}
return attributeValue;
}
With jQuery of course this would be a lot easier but for now i was not allowed to use a third party library for this project.
Maybe we should change a method to archieve this goal.Such as:
<div><ul><li>1</li><li>2</li></ul></div> //make it fixed
<table>
<thead>
<tr><th>1</th><th>2</th></tr>
</thead>
<tfoot></tfoot>
<tbody></tbody>
</table>
Of course, this is not good to sematic.But it is the simplest way without js or jq.
Don't you think so?