Angular js scope variable (array) changes not reflected in ng-repeat - javascript

I am binding a scope variable, an array to ng-repeat div tag (a table basically).
When I dynamically add any data to the array, it works! A row is added to the table.
But when I remove an element from the array, the change doesn't reflect on the table. One row should be removed.
Following is the code that I'm working with (Javascript):
$scope.myfields = [];
$scope.addField = function () {
$scope.myfields.push({ "name": "", "type": "", "required": "", "enum": "" });
console.log("add: " + $scope.myfields.length);
console.log(JSON.stringify($scope.myfields));
}
$scope.removeField = function (index) {
$scope.myfields.splice(index, 1);
console.log("remove: " + $scope.myfields.length);
console.log(JSON.stringify($scope.myfields));
}
EJS: Please see below!
Weird thing is,
In console log, it says that changes are made as expected to $scope variable, only view(table) is not getting updated.
And if I don't put "track by $index", add and remove both stops reflecting in table!
Any help appreciated. Thanks!
EDIT 2:
The code you have asked for:
<div class="col-md-12">
<p style="text-align:center"><strong>DEFINE CUSTOM FIELDS:</strong></p>
<br>
<div style="text-align:center">
Click on '+' button to add custom field:
<div class="fa fa-plus-circle" ng-click='addField()'> </div>
<div class="fa fa-minus-circle" ng-click='removeField(0)'> </div>
</div>
<br>
<div data-responsive-table>
<table data-table>
<thead >
<tr >
<th data-event='sort'>
Field Name
</th>
<th data-event='sort'>
Type
</th>
<th data-event='sort'>
Is Required?
</th>
<th data-event='sort'>
Enumeration
</th>
</tr>
</thead>
<tbody >
<tr data-parent-row ng-repeat="um in dynamicFields track by $index">
<td>
<input placeholder="Name" ng-model="um.name" validation="required" >
</td>
<td>
<select style='height: 45px;' ng-model="um.type" >
<option value="string">string</option>
<option value="boolean">boolean</option>
<option value="integer">integer</option>
</select>
</td>
<td>
<select style='height: 45px;' ng-model="um.required" >
<option value="true">true</option>
<option value="false">false</option>
</select>
</td>
<td>
<input placeholder="Enum" ng-model="um.enum" validation="required" >
</td>
</tr>
</tbody>
</table>
</div>
</div>

The variable name in your ng-repeat should be myfields and not dynamicfields
Since in your controller it is $scope.myfields, in your view it should be
ng-repeat="um in myfields track by $index"

Related

JQuery function not working after another row in my table is dynamically generated [duplicate]

