How to get attribute values from html in angular 4 - javascript

I am working on angular 4 project, I had a requirement to drag item, So I was using ng2-dragula plugin to do it for me. Now I need to extract data-id attribute from each row after drop to specific position.
dragulaService.drop.subscribe(
(args) =>{
const [bagName, elSource, bagTarget, bagSource, elTarget] = args;
console.log('after', $(bagSource)); // element after the inserted element
});
In $bagSource variable I will get each rows in new order. Say it be
<tr attributes data-id="23"><tr>
<tr attributes data-id="2"><tr>
<tr attributes data-id="32"><tr>
How could I extract data-id from each tr fields. I need to get each id in their new order in an array.
My required result should be [23,2,32];
jquery $(element).attr("data-id") equivalent in angular 4.

Something similiar to jquery $(element).attr("data-id") can be achieved with #ViewChild and some old-fashioned javascript. It would look like:
Use this only when drag zone and table is not related (if it is use #user3263307 answer it's more Angular-friendly). However the best option would be make use of [attr.data-id]="someVariable", but since I don't know your app structure can't help you with implementing this method.
.*html File
<table #tableRef>
<tr attributes [attr.data-id]="23"><tr>
<tr attributes [attr.data-id]="2"><tr>
<tr attributes [attr.data-id]="32"><tr>
</table>
Simply #tableRef is something like template variable which angular will bind with #ViewChild in component file.
*.component.ts
#ViewChild() tableRef: ElementRef;
public getIds() {
if (!this.tableRef|| !this.tableRef.nativeElement)
return;
//assuming that table would have only one `tbody` tag.
let tableBody = this.tableRef.tBodies[0];
if (!tableBody)
return;
// we need to use Array.from, because `.children` returns HTMLCollection<Element>
// type which is not handy to iterate through it.
let childs: Array<HTMLTableSectionElement> = Array.from(tableBody.nativeElement.children);
let arrIds = [];
for(let child of childs) {
if (!(child.nodeName && child.nodeName.toLowerCase() == "tr"))
continue;
arrIds.push(child.dataset.id);
}
}
Please note that it's a bit brutal method for angular since it's best not to interfere into HTML DOM structure. However as long we are only getting data from elements everything should be fine.
Please note that too about using #ViewChild.
Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you can take a look at Renderer2 which provides API that can safely be used even when direct access to native elements is not supported.
Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.
From Angular docs link

In angular attributes should be defined like this:-
<tr attributes [attr.data-id]="23"><tr>
And attribute can be accessed using dataset property of Eventtarget object:-
dragulaService.drop.subscribe(
(args) =>{
const [bagName, elSource, bagTarget, bagSource, elTarget] = args;
console.log('id is:', elTarget.dataset.id);
});

Related

Reverse Dom traversing

I started to learn JavaScript and I am creating a navigation component in svelte and needed to add some sort of CSS visualization when there is a dropdown multiple levels deep. so I can see which parents nodes are connected with my "current-item" CSS class.
the visualization I use for this project is a color.
I came up with this logic:
https://codepen.io/Digi4Care/pen/YzaJbdW
The logic I use to determine when I reach the beginning of the node:
"LI" == node?.parentNode?.parentNode?.parentNode?.nodeName
I am wondering if there is another and more efficient way to do this?
Since you're using Svelte - there's the <svelte:self> element to create such a structure recursively. This is the tutorial lesson.
When passing the file object instead of just the name to the File component, the currentFile can then be saved to a store on click. (How do you set the current-menu-item? Based on the url) This value can be used inside the Folder component to set a conditional class, if it's an ancestor.
function isAncestor(files, cF) {
return files.includes(cF) || files.some(file => file.files && isAncestor(file.files, cF))
}
class:current-ancestor={isAncestor(files, $currentFile)}
Here's a REPL with the complete example

Does a backdraft component have any visibility into the dom element where it's being inserted?

Using backdraftjs I want to do a customized fancy select widget similar to chosen.js.
My thought was that I could have markup like this:
<div id="bobSelect">
<select name="bob">
<option>Bob Crane</option>
<option>Bob Hope</option>
<option>Bob Melvin</option>
<option>Bob Dobbs</option>
</select>
</div>
Then I'd replace it via something like
render(FancySelectComponent, {}, 'bobSelect', 'replace');
...and in FancySelectComponent I would grab things like the options from bobSelect.
However it looks like there's no way for FancySelectComponent to have visibility into the dom element where it will eventually be inserted. Is that right?
So instead maybe in render I need to do something like
render(FancySelectComponent, {basedOn: 'bobSelect'}, 'bobSelect', 'replace');
Does that make sense?
You are correct; as written,
render(FancySelectComponent, {}, 'bobSelect', 'replace');
FancySelectComponent's constructor is not provided any information about the existing DOM tree rooted at div#bobSelect. In fact, the render application above is just syntax sugar for...
let result = new FancySelectComponent({});
let replacedNode = document.getElementById('bobSelect');
let parentNode = replacedNode.parentNode;
parentNode.removeChild(replacedNode);
parentNode.appendChild(result.render());
return result;
(note: in your example, both the replaced and replacing node are single nodes, but render can handle cases when either or both are a forrest of nodes)
You are also correct that, if you want access to the existing div#bobSelect, then you can pass it directly to the constructor via the second argument to render:
render(
FancySelectComponent,
{replacingDiv:document.getElementById('bobSelect')},
'bobSelect',
'replace'
);

Display react component name in attribute

While working with React, i would like to display component name in an attribute of the component. E.g. if I have a component <LoginBox /> I would like it to be rendered as
<div data-react-name="LoginBox">...</div>
But I want this to be done automatically for each transpiled component. Reason for this is automated testing when I'd check for rendered elements in HTML/DOM, currently a component is not differentiated by the name in rendered HTML.
I thought I'd write a babel plugin, but I have no idea what visitors I'd use and how to make it robust enough. I tried google for such a plugin but I have no idea how it would be called and found nothing useful.
So is there any plugin or any way to achieve this?
Thanks
Now after a year, as I'm rethinking, it should be quite easy.
For more details on writing plugins see handbook.
Use ASTexplorer to inspect what AST would your code result in. And then, for generated tree, prepare visitors. So e.g. with code:
<div><Custom some-prop="prop">Some text</Custom></div>
we would infer, that we need to use visitor JSXOpeningElement and alter node's property attribute. To this property - array we would add a new element that we would create by Babel.types.jsxAttribute(name, value). We will get the name of tag from node's property .name.name (the name string is nested inside name object). We also need to use appropriate types. So it would look like this:
module.exports = function(Babel) {
return {
visitor: {
JSXOpeningElement(path) {
const name = Babel.types.jsxIdentifier('data-testname');
const value = Babel.types.stringLiteral(path.node.name.name);
path.node.attributes.push(Babel.types.jsxAttribute(name, value));
}
}
};
};
The code is tested with the ASTExplorer.

Update dom after component rendered the view, best practice in Angular2?

I'm seeking some advice how I should handle elements when working with Angular2.
I have stored some elements id's in the localstorage, and want to set a selected class name on some specific elements.
For now I'm using this way:
ngAfterViewChecked() {
// Check if seats has been selected before
var selectedSeats: elementInterface = JSON.parse(localStorage.getItem('SelectedSeats'));
if (selectedSeats != null) {
var outboundElement = document.getElementById(selectedSeats.outboundSelectedElement);
var returnElement = document.getElementById(selectedSeats.returnSelectedElement);
this.autoSelectSeats(outboundElement);
this.autoSelectSeats(returnElement);
}
}
Method:
private autoSelectSeats(element: Element) {
// Set selected class on element
element.classList.add('selected');
}
I see two issues here. The first is the ngAfterViewChecked hook that fires more than once after the view is created. Is there something I can do so it only fires once?
Second: Is there a better way to get the element when you know the id and set a class attribute on it after the content has loaded?
I can't seem to find the Angular2 way of doing it, besides using this hook.
Any idea? Also, please don't post Jquery posts, as I don't want to implement that when using Angular :)
How about adding a custom directive to each of your "seat" elements and let that directive add the CSS class?
In your template, the directive would be used as follows (I'm guessing, since you didn't show your template):
<div *ngFor="let seat of seats" [highlight]="seat.id">
...
</div>
You need to pass some information to the directive to identify which seat it is working on. It seems better to pass an id directly (e.g. seat.id) rather than to rely on HTML ids (although in your case they might be one and the same).
Now the code for the directive:
#Directive({
selector: '[highlight]'
})
export class HighlightDirective {
#Input() highlight: string; // This will contain a seat.id
constructor(el: ElementRef, ss: SeatService) {
const selectedSeats = ss.getSelectedSeats();
// If current seat found in selectedSeats, mark it as selected.
if (selectedSeats.indexOf(this.highlight) !== -1) {
this.el.nativeElement.classList.add('selected');
}
}
}
The reason I'm using an external service SeatService to get the data from localStorage is that Angular will create an instance of HighlightDirective for every match it finds in your template. You don't want to refetch the selected seats in every instance (the service lets you cache the seats and return the same data).
Angular way has pretty good documentation, classes are toggled using the following syntax: [class.selected]="selected"

How to display a list of all names of templates?

In the client, I know you can use Template.[template name] to reference a specific template.
How could you get a list of all custom meteor templates included in your app that have been created by you (not meteor or included from a package)?
Building on what Richard said, you can check if the property on the Template object is a template like this:
var templates = [];
for(var key in Template){
if(Blaze.isTemplate(Template[key])){
templates.push( key );
}
}
console.log( templates );
You'd probably have to use a specific naming convention for identifying your own templates.
Just do Object.keys(Template). I attached a screenshot from the console.
Basically meteor creates a class called Template. We can iterate over all the keys in the template class.

Categories

Resources