How to access element by id in angular 2 - javascript

I have table generated with dynamic ids like this one
<table>
<tbody>
<tr *ngFor="let row of createRange(seats.theatreDimension.rowNum)">
<td id={{row}}_{{seat}} *ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>
</tr>
</tbody>
</table>
I want to access table td element from angular 2 .ts file. Here are functions:
ngOnInit() {
this.getSeats();
}
getSeats() {
this.cinemaService.getAllSeats(this.repertoire.id).subscribe(
data => {
this.seats = data.json();
this.setReservedSeats();
}
)
}
setReservedSeats() {
this.seats.reservedSeats.forEach(seat => {
let cssSeatId = seat.rowNumReserved + "_" + seat.seatNumInRowResereved;
document.getElementById(cssSeatId).className += "reserved";
}
)
}
and after that I want dynamically to set class of every td, but I am getting this console error in my browser:
ERROR TypeError: Cannot read property 'className' of null
Just to note once again that I generate td ids dynamically. They are in form rowNum_cellNum.
I am getting this object from api.
{
"theatreDimension": {
"rowNum": 17,
"seatNumInRow": 20
},
"reservedSeats": [
{
"rowNumReserved": 9,
"seatNumInRowResereved": 19
},
{
"rowNumReserved": 2,
"seatNumInRowResereved": 4
},
{
"rowNumReserved": 15,
"seatNumInRowResereved": 15
}
]
}
I am using theatreDimension to make table. Then I try to make reserved seats from reservedSeats array with red background (reserved)
How I can access td elements from angular 2 and solve this issue?

Instead of accessing the DOM directly, you should try using the ngClass directive to set the class:
<td [ngClass]="{'reserved': isReserved(row, seat)}" id={{row}}_{{seat}} *ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>
You can then implement the isReserved(row, seat) function, and if it returns true, it will apply the reserved class.
isReserved(rowNum: number, seatNum: number) {
return this.seats.reservedSeats.some((r) => r.rowNumReserved === rowNum && r.seatNumInRowResereved === seatNum);
}

To directly answer your question, in order to get the element by ID, you need to do so after the page has been renedered. Use the function ngAfterViewInit(). You will need to remove the call to setReservedSeats() from getSeats().
ngOnInit() {
this.getSeats();
}
ngAfterViewInit(){
this.setReservedSeats();
}
However, I would suggest going a different route. You can set the style of the element based on whether or not the seat has been reserved. Assuming you have some sort of "reserved" flag on the seat object you can do something like this:
<td id={{row}}_{{seat}}
[ng-class]="{'reserved' : seat.researved}"
*ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>

Related

Svelte application bug: converting string to boolean in the view fails