This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
How can I apply a jQuery function to all elements with the same ID?
(4 answers)
Closed 1 year ago.
I have an input form that when the add button is clicked, it submits the current line of data and then adds a new row of input fields for more data entry. When I click the add button, it will post the first input data into the database and create a new row. However when I try to submit the second row, nothing happens. The data isn't submitted and another row isnt added.
This is my jquery click function
$('#addRow').click(function(e) {
const cloneRow = $('#tableData tbody tr').first();
e.preventDefault();
let data = {
project_id: $(".project_id").last().val(),
imp_or_ann: $(".imp_or_ann").last().val(),
category: $(".category").last().val(),
cost: $(".cost").last().val(),
hours: $(".hours").last().val()
}
$.ajax({
url: '/costs_hours',
type: 'POST',
data: data
}).then(
cloneRow.clone().appendTo('#tableData tbody').find(".cost, .hours").val(''),
$("#next").removeAttr('disabled'),
$("#link").attr('href', '/fundings')
)
})
This is my input form
<table id="tableData" class="table text-light text-center mt-3">
<thead>
<tr>
<th scope="col">Project ID</th>
<th scope="col">Implementation or Annual</th>
<th scope="col">Category</th>
<th scope="col">Costs</th>
<th scope="col">Hours</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="input-group mb-3">
<input name="project_id" type="text" class="form-control project_id">
</div>
</td>
<td>
<div class="input-group mb-3">
<div class="input-group mb-3">
<select name="imp_or_ann" class="form-select imp_or_ann"
id="inputGroupSelect01">
<option disabled selected>Choose...</option>
<option>Implementation</option>
<option>Annual</option>
</select>
</div>
</div>
</td>
<td>
<div class="input-group mb-3">
<div class="input-group mb-3">
<select name="category" class="form-select category" id="inputGroupSelect01">
<option disabled selected>Choose...</option>
<option>EMO</option>
<option>Analysts</option>
<option>Maintenance</option>
<option>ETS</option>
<option>BOT</option>
<option>OtherUT</option>
<option>Materials</option>
<option>Non-UT Contract</option>
<option>Contingency</option>
</select>
</div>
</div>
</td>
<td>
<div class="input-group mb-3">
<input name="cost" type="text" class="cost form-control">
</div>
</td>
<td>
<div class="input-group mb-3">
<input name="hours" type="text" class="hours form-control">
</div>
</td>
<td>
<button id='addRow' type="button" style="background-color: #bf5700;"
class="btn btn-warning text-light"><i
class="fas fa-plus-circle"></i> Add</button>
</td>
</tr>
</tbody>
</table>
I want the button to stay in the so the use knows they have to click the add button to add that piece of data. But this currently only works for the first data input. When the second row is added, The button doesnt work, I cant submit data and another row isnt created. Any advice is greatly appreciated!
$('#addRow') is evaluated once, when the JS code is executed.
When you clone and add new row, you are effectively created a new (and different!) element with id="addRow". THAT element does not have a handler listening for click events.
Instead, you need to put the listener on something that does not change, for example #tableData, using the jquery $.on():
$('#tableData').on('click', '.addRow', function(e){
...
});
This way, the element listening is #tableData, which gets clicks and then checks to see if the click originated on something matching .addRow. Your actual function doesn't need to change.
However, you will need to change your add button from id="addRow" to class="addRow", (or use name attribute or anything else) so you don't have multiple elements with same ID in the DOM.
Well, you were setting the same ID for multiple objects which is problematic, but what you really needed was to set the listener on a static parent (I used tableData). I changed your button ID to a class, so it could apply to all add buttons.
Your listener was set to scan the page for button#addRow and attach a listener. Then you create a new instance of the button, but the listener setup had already fired and wasn't aware of the newly created instance. Instead, set the listener on a static parent. The event is on with arguments of click to detect the click and button.addRow to specify which element to focus the listener on.
$('#tableData').on('click', 'button.addRow', function(e) {
const cloneRow = $('#tableData tbody tr').first();
e.preventDefault();
let data = {
project_id: $(".project_id").last().val(),
imp_or_ann: $(".imp_or_ann").last().val(),
category: $(".category").last().val(),
cost: $(".cost").last().val(),
hours: $(".hours").last().val()
}
$.ajax({
url: '/costs_hours',
type: 'POST',
data: data
}).then(
cloneRow.clone().appendTo('#tableData tbody').find(".cost, .hours").val(''),
$("#next").removeAttr('disabled'),
$("#link").attr('href', '/fundings')
)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="tableData" class="table text-light text-center mt-3">
<thead>
<tr>
<th scope="col">Project ID</th>
<th scope="col">Implementation or Annual</th>
<th scope="col">Category</th>
<th scope="col">Costs</th>
<th scope="col">Hours</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="input-group mb-3">
<input name="project_id" type="text" class="form-control project_id">
</div>
</td>
<td>
<div class="input-group mb-3">
<div class="input-group mb-3">
<select name="imp_or_ann" class="form-select imp_or_ann" id="inputGroupSelect01">
<option disabled selected>Choose...</option>
<option>Implementation</option>
<option>Annual</option>
</select>
</div>
</div>
</td>
<td>
<div class="input-group mb-3">
<div class="input-group mb-3">
<select name="category" class="form-select category" id="inputGroupSelect01">
<option disabled selected>Choose...</option>
<option>EMO</option>
<option>Analysts</option>
<option>Maintenance</option>
<option>ETS</option>
<option>BOT</option>
<option>OtherUT</option>
<option>Materials</option>
<option>Non-UT Contract</option>
<option>Contingency</option>
</select>
</div>
</div>
</td>
<td>
<div class="input-group mb-3">
<input name="cost" type="text" class="cost form-control">
</div>
</td>
<td>
<div class="input-group mb-3">
<input name="hours" type="text" class="hours form-control">
</div>
</td>
<td>
<button type="button" style="background-color: #bf5700;" class="addRow btn btn-warning text-light"><i
class="fas fa-plus-circle"></i> Add</button>
</td>
</tr>
</tbody>
</table>

how we can bind a list of a list of object using thymeleaf

I have a form where the user can add as much as he want of Table object that also can contains as much as he want of Columns object (like building tables in SQL).. I've tried the code bellow but nothing works and the form dosnt appear anymore when I've tried to bind the two lists.
Controller
#ModelAttribute("page")
public Page getTable() {
TableColumn column = new TableColumn();
List<TableColumn> columns = new ArrayList<>();
columns.add(column);
Table table = new Table();
table.setColumns(columns);
List<Table> tables = new ArrayList<>();
tables.add(table);
Page page = new Page();
page.setTables(tables);
return page;
}
#GetMapping("/scriptsqlgenerator")
public String viewForm(#ModelAttribute("page") Page page) {
return "ScriptSqlNext";
}
#PostMapping("/scriptsqlgenerator")
public String generateScript(#ModelAttribute("page") Page page) {
page.tables.forEach((t) ->{
System.out.println(t.getName());
t.getColumns().forEach((c) -> {
System.out.println(c.getName());
System.out.println(c.getType());
System.out.println(c.getIndex());
System.out.println(c.getNotnull());
});
});
}
HTML
<form th:object="${page}" class="list-group" th:action="#{/filegenerated}" method="get">
<a class="list-group-item list-group-item-action" data-toggle="collapse" data-target="#target1"> Create Table </a>
<div id="target1" class="collapse" style="margin: 30px;">
<div id="tablelist">
<div class="form-inline itemtable" th:each="table, itemStat :${page.tables}">
<div class="form-group mb-2 d-none">
<input th:field="*{tables[__${itemStat.index}__].id}" type="text" class="form-control">
</div>
<div class="form-group mb-2">
<input th:field="*{tables[__${itemStat.index}__].name}" type="text" class="form-control" placeholder="Table name">
</div>
<input type="button" class="btn btn-danger mb-2 ml-2" onclick="addRow()" value="Add column">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" class="d-none">Id</th>
<th scope="col">Column Name</th>
<th scope="col">Type</th>
<th scope="col">Index</th>
<th scope="col">Null</th>
</tr>
</thead>
<tbody id="columnlist">
<tr class="item" th:each="column,status :
${table.columns}">
<td><input th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].name}" type="text" class="form-control" required></td>
<td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].type}" id="inputState" class="form-control" required>
<option value="" selected
disabled>Choose</option>
<option th:value="${type}">int</option>
<option th:value="${type}">varchar</option>
</select>
</td>
<td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].index}" id="inputState" class="form-control" required>
<option value="" selected
disabled>Choose</option>
<option th:value="${index}">on</option>
<option th:value="${index}">off</option>
</select>
</td>
<td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].notnull}" id="inputState" class="form-control" required>
<option value="" selected
disabled>Choose</option>
<option th:value="${notnull}">on</option>
<option th:value="${notnull}">off</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<button class="btn btn-danger mb-2 text-center" type="button" id="addTable" style="margin-top: 30px;">Add table</button>
</div>
<div class="text-center">
<button type="submit" class="btn btn-outline-danger btn-lg" style="margin-
top: 50px;">Generate File</button>
</div>
</form>
For the JS part I was using some codes to implement the addRow() method which will add more Columns to the Table and addTable() method that would add another Table object, but nothing was working for my case
This is my view is looking like:
*PLEASE CAN ANYONE HELP ME TO SOLVE THAT .. I REALLY NEED IT .... *
Your approach is okay. But you need to fix a few things.
In the getTable method, you are setting empty lists for tables and columns. So there is nothing to iterate over in the view layer to show the form. Change to:
#ModelAttribute("page")
public Page getTable() {
Column column = new Column();
List<Column> columns = new ArrayList<>();
columns.add(column);
Table table = new Table();
table.setColumns(columns);
List<Table> tables = new ArrayList<>();
tables.add(table);
Page page = new Page();
page.setTables(tables);
return page;
}
And
Add missing } for th:field="*{tables[__${i.index}__].name" and close this input tag.
NOTE:
I am not sure how you wanted to handle the three select inputs. I tested omitting them, meaning, keeping only Column id and name in the form, data bind without any issue in that case.
Also I didn't check your JS, as you have mentioned that you haven't tested it yet.
Suggestions:
I see you are returning a view name from your POST handler. Take a look at the following article on Wikipedia.
Post/Redirect/Get

