Map DOM element to memory object in Angular 6 - javascript

I'm building a basic grid component with Angular 6. The grid itself is a <table>, with a <thead> for the header and a <tbody> section, where there are multiple rows. Each row has a fixed first column (which has de ID of the row), a dynamic number of 'positions' and a fixed last column (which holds an action combo button). Each of the positions contain a list of objects, dragged from an external tree component. All the rows, the positions and the list of objects inside them are generated from a DB, and displayed using *ngFor in the component template.
To make it clear, here's an schema of the grid:
The grid is built in this way:
<table #mainGrid>
<thead>
[...]
</thead>
<tbody (contextmenu)="showContextMenu($event)">
<tr *ngFor="let row of objectGrid; let i=index">
<td>{{ i }}</td>
<td *ngFor="let position of row.positions">
<ul class="object--list">
<li class="object--list--item" *ngFor="let object of position.objects">
{{ object.name}}
</li>
</ul>
</td>
<td><!-- CODE FOR THE ACTION BUTTON --></td>
</tr>
</tbody>
</table>
Each object has several actions associated to it (f.i. 'Delete', 'Locate in tree', 'Edit', etc) that must be chosen using the right button of the mouse. I'd like to avoid attaching an event handler to each object because sometimes the grid will contain more than 700 objects, so my idea is to attach only 1 contextmenu handler to the <tbody> (to disable right clicking on the table header) and use logic to find out the object where the user right clicked.
So far, I can use the event path property to identify the row, cell and the object (li element) where the right click occured. But now I'd like to know an elegant way of associating univocally the clicked li element to the position of the array that generated it.
For example, if I right click on the object 'Object 1.4.2', I'd like to get the reference to this.objectGrid[0].positions[3].objects[1].
Any suggestions? Thanks!

I couldn't find a safe way of doing it, so I've ended up adding the event handler to each of the objects that need it. So far, the performance for a grid with 500+ elements is pretty good, but I'm studying how to implement a virtualization to the grid component to load only the rows displayed on screen.

Related

Duplicate already rendered Vue.JS component

I have the following Vue.JS component definition:
<table>
<tr v-for="n in lines">
<td>in stock</td>
</tr>
<tr><td><button #click="lines++">+</button></td></tr>
</table>
Which looks like like this:
And it allows the user to edit each cell and press the "+" button to add more rows. I want to duplicate the whole component, all its content and functionality included so that I can move it to a different part of the page and still be able to use the duplicated component and the original. Like this:
The problem is that clicking on the "+" button of the duplicated table (the one on the left) will no longer work because it is a mere HTML clone.
How can I "deep-copy" the whole component so that it remains usable?
Note that the table can't be just wrapped into a v-for because the user is meant to clone more complex component structures like this.

How to trigger event when an object is rearranged?

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.

myGrid.setStore in nested div

I am trying to develop a ersi map javascript page.
I am creating
var myData = { items: myItems };
var myStore = new dojo.data.ItemFileReadStore({ data: myData });
These create correctly and have the correct data in
I have a problem updating a dojox.grid.DataGrid with the myGrid.setStore(myStore);
The code I have for the grid is:
<table dojotype="dojox.grid.DataGrid" jsid="myGrid" noDataMessage="No results found in the current map extent" style="width:100%;height:100%;" selectionMode="none">
<thead>
<tr>
<th field="NAME" width=75%>Neighborhood</th>
<th field="ownPct" width=25%>% Own</th>
</tr>
</thead>
</table>
This table in nested in a number of divs in the HTML.
I suspect that when I run myGrid.setStore(myStore); it cannot find the table in the DOM.
If I move the table to be out of any DIV's and have it directly after the <body> tag (see link). the code will work and populate the table. If I move the table to the end of the HTML just before the </body> tag it will work (see link). But when I put the table in the nested Divs then it will not populate (see link). this should be on the policy tab!!
I hope i make sense, this has been driving me crazy
thanks in advance Paul
I think the problem is not with setStore, but one or two other things.
When the DataGrid is instantiated, it attempts to detect the space available (to determine how many rows to render etc). You've told it to be 100% of its parent div, but remember that divs have height 0px by default!
So, you can try to give the grid (or gridDiv) an explicit height. However, the grid is also in an inactive tab so it still detects it has no height (it's invisible, after all!).
So, next you can try to set the grid's tab as the active one (i.e. <div class="tab-pane active" id="policies">). That should give you a rendered grid when the page loads.
Alas, you don't want the grid's tab to be the active one. So the final solution is that you need to explicitly tell the grid to recalculate its height (myGrid.resize()) when the policy tab is being activated. I haven't used bootstrap's tabs, so I don't know exactly how to do that - perhaps you do?

Info panel below TRs

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 */
}

Dynamic layout in JS/CSS/HTML

In my application users define documents layouts.
These layouts are logically tables most of the time, so users specify which "property" is going to be displayed in which cell. They also can define a number of rows and columns as well as how many rows or columns a property will take (collspan and rowspan in HTML terminology).
Now for a given document layout, and a set of documents I need to display it in browser.
I would like to do it on the client side, possibly using jQuery or/and Knockout.js, or some other framework/library.
Before I start reinventing the wheel, can someone point me in the right direction of doing it?
Not sure if this really counts as a question. It's quite open to interpretation but here goes.
Define your LayoutViewmodel with a set of multi-dimensional observableArrays. Each index in the multidimentional array corresponds to a Property object which holds the data about rowspan, colspan etc (not sure what data you need).
Display you layout with foreach bindings. You most likely have two of these, one for selection and one for display. Below is my attempt at the display one.
<table data-bind="foreach: rows">
<tr data-bind="foreach: columns">
<td data-bind="attr: { colspan: colspan, rowspan: rowspan }, text: name">
</td>
</tr>
</table>
Hope this helps.

Categories

Resources