Show and hide Blade table rows with Alpine JS - javascript

I am using Blade with Laravel 8.x and would like to show or hide table rows using x-show in Alpine JS (v3).
<table>
#foreach($items as $item)
<div x-data="{show: false}">
<tr>
<td>
<button type="button" x-on:click="show = !show">
Toggle Show
</button>
</td>
</tr>
<tr x-show="show">
<td>{{ $item->name }}</td>
</tr>
</div>
#endforeach
</table>
When I do this however, I get a ReferenceError: show is not defined error. Is this approach possible without using x-for as I would like to still have access to the Blade variables within the rows.

The problem is that you cannot place <div> element in a <table> element. When browser executes your code, it places the <div> outside <table> element, so in <tr> the show is not defined.
You can workaround this by replacing <div> tag with <tbody> or making the button and item name in one row as separate <td> and appending x-data to <tr>.

Related

Nested StimulusJS scopes / controller issue / HTML 5 template tag

I have a basic Rails app with a nested association and I am doing a standard 'line items' type view where I am dynamically adding and removing rows from a template tag. I have a select that triggers a AJAX call to replace all the nested row selects (the context changed). The expenditure controller handles the overall form and the nested-form controller is used on other pages to handle the adding and removal of rows:
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template data-target="nested-form.template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
</tbody>
</table>
</div>
It works fine. I can add and remove rows and if I change the expenditure.budget select all the expenditure.budgetItemSelect targets get updated EXCEPT for the one inside the template. It's as if it's missing from the entire scope of the controller. I had them nested before but now have them in the same div data-controller="expenditure nested-form" to double check and it still doesn't work. Checked spelling and even tried removing the data-target="nested-form.template". No errors in the browser console. Am I missing something obvious here?
UPDATE
Hmmm... it seems that the template tag is read only and NOT part of the DOM which is why this fails.
I managed a hack where I replaced the contents of the entire template but that seems to break the controller that adds / deletes the rows 🤦‍♂️.
UPDATE 2
I found a workaround - If someone can improve this code I will accept this as a better answer.
It seems to be an issue with the <template> tag in HTML5.
I have a workaround but it's ugly.
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template id="expenditure_items_template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
<script type="text/template" data-target="nested-form.template" id="expenditure_items_template_script">
</script>
</tbody>
</table>
</div>
Here is what I did in my controller:
// find the template
var template = document.getElementById("expenditure_items_template");
// load the template contents
var new_template = document.importNode(template.content, true);
// replace the select with my new content (off screen)
new_template.getElementById('expenditure_expenditure_items_attributes_NEW_RECORD_budget_item_id').innerHTML = select.innerHTML;
// clear the new script place holder
document.getElementById("expenditure_items_template_script").innerHTML = "";
// set the new updated template into the script tag
document.getElementById("expenditure_items_template_script").appendChild(new_template);
I basically have two templates - one <template> which holds the raw HTML and the second <script> that works with Stimulus.

How to add row to table dynamically on button click with Angular2?

I am working on Angular2 project and want to want to add rows to table dynamically, when a button is clicked. I know that *ngFor can be used to add rows dynamically, but I am interested if it is possible to use *ngFor only if button is clicked.
usually you use *ngFor to iterate over an array (generally of objects). So if your array is called "data" yo can have some like
<table *ngIf="data.length"> <!--Don't show nothing if no data-->
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
</tr>
<tr *ngFor="let item of data">
<td>{{item.firstName}}</td>
<td>{{item.lastName}}</td>
<td>{{item.age}}</td>
</tr>
</table>
Where you has a variable data like
data:any[]=[] //don't forget initialize!
A button can execute some like
onClick()
{
this.data.push({firstName:"firstName",lastName:"Last Name",age:18})
}
A quick sample:
<button (onClick)="sth=!sth">click</button>
<div *ngIf="sth">
<div *ngFor="...">
...
</div>
</div>

Why can't I nest a tbody tag inside a div tag with JSX in React?

I'm a React beginner and I'm working on a project where I'll have a form and as data gets entered a table will be dynamically populated with data from the user.
My code:
<div>
<tbody>
<tr>
<td>{this.props.someData}</td>
</tr>
<tr>
<td>{this.props.moreData}</td>
</tr>
</tbody>
</div>
React is throwing this error: ValidateDOMNesting(...): cannot appear as a child of div
I tried putting the table inside a new component and then nesting the new component but I have the same problem. Why does JSX not like tables inside of divs? Is there any work-around?
Your problem is an HTML one. To make a table, the table body has to be inside the <table> tag:
<table>
<tbody>
...
</tbody>
...
</table>
No table tag works without tag. should be under
like :
<table>
<tbody>
<tr>
<td>{this.props.someData}</td>
</tr>
<tr>
<td>{this.props.moreData}</td>
</tr>
</tbody>
</table>