How to clear values of dropdownlists in angularjs

I am using ng-repeat in HTML to loop on a javascript array. showing this array in select.
what i want to do is to clear all selected data from these dropdownlists when press on a button
HTML
<div class="widget-title">
<div class="widget-controls">
<button class="btn btn-xs btn-primary" ng-click="newassignment()">New</button>
<button class="btn btn-xs btn-success" ng-click="saveavd()">Save</button>
</div>
<h3><i class="icon-ok-sign"></i>Assignment</h3>
</div>
<div id="vacation" class="widget-content" style="height:81vh; overflow:auto;">
<div class="row">
<table style="width: 70%;" border="1">
<thead>
<tr>
<th>Departments</th>
<th>Work Level</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="d in departments | filter : depts">
<td>
<input style=" margin-top: 0;" type="checkbox" ng-model="d.details"/> {{d.Dep_LDesc}}
</td>
<td>
<select class="form-control input-sm" ng-model="wrklevel2">
<option ng-repeat="w in worklevel" value="{{w.lvlid}}">{{w.lvlnm}}</option>
</select>
</td>
</tr>
</tbody>
</table>
angularjs
$scope.worklevel = [
{ lvlid: 1, lvlnm: 'First Level' },
{ lvlid: 2, lvlnm: 'Second Level' }
]
$scope.newassignment = function () {
$scope.wrklevel2 = {};
angular.forEach($scope.departments, function (d) {
d.details = false;
})
}
You should have different models for your selects inside your ng-repeat to achieve that you can try the following
(ng-model="d.wrklevel2")
<select class="form-control input-sm" ng-model="d.wrklevel2">
<option ng-repeat="w in worklevel" value="{{w.lvlid}}">{{w.lvlnm}}</option>
</select>
after that you can also clear values of select inside your forEach loop
angular.forEach($scope.departments, function (d) {
d.details = false;
d.wrklevel2 = undefined;
})
set the ngModel of select to empty string -
function saveavd()
{
$scope.wrklevel2 = ""
// ...
}