In a Svelte app, I have this array of countries:
let countries = [
{
name:"Alegeria",
status: "1"
},
{
name:"Bulgaria",
status :"0"
}
]
Note the status property is a string. I iterate the array this way:
{#if countries.length > 0}
<table class="table">
<thead>
<tr>
<th>Country</th>
<th class="text-right">Status</th>
</tr>
</thead>
<tbody>
{#each countries as c}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={Boolean(Number(c.status))} /></td>
</tr>
{/each}
</tbody>
</table>
{:else}
<p class="alert alert-danger">No countries found</p>
{/if}
As you can see, I try to convert the value of the status property to a boolean this by using Boolean(Number(c.status)).
Instead of the desired conversion I get the error: Can only bind to an identifier (e.g. foo) or a member expression as the REPL shows.
What am I doing wrong?
As it says in the error, you can only bind to an identifier or member expression - ie a variable.
This is because a bind is a two-way thing, and if you have applied Boolean(Number(()) to it, when someone changes that input, then svelte doesn't know how to undo those functions to 'save' the data back into that variable it's bound to.
If you can't change the status variables to be boolean (better solution, as suggested by other answers), you need to manually do this two-way updating. Drop the bind, just have checked={Boolean(Number(c.status))}, and handle the input's change event to convert from true/false back into "1" or "0", and save that to the status.
Use:
function handleClick(country) {
countries.find(c => c.name == country.name).status = (country.status == "1") ? "0" :"1"
}
and
<Switch checked={Boolean(Number(c.status))} on:change={() => handleClick(c)}/>
See it working in this repl
I think the problem is that the Boolean() function creates a new object, to which you can't bind, because it is never again referenced. You can bind directly to your array of values in countries, using this code:
{#each countries as c, index}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={countries[index].status} /></td>
</tr>
{/each}
What has changed is that you use the index parameter of the #each loop now to bind to the variable of the countries array. Please be aware that in order for this to properly work, you need to change the status values to true or false. Otherwise it will still work, but the initial value will always be true.
If you just want pass the value down to the Switch component, simply remove the bind: like so:
<td class="text-right"><Switch checked={Boolean(Number(c.status))} /></td>
If you want to update the countries model via the switch component, I suggest to forward the click event and use a simple click handler method, something like this:
function onClick(event, country) {
countries = countries.map(c => {
if (c.name === country.name) {
c.status = event.target.checked ? '1' : '0';
}
return c;
})
}
...
<td class="text-right"><Switch checked={c.status === '1'} on:click={(e) => onClick(e, c)}/></td>
full code on REPL: https://svelte.dev/repl/013286229d3847c1895c4977aee234af?version=3.9.1

is there a way to add automatically buttons on each element of a generated list?

I'm working on a web app that lets people schedule lessons and timeslots, and I'm trying to make it so that people can click on the timeslot to accept it. Currently, the table is retrieved from the database and generated with this code:
function updateSchedules(name = "missing", grade = "missing", date = "missing", time = "missing", status = "missing", id) {
let table = document.getElementById('schedules');
let color;
console.log("Output schedule for " + name);
if (status.toLowerCase() === 'pending') {
color = "background-color:rgb(252, 38, 0)";
} else if (status.toLowerCase() === 'confirmed') {
color = 'background-color:rgb(0, 255, 13)';
} else {
color = 'background-color:rgb(35, 64, 153)';
}
table.innerHTML += `
<tr>
<td>${name}</td>
<td>${grade}</td>
<td id=testday2>${date}</td>
<td id=testtime2>${time}</td>
<td style="${color}">${status}</td>
</tr>`;
}
Is there a way to add buttons on each row of the table that refers to each row's unique id without just making buttons and adding event listeners manually for each row?
You have to add one more <td> tag with <button> tag and bind with onlick event and pass unique_id in function parameter like this:
<td><button type="button" onclick="myfunc(${id})"></button></td>
${id} may be string or number it's depend on requirements
Note-:
You have to pass unique id from (backend).
unique_id might be primary key of database row.
Then write script to handle button like-:
<script>
function myfunc(unique_id){
console.log(unique_id);
// do something
}
<script>
More appropriate if you use anchor tag with button like-:
<td><a onclick="myfunc(${id}")><button type="button"></button></a></td>
There are multiple ways to do this same thing. More appropriate if you use this keyword with jquery instead of write pure javascript.

making dynamic condition in typescript

I have a dynamic array that represents columns in a table, and I have an array of rows in the table. I get the arrays as input in my angular 4 components and I don't know anything in the columns before I get them (should work on multiple types of variables).
Each column has a variable that says if it is mandatory or not and according to this I build a condition string that I want to run before I insert a new row to the array of rows or change an existing one.
I'm creating the condition in the ngInit, and after the creation it looks something like this:
"(r.id == null) || (r.name == '')" and so on (there could be && too)
I'm trying to bind the condition to the disabled attribute of a button, but I can't seem to convert it to a real.
How can I convert it to a condition so I could execute it and return true/ false accordingly?
I've already tried doing [disabled]="[conditionVar]", and also tried using.
${condition},
but those didn't work
my code looks something like this:
myComponent.ts:
export class Column {
title: string;
fieldName:string;
mandatory: boolean;
}
#Component({
selector: 'myComponent',
templateUrl: './myComponent.component.html',
styleUrls: ['./myComponent.component.css']
})
export class MyComponentimplements OnInit {
#Input() columns:Column[];
#Input() rows;
conditionString = "";
constructor() { }
ngOnInit() {
for (let c of columns.filter(d=>d.mandatory)) {
this.conditionString += "(r." + c['fieldName'] + " == null) || "; // 'r' is the name of the variable that i want to run the condition on
}
this.conditionString = this.conditionString.substr(0, this.conditionString.length - 4); // to remove the last ||
}
}
myComponent.html:
<table>
...
<tbody>
<tr *ngFor="let r of rows">
<td *ngFor="let c of columns">
<input [(ngModel)]="r.[c.fieldName]">
</td>
<td *ngFor="let c of columns">
<button [disabled]="here I want to bind my condition">save this row</button>
</td>
</tr>
</tbody>
</table>
(the user should edit the rows and if he want he can save the changes)
every thing works but the binding of the condition

How to correctly setup a conditional expression (if) in a React render()

I want to be able to rendering a certain section of HTML if the condition is true. I am curious about the correct way to setup a conditional if expression within a react render().
I looked it up online and found one way to do this with a inline expression to check if the value is true, if so then it will render the remaining element.
I also setup another way to create variables for the html to be rendered.
Question:
I was unable to wrap both td tags as one for the condition. It looks like this needs to be done per td tag.
Is there a way to do this around both tags or does it require setting up another element around them?
I thought this could also be setup using a => function possibly.
Code for inline render() expression:
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{this.props.objectTypeEditable &&
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
}
{this.props.objectTypeEditable &&
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
}
</tr>
</tbody>
)
}
Code to create buttons outside of render()
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
var updateButton;
var deleteButton;
// if the object can be edited create the update and delete buttons
if (this.props.objectTypeEditable) {
updateButton = (
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
);
deleteButton = (
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
);
}
return (
<tbody>
<tr>
{tdsForObject}
{updateButton}
{deleteButton}
</tr>
</tbody>
)
}
JSX doesn't allow you to return 2 side by side elements. It can only return 1 element. So yeah you can either wrap those 2 inside a single element and use the same verification as you do now.
{this.props.objectTypeEditable &&
<div class="wrapper">
<td>
[...]
</td>
<td>
[...]
</td>
</div>
}
You can also use inline self invoked function and return an array of JSX elements. (the render methods will automatically loop through them and render them). Here I use ES6 arrow function to bind directly the this reference but it is probably doable with a normal function and binding it by hand like this .bind(this)
{(() => {
let elements = [];
if(this.props.objectTypeEditable) {
// push td elements in array
}
return elements;
})()}
you need to use a ternary expression
condition ? expr1 : expr2
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{ this.props.objectTypeEditable
? <td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
: null
}
{ this.props.objectTypeEditable
? <td>
<button onClick={this.handleDelete}>Delete</button>
</td>
: null
}
</tr>
</tbody>
)
}
It is not possible to use more than one inline. React's documentation and examples use ternary operations and recommends it as the default pattern. If you prefer one method over the other thats fine, they are both valid, just stick to one for consistency's sake :)