Get multiple Angular.js controllers for multilple <td> within the same <tr>

I'm trying to add multiple angular controllers within the same tr tag, the problem is that chrome rewrites the table to standardize it, and there is no element between tr and td in the HTML table hierarchy.
Here is what I currently have, each color represents a different controller to call.
The final aim is to have a table like below, with a different controller for one or multiple td, instead or multiple trs
I know I could use a global controller to handle all the data, or use multiple div elements with a fixed width to achieve this, but I'd prefer using a single tr table.
Here is the code :
<table>
<tr>
<div ng-controller="testController">
<td>{{testcontrollerscopevalue}}</td> <!-- empty when displayed -->
<td>{{testcontrollerscopevalue2}}</td> <!-- empty when displayed -->
<td>{{testcontrollerscopevalue3}}</td> <!-- empty when displayed -->
</div>
<div ng-controller="testController2">
<td>{{testcontroller2scopevalue}}</td> <!-- empty when displayed -->
</div>
</tr>
</table>
The following works :
<table ng-controller="testController">
<tr>
<td>{{testcontrollerscopevalue}}</td> <!-- set when displayed-->
</tr>
</table>
Here is what chrome generates :
<body>
<div ng-controller="testController"></div>
<div ng-controller="testController2"></div>
<table>
<tbody>
<tr>
<td>{{testcontrollerscopevalue}}</td> <!-- out of scope-->
<td>{{testcontrollerscopevalue2}}</td> <!-- out of scope-->
<td>{{testcontrollerscopevalue3}}</td> <!-- out of scope-->
<td>{{testcontroller2scopevalue1}}</td> <!-- out of scope-->
</tr>
</tbody>
</table>
<table ng-controller="testController">
<tbody>
<tr>
<td>{{testcontrollerscopevalue}}</td> <!-- set -->
</tr>
</tbody>
</table>
Is there any way I can achieve this ?
Is there any tag I could use instead of div to get this to work?
Thanks,
As we discussed at length in the chat session, This is a case where you are best served by using the ControllerAs Syntax, and wrapping the <table> element in multiple <div> elements holding each controller's logic.
My suggestion was something similar to the following code:
<div ng-controller="testController as tc">
<div ng-controller="testController2 as tc2">
<table>
<tbody>
<tr>
<td>{{tc1.testcontrollervalue}}</td>
<td>{{tc1.testcontrollervalue2}}</td>
<td>{{tc1.testcontrollervalue3}}</td>
<td>{{tc2.testcontroller2value1}}</td>
</tr>
</tbody>
</table>
</div>
</div>
For this to work, your variables need to be converted to be properties of the controller, rather than properties of $scope.
var tc1 = this; //consistent reference to controller object
SomeService.get(function(data) {
tc1.someProperty = data;
});
You should probably just do:
<td ng-controller="testController2">{{testcontrollerscopevalue}}</td>
If you really need the div:
<td><div ng-controller="testController2">{{testcontrollerscopevalue}}</div></td>

dynamically close and open table rows

I have a table on my site that contains the options for different products. I didn't put it there, the ecommerce platform did. It lists the options in a row in the table. The table looks like this:
<div class="attributes">
<table>
<tbody>
<tr>
<td>Size:</td>
<td> </td>
<td><select><option>Sizes here</option></select></td>
</tr>
</tbody>
</table>
</div>
Then if there were another option it would be in an additional row with the same markup.
This renders with the label (size in this case) out in front of the <select> box. I want the label above the <select> box and I figured the easiest way to accomplish this would be to close the <tr> and open a new one. Any ideas on how to do this?
EDIT: I should mention that the ecommerce platform generates the html and I do not have access to the source code
Assuming that it follows that exact structure, try this:
$(".attributes select").each(function(){
$label = $(this).parent().prev().prev();
$label.parent().before("<tr></tr>");
$label.parent().prev().append($label.clone());
$label.remove();
$(this).parent().prev().remove();
});
Here's an example: Demo
Like so?
<div class="attributes">
<table>
<tbody>
<tr>
<td>Size:</td>
</tr>
<tr>
<td><select><option>Sizes here</option></select></td>
</tr>
</tbody>
</table>
</div>
I think you have to do it in two steps:
remove the elements:
$('.attributes').find('tr:first-child').remove();
$('.attributes').find('tr:first-child').remove();
2.append them back in the same place
$('.attributes').find('tr:first-child').before('<tr><td>Sizes here</td></tr>');

Categories