In a website (not mine) there's a table containing values. I'm making a somewhat add-on to this site. I have a jQuery script that scrapes the values of that table,then adds a column to that table and calculates a certain value for each row. These values change when the rows on the table gets rearranged (the user can drag and drop the rows around), I want to know how to detect that event so I'll know when to recalculate values.
The way I do this is I have an object x that contains several <tr> with ids.
For example: x = [ <tr id="1">, <tr id="2">, <tr id="3">]
I need to trigger an event when the elements of x gets rearranged, for example if x becomes [ <tr id="2">, <tr id="1">, <tr id="3">]
Make a copy of the object at the start. Every time any function activates that could affect the object, check to see if each element in the object is equal to the element in the copied object. If it is, keep going. If not, do whatever you need to, then assign the current object with the new positions to the copy variable you made at the beginning, and keep going.
Related
Js student here.
I have made an example CODEPEN.
In this test case,as you can see in the above code on the right side i have 6 hardcoded html inputs,3 for first name and 3 for last name.
On the left side i have a <button> which is called ADD,and with every click of this button (with a maximum of 3 clicks) the function addInputs() generates a <li>element inside <ol id="originContainer"></ol>.
This generated <li> element contains 2 html inputs,one for the first name and one for the last name,every generated input gets a unique id using a counter variable (var inputscounter=0;).
The goal is to copy dynamically,whatever i type in the generated inputs -to the left- to the hardcoded ones -to the right-,first name to first name,last name to last name,all that by using the function changeValues() which runs with every keyup of the generated inputs.
My problem is,the function changeValues() works fine but only after i generate all 3 of the elements ( As its visualized in THIS picture ) on the left,but not with just one of them,or 2 ( like THIS )
What am i missing ?
When you try to get the value of an element that is not created yet, the function throws an error. That is, in your changeValue function where you try to get the value attribute on FnameX when it is undefined (since it can't find the element in the DOM). Same for adding the click handlers on undefined elements.
You could fix that by either checking if Fname1 etc are defined before you try to get their value.
A nicer approach would be to store in an array which elements you have generated so far. Then loop over those elements and transfer its values.
You can either store an array of ids you can use to find an element when you don't need to access them that often. In this case I would store the objects themselves.
Also you don't have to add click handlers on every element, every time you add one.
Is there a way to use angular.element(...).on('click', onTdClick); (for example) in a way that executes onTdClick (providing the element to it) on every that gets clicked?
Let's say I have 2 tables, both have cells and columns.
I want to be able to click on CELLS and send the element of what I clicked and send it to onTdClick($event).
$scope.onTdClick = function(ev){
window.getSelection().selectAllChildren(ev.target);
};
Essentially doing the same as: ng-click="onTdClick($event)" without having to put ng-click on hundreds of <td>'s
I dynamically add table rows and table cells with .insertRow and .insertCell
https://www.w3schools.com/jsref/met_table_insertrow.asp
So statically doing angular.element().find('td').on() wont work well here.
Whats my end result?
I'm attempting to make it so I can click <td>'s to essentially highlight cells by just clicking them.
The highlight code is already tested and works:
window.getSelection().selectAllChildren(ev.target);
(where ev is the td element)
You shouldn't use insertRow with AngularJS. Instead, you should use ng-repeat with an array that has an object for every row, and just push objects when you want to insert a row.
$scope.insertRow = function(){
$scope.tdList.push({});//push whatever you want
}
If you want the number of cells to be variable, you can store some param in this object to tell the view how many cells the row has, and put a nested ng-repeat inside to create the cells
I have a table, and I'd like to add a hideable/showable panel below each row for more controls and info than can reasonably fit in the table row. My first thought was to have a sibling tr for each original tr, and put a single td inside with an appropriate colspan:
<tbody>
<tr>
<td>...</td>
...
<tr>
<tr class="tablesorter-childRow">
<td colspan="4">...</td>
</tr>
...
</tbody>
Each original row would have a button that would hide() or show() the corresponding tr, and the td in the child row would have all the extra controls that don't need to be seen normally.
This gets tricky because I'm using d3 to build the table, and d3 doesn't like multiple elements per datum (see this stack post and this other thing).
It's also tricky because I'm using tablesorter, which sorts the table client-side using the values in tds, so the original data has to stay in table format. (It could keep pairs of rows together using a css class "tablesorter-childRow".) I also don't believe I can have multiple tbodies because they aren't sorted along with rows -- each tbody's rows are sorted.
I thought about using jquery afterwards to insert a tr after each original tr, but then d3 won't update the table properly when something changes (since the data won't join properly), and the reason I'm using d3 is because it makes building lots of dom elements easier (for me at least).
So, question time: how else can I create this panel that
moves with the original table rows
doesn't affect sorting
can be hidden or shown?
If you want two sibling elements to share the same data, the easiest way in d3 is to group them under a parent element. You assign the parent element the data, and then when you create its two child elements (without assigning data) they both inherit the parent's data.
In SVG, the usual parent element is a <g>. For your purpose, the natural parent element would be <tbody> which can be used to group table rows. However, you'd have to modify the table sorting code that you're using, to sort individual <tbody> elements instead of individual rows.
The only other option would be to dynamically set the content of the info row and insert it in the correct place every time you want to show it, similar to how many tooltip examples work: it's the same tooltip, just moved around and with new data. If you're using d3 to attach the event handler on the table rows, it will pass the data object of the clicked-on row to the event handling function, so you can use that data to fill the information content without creating a data-join. To insert the info row after the clicked <tr> element, you could use d3's insert() function, but the format isn't ideal; better to use plain Javascript or JQuery. You'd also have to remove the info row before running your sort.
tableRows.on("click", showInfo);
/* remember to give your rows a tabIndex, so that keyboard users can
trigger the click action */
/* Create the persistent info box */
var infoRow = d3.select( document.createElement("tr") )
//create a new <tr>, unattached to the document
.attr("class", "infoBox";//set class to allow unique formatting
infoRow.append("td") //add a <td> within the <tr>
.attr("colspan", colNames.length); //set to fill all columns
/* Show the info row for a clicked element */
function showInfo(d,i) {
/* Hide info box if currently shown */
infoRow.style("display", "none");
/* Set content to match clicked row */
infoRow.select("td") //select the <td> element
.html(/* create html from the d object
you don't need a function(d), just
concatenate the string. */)
/* Insert the row in the correct place.
This will automatically remove it from any current location */
this.parentNode.insertBefore(infoRow, this.nextSibling);
//"this" is the tableRow object that received the click event
infoRow.style("display", null);
//revert to default display setting (i.e. table-row)
}
function sort(/*parameters*/) {
infoRow.remove();
//remove the infoRow element from the document,
//so it only exists as a Javascript object again
/* Run your sort code */
}
I'm using Django and Zurb-foundation.
On a certain page I have on the one hand a bunch of items organized in a table, displaying their properties. On the other hand, on the same page I have a form which contains a select box, the elements in which are the previously mentioned items. Now in principle as the number of items grows larger it will get boring finding one in the form's select box. I would like to, if you click on one of those items being displayed on the table, have that same item be selected in the select box.
How do I do this? I know absolutely nothing about javascript, but if you can at least mention the relevant key concepts, I'll learn them.
Thanks!
Please check out this JSFiddle: http://jsfiddle.net/zvCvL/5/
Although I have left several comments detailing how this should be done, I will summarize below:
Loop through each of the items and their descriptions using Django's templating engine's looping feature (this part is going to be left up to you to figure out, but I have left you a link in the fiddle to get you started) For example, you will be adding these to your tbody in each of your loops:
<!-- loop iteration one -->
<tr>
<td class="item1">Item 1</td>
<td class="item1">The first item</td>
</tr>
Do not forget, however, that my method requires that a unique name be given to the td's in every iteration.
Then, you can work on the dropdown. This will require a simplier loop, just adding these to the ul which acts as your dropdown:
<!-- loop iteration one -->
<li class="item1"><a>Item 1</a></li>
Once again, this requires a unique class name, yet one that corresponds to the class name given to the item in table.
Finally, you have your JavaScript to take care of. Simply, you can check to see if the user is hovering over an li, check which class it belongs to, and apply some sort of color to all elements which have that same class. Of course, one caveat is that this will act upon all list elements. I'll leave it up to you to change that as needed.
Hopefully this helps! (The ultimate result is: http://jsfiddle.net/zvCvL/5/embedded/result/)
Is there a way to create custom javascript-events, that trigger once elements get a class? I want to watch all elements inside a table and process some data once one specific class gets added to them.
Is that generally possible, and if yes, how?
edit
I have a table with 10 rows and 10 columns. Once a player clicks one of those table cell (<td>-tags) there are some calculations made and the cell gets a value (a number, representing the number of mines on the surrounding fields) or pops up to be a mine. (When it has a value its class becomes .opened and when it has a mine its class becomes .mine)
Now every time the player clicks on a cell, that has no surrounding mines, its value is 0. At this time every surrounding field should be checked to see if it is also a 0. If it is, it should be unveiled (class becomes .opened). Now every surrounding field of this newly opened should be checked again, and again, and again - As long as none of the surrounding fields is 0 anymore.
I thought it would be the easiest way to achieve that, by simply checking for the .opened-class on the cell, to trigger the "chain reaction".
It is possible using Mutation Events API or DOM Mutation Observers API. Read this article
document.addEventListener("DOMAttrModified", function(e) {
console.log(e.attrName, e.attrChange, e.prevValue, e.newValue, e.relatedNode)
}, false);