Data Binding to Array in Polymer

I'm in the process of learning Polymer. I am trying to bind an array to my UI. Each object in the array has a property that will change. I need my UI to update when the property value changes. I've defined my Polymer component as follows:
my-component.html
<dom-module id="my-component">
<template>
<h1>Hello</h1>
<h2>{{items.length}}</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr repeat="{{ item in items }}">
<td>{{ item.name }}</td>
<td>{{ item.status }}</td>
</tr>
</tbody>
</table>
<br />
<button on-click="testClick">Test</button>
</template>
<script>
// element registration
Polymer({
is: "my-component",
properties: {
items: {
type: Array,
value: function() {
return [
new Item({ name:'Tennis Balls', status:'Ordered' }),
new Item({ name:'T-Shirts', status: 'Ordered' })
];
}
}
},
testClick: function() {
for (var i=0; i<items.length; i++) {
if (items.name === 'Tennis Balls') {
items[i].status = 'Shipped';
break;
}
}
}
});
</script>
</dom-module>
The component renders. However, the bindings do not work at all.
The line with {{ items.length }} does not show a count. Its basically just an empty h2 element.
The first item gets rendered in the list. However, the second one does not.
When I click the Test button, the update to the status is not reflected on the screen.
When I look at everything, it looks correct to me. However, it is clear from the behavior that the data-binding is not setup properly. What am I doing wrong? The fact that items.length and the initial rendering of all of the items in the array has me really confused.
Polymer data binding system works like this:
If the declared property changes (for example adding a new item) then it will detect the change and update your DOM.
However Polymer won't monitor changes inside your property (For performance/compatibility reasons).
You need to notify Polymer that something inside your property changed. You can do that using the set method or notifyPath.
E.g (From the polymer data binding section)
this.set('myArray.1.name', 'Rupert');
You can also add an observer if you want to do something when your array is updated.
Polymer 1.0 properties Documentation
And I think you should also add a notify:true to your property
items: {
type: Array,
notify:true,
value: function() {
return [
new Item({ name:'Tennis Balls', status:'Ordered' }),
new Item({ name:'T-Shirts', status: 'Ordered' })
];
}
}

Categories