Adding ng-model to Select multiple causes Options not to display in IE

This is so strange...I have a multiple select list. If I add ng-model to the select. the option values display as below:
If I remove ng-model, the CompanyName shows correctly.
Here is my HTML:
<tab heading="Contractors">
<div class="panel panel-default">
<div class="panel-body">
<table style="width:100%">
<tr>
<td>
#Html.Label("Enter Name, FID, or SSQID")<br />
<input type="text" style="width:200px"/>
</td>
<td rowspan="2">
<select multiple ng-multiple="true" ng-model="selectedContractors">
<option ng-repeat="c in contractors" value="{{c.CompanyID}}">{{c.CompanyName}}</option>
</select>
</td>
<td rowspan="2" style="align-content:center" ng-show="viewContractors">
<i class="fa fa-chevron-circle-right" style="cursor:pointer" ng-click="addContractor()"></i><br /><br />
<i class="fa fa-chevron-circle-left" style="cursor:pointer"></i>
</td>
<td></td>
</tr>
<tr>
<td>
<button type="button" class="btn btn-primary" ng-click="submitSearch(search)">Search</button>
</td>
<td></td>
<td style="align-content:center"></td>
</tr>
</table>
</div>
</div>
</tab>
</tabset>
Here is my controller code:
$scope.submitSearch = function (cs) {
var id = $scope.businessUnitID;
operatorService.getsearch(cs, id)
.success(function (data) {
$scope.contractors = data.SearchResults;
$scope.viewContractors = true;
});
};
What is most perplexing is that I use the select multiple list EXACTLY the same way in another project but in that project the option values show correctly with ng-model. I just can't make sense of this.
Since first posting this question, I have discovered that the issue seems to be caused by placing select on Angular ui-tabs, but it still only happens in IE. If I move the select from the tab and just place it somewhere on the page, it works as aspected, but hence, I need it on the tabs.
Any assistance is greatly appreciated!

Send all the columns(<td>) for each row whose dropdown is selected from JSP to Servlet

I have a table in JSP page which is wrapped inside the form tag.
Now the table has 3 columns and each row has a dropdown to select from.
My problem is that when I change the dropdown select value and click on the button in the form (below table), all the data for that row(all the td values) must be transferred to a Servlet.
So lets say if I change the drop down value to Available and click button, then all 3 column values: Java(1st column), Available(2nd column) and john(3rd column) should be transferred to Servlet.
How can I possibly do that?
Here is my table:
<form method="post"
action="${pageContext.request.contextPath}/DemoServlet"
class="container">
<div class="panel panel-default ">
<!-- Default panel contents -->
<div class="panel-heading">Operations form</div>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-center">Title</th>
<th class="text-center">Status</th>
<th class="text-center">User</th>
</tr>
</thead>
<tbody>
<c:forEach items="${entries}" var="entry">
<tr>
<td>${entry.title}</td>
<td class="text-center"><c:choose>
<c:when test="${entry.status == 'Reserved'}">
<select name="status" id="status">
<option value="${entry.status }">${entry.status }</option>
<option value="Available">Available</option>
<option value="Checkedout">Checkedout</option>
</select>
</c:when>
<c:when test="${entry.status == 'Checkedout'}">
<select name="status" id="status">
<option value="${entry.status }">${entry.status }</option>
<option value="Available">Available</option>
</select>
</c:when>
<c:otherwise>
${entry.status}
</c:otherwise>
</c:choose></td>
<td class="text-center">${entry.username}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
<div class="row clearfix">
<div class="col-xs-2">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</form>
Thanks in advance.
Put some sort of identifier as a data attribute on each row
<tr data-id="${entry.id}">
Then you can use a change handler on the select elements to make an ajax call. Using jQuery it would look something like:
$('select[name=status]').change(function(){
var data={
id : $(this).closest('tr').data('id'),
name : $(this).val()
};
$.post('path/to/server', data, function(response){
/* validate server response and then do something in UI */
});
});
Note that element ID's must be unique by defintiion but you are repeating the same ID's in your loop to create row elements.
Reference : $.post() API Docs

Categories