How to add dynamic row to a table using angularjs - javascript

Like jQuery, How can I add dynamic rows to a table with form elements in a button click using angularjs and How to differentiate these form elements like array name in normal jquery submit.
<tr>
<td style="text-align:center">1</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.assets">
</td>
<td>
<select ng-model="newItem.type" class="form-control">
<option value="Rent" ng-selected="'Rent'">Rent</option>
<option value="Lease">Lease</option>
</select>
</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.amount" />
</td>
<td>
<select ng-model="newItem.calculation_type" class="form-control">
<option value="Daily" ng-selected="'Daily'">Daily</option>
<option value="Monthly">Monthly</option>
<option value="Yearly">Yearly</option>
</select>
</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.total_amount" />
</td>
</tr>

It is important to remember that, with Angular, you are not adding new rows to the table but are, instead, modifying the data of the model. The view will update automatically when the model changes. What you have shown in your example is merely the HTML template portion of what Angular is going to do. As mentioned, you will not be modifying these DOM elements but instead should be manipulating the model. So here are the steps I would suggest:
Create a controller for your table
app.controller('CostItemsCtrl', [ '$scope', function($scope) {
// the items you are managing - filled with one item just as an example of data involved
$scope.items = [ { assets: 'My assets', type: 'Rent', amount: 500, 'calculation_type': 'Daily', total: 0}, ... ];
// also drive options from here
$scope.assetTypes = [ 'Rent', 'Mortgage' ];
$scope.calcTypes = [ 'Daily', 'Monthly', 'Yearly' ];
// expose a function to add new (blank) rows to the model/table
$scope.addRow = function() {
// push a new object with some defaults
$scope.items.push({ type: $scope.assetTypes[0], calculation_type: $scope.calcTypes[0] });
}
// save all the rows (alternatively make a function to save each row by itself)
$scope.save = function() {
// your $scope.items now contain the current working values for every field shown and can be parse, submitted over http, anything else you want to do with an array (like pass them to a service responsible for persistance)
if ($scope.CostItemsForm.$valid) { console.log("it's valid"); }
}
Display the data with your HTML
<form name="CostItemsForm" ng-controller="CostItemsCtrl">
<table>
<tr><th>Assets</th><th>Type</th><th>Amount</th><th>Calculation</th><th>Total</th></tr>
<tr><td colspan="5"><button ng-click="addRow()">Add Row</button></td></tr>
<tr ng-repeat="item in items">
<td><input required ng-model="item.assets"></td>
<td><select ng-model="item.type" ng-options="type for type in assetTypes"></select></td>
<td><input required ng-model="item.amount"></td>
<td><select ng-model="item.calculation_type" ng-options="type for type in calcTypes"></td>
<td><input required ng-model="item.total"></td>
</tr>
<tr><td colspan="5"><button ng-click="save()">Save Data</button></tr>
</table>
</form>
Optionally add CSS to display when fields are valid/invalid
input.ng-invalid { background-color: #FEE; border: solid red 1px }
The "Angular Way"
As you can see, you are doing no direct modification of the DOM whatsoever. You get all the baked-in goodness of form validation without having to write any real code. The controller acts purely as a controller by holding the model and exposing various functions to its scope. You could take this further down the angular path by injecting services which handle retrieving and updating the data, and those services are then shared. Perhaps you already do this in your code, but this code should work for your specific example without any additional dependencies.

You should render the row using ng-repeat, as such:
<form ng-submit="onSubmit(newItem)">
<table>
<tr>
<td style="text-align:center">1</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.assets">
</td>
<td>
<select ng-model="newItem.type" class="form-control">
<option value="Rent" ng-selected="'Rent'">Rent</option>
<option value="Lease">Lease</option>
</select>
</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.amount" />
</td>
<td>
<select ng-model="newItem.calculation_type" class="form-control">
<option value="Daily" ng-selected="'Daily'">Daily</option>
<option value="Monthly">Monthly</option>
<option value="Yearly">Yearly</option>
</select>
</td>
<td>
<input type="text" class="form-control" required ng-model="newItem.total_amount" />
</td>
</tr>
<tr ng-repeat="row in rows">
<td>{{row.assets}}</td>
<td>{{row.selected}}</td>
<td>{{row.amount}}</td>
<td>{{row.calculation_type}}</td>
</tr>
</table>
</form>
where this is how your controller should look like:
angular.module('angularSimpleApp').controller('MyCtrl', function ($scope) {
$scope.newItem = ''; // represents the models in the form
$scope.rows = [];
$scope.onSubmit = function(obj) {
$scope.products.push(obj);
}
});

Related

How to use innerHTML to format all the rows like row 1 when adding rows?

I'm new to web development and coding in general. I'm practicing with tables. I have this HTML here:
Essentially I'm stuck on finessing the inner html portion so that each new row will have the exact same placeholder and selection box as row 1. Thank you for your help.
<!DOCTYPE html>
<html lang="en">
<style>
table, th, td {
border: 1px solid black;
}
</style>
<head>
</head>
<body>
<table id = "table">
<thead>
<tr>
<th>Product</th>
<th>Amount</th>
<th>Buy or Sell?</th>
</tr>
<tr>
<td><input type = "text" name = "name" placeholder = "Enter the Product Name Here"> </td>
<td><input type = "text" name = "Amount" placeholder = "Enter the Amount you want to buy or sell Here"></td>
<td><select id = "optionSell">
<option value = "placeholding">Buy or Sell</option>
<option value = "buy">Buy</option>
<option value = "sell">Sell</option>
</select>
</tr>
</thead>
</table>
<div class = "container">
<div id = "click">
<button onclick = "MycreateRow()">Add Row</button>
</div>
</div>
</body>
<script>
function createRow() {
let table = document.getElementById("table");
let row = table.insertRow(-1);
let productCell = row.insertCell(0)
let amountCell = row.insertCell(1)
let buyCell = row.insertCell(2)
productCell.innerHTML = "<div contenteditable></div>"
amountCell.innerHTML = "<div contenteditable></div>"
}
</script>
Since you stated that you're new to the Web-Stack I will give you an answer that contains more information than just your question.
Learn and use the semantic elements. For an input, the semantic elements are either <input> or <textarea>. Semantic tags by default already help you with accessibility. This means you don't have to bother too much to think about users that might use screen-readers.
When using tables, question yourself if the content is tabular data. Never! use tables for styling or aligning purpose. In that case, always use CSS-Grid or Flexbox. The only acceptable exception is an email template.
Do not use the onclick-attribute as a trigger. The modern solution is to split HTML and JS completely and use an external JS file. This will help you later on with maintenance as you only have to bother about one file and do not have to search for connections and change them all manually (saves you work later on). The solution is to use an addEventListener in JS and to listen to a 'click' of the button in the JS directly.
put the entire row you want to create inside backticks. This allows you to write them multiline (also has other advantages for other cases).
if you want to add Elements you should not use innerHTML anymore. It poses a security risk (XSS-Injections) and is slow as the whole DOM has to be reparsed. If you want to add HTML you could use the simple and easy insertAdjacentHTML method.
insertAdjacentHTML follows by a syntax out of 2 keywords. First the position and then the text/string you want to insert. beforeend will insert the string before the end of the element. afterend would insert it after the element e.g.
document.querySelector('#add-row').addEventListener('click', function() {
let row = `<tr>
<td><input type="text" name="name" placeholder="Enter the Product Name Here"> </td>
<td><input type="text" name="Amount" placeholder="Enter the Amount you want to buy or sell Here"></td>
<td>
<select id="optionSell">
<option value="placeholding">Buy or Sell</option>
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</td>
</tr>`;
let table = document.querySelector('#table');
table.insertAdjacentHTML('beforeend', row);
})
table,
th,
td {
border: 1px solid black;
}
<table id="table">
<thead>
<tr>
<th>Product</th>
<th>Amount</th>
<th>Buy or Sell?</th>
</tr>
<tr>
<td><input type="text" name="name" placeholder="Enter the Product Name Here"> </td>
<td><input type="text" name="Amount" placeholder="Enter the Amount you want to buy or sell Here"></td>
<td>
<select id="optionSell">
<option value="placeholding">Buy or Sell</option>
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</td>
</tr>
</thead>
</table>
<div class="container">
<div id="click">
<button id="add-row">Add Row</button>
</div>
</div>

Retrieve child elements which are not within other child elements

I need a way to retrieve elements of a given element which are not children of certain other elements. To distinguish between these "parent" elements I've been using a data attribute.
This is an example of my following HTML stucture:
<form method="post" data-component-id="3">
<table border="0">
<thead>
<tr>
<th></th>
<th>First Name</th>
<th>Last Name</th>
<th>Date of Birth (dd/mm/yyyy)</th>
<th>Sex</th>
</tr>
</thead>
<tbody>
<tr data-component-id="8">
<td>Spouse</td>
<td><input name="txtFirstName_1" type="text" maxlength="255" id="txtFirstName_1" data-mapping-id="Person.Firstname"></td>
<td><input name="txtLastName_1" type="text" maxlength="255" id="txtLastName_1" data-mapping-id="Person.Lastname"></td>
<td><input name="txtDOB_1" type="text" maxlength="10" id="txtDOB_1" data-mapping-id="Person.Birthday"></td>
<td>
<select name="ddlSex_1" id="ddlSex_1" data-mapping-id="Person.Sex">
<option value=""></option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</td>
</tr>
<tr data-component-id="9">
<td>Child</td>
<td><input name="txtFirstName_2" type="text" maxlength="255" id="txtFirstName_2" data-mapping-id="Person.Firstname"></td>
<td><input name="txtLastName_2" type="text" maxlength="255" id="txtLastName_2" data-mapping-id="Person.Lastname"></td>
<td><input name="txtDOB_2" type="text" maxlength="10" id="txtDOB_2" data-mapping-id="Person.Birthday"></td>
<td>
<select name="ddlSex_2" id="ddlSex_2" data-mapping-id="Person.Sex">
<option value=""></option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</td>
</tr>
<tr data-component-id="9">
<td>Child</td>
<td><input name="txtFirstName_3" type="text" maxlength="255" id="txtFirstName_3" data-mapping-id="Person.Firstname"></td>
<td><input name="txtLastName_3" type="text" maxlength="255" id="txtLastName_3" data-mapping-id="Person.Lastname"></td>
<td><input name="txtDOB_3" type="text" maxlength="10" id="txtDOB_3" data-mapping-id="Person.Birthday"></td>
<td>
<select name="ddlSex_3" id="ddlSex_3" data-mapping-id="Person.Sex">
<option value=""></option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</td>
</tr>
</tbody>
</table>
<input type="button" name="submit" value="SUBMIT">
Note: The reason there are two data-component-id="9" attributes is because I treat this attribute as a type for mapping purposes in my back-end. (8 = Spouse data, 9 = Child data)
I created a JavaScript function which accepts one element and recursively builds a Component object, holding an ID, an array of its fields, and sub-components (recursive).
// Component Object
function Component(componentID, fields, subComponents) {
this.ComponentID = componentID; // Numeric
this.Fields = fields; // Array
this.Components = subComponents; // Array
}
// Recursively build component (and sub-components)
$.fn.formDataToComponent = function() {
var componentID = $(this).attr("data-component-id");
var componentName = "";
var fields=[];
var subComponents=[];
var subComponentsIndex=0;
// Recursively create the sub components
$(this).find("[data-component-id]").each(function(oSubComponent){
subComponents[subComponentsIndex] = $(oSubComponent).formDataToComponent();
subComponentsIndex++;
});
$(this).find('[data-mapping-id]').each(function() {
// $(this).find('[data-mapping-id]') will retrieve all elements with the data attribute (therefore 12 elements will be selected)
// With the list of these elements, I want to select ONLY those which are closest to the parent element with the attribute "data-component-id".
// Therefore in this particular scenario I only want to select the 4 elements of each "tr" element with each recursive call.
// I need to do so in a way that is dynamic and is based on the data attribute as I'll be using this with various forms.
});
return new Component(componentID, componentName, fields, subComponents);
}
I've looked at using the .not() function in jQuery but I don't think this works how I think it does. I'll continue searching for the solution, but if anyone knows of an easy/efficient way to do so please, I would really appreciate the help!
Solution
For a solution to your problem, check jQuery's .filter function here: http://api.jquery.com/filter/
// Get list of sub components
var subComponentArray = $(this).find("[data-component-id]");
// Get all fields that are not within the sub components
$(this).find('[data-mapping-id]').filter(function(){
return $(this).closest(subComponentArray).length === 0;}).each(function(index) {
// DO STUFF
});
For a solution to your problem, check jQuery's .filter function here:
http://api.jquery.com/filter/
and do something like this:
$(this).find('[data-mapping-id]').filter(function(){
return $(this).closest("[parent selector you don't want]").length === 0;
}).each(function() { .... });
Might be able to use not(). Using your Root, Child and Field tags as example if you only wanted the nested Field inside Child you can do:
$('Field').not("Root > Field');
This would exclude any Field that was a direct child of Root
It isn't clear what you are trying to filter exactly

How do we work with multiple modules in angularJS?? I have tried hard, one is working second one is not working

angular
.module('ApplicationOne',[])
.controller('myControllerOne', function($scope){
$scope.name = "Luther";
$scope.fname = "Martin";
$scope.ed = "B.TECH";
});
angular
.module('App2',[])
.controller('myControllerTwo', function($scope){
$scope.name = "Juliet";
$scope.fname = "Willium";
$scope.ed = "BSC";
});
In my localhost, the first module is working fine, but problem with the second module, I can't catch it even I referred official documentation of AngularJS, Please give some brief about this, I'm very interested to learn 'ng-script', And i'm a starter on this topic.Click to see the result in my localhost
Here's the link of my jsfiddle: https://jsfiddle.net/daranaveen007/dt256cep/
Add the first module as a dependency to the second module.
angular.module('App2',['ApplicationOne'])
Although this is useful, only if your first module declares services or components which the second module can use. As far as controllers are concerned, they should be added to single root module.
In your case, you do not need two modules. A single module with one controller is sufficient. Instead you need array of persons.
angular.module('ApplicationOne', [])
.controller('myControllerOne', function($scope, $rootScope) {
$scope.message = "Hello World!";
$scope.persons =[{
name: "Luther",
fname: "Martin",
ed: "B.TECH"
},{
name: "Juliet",
fname: "Willium",
ed: "BSC"
}]
});
HTML:
<div ng-app="ApplicationOne" ng-controller="myControllerOne">
<div class="row" ng-repeat="person in persons">
<div class="col-md-6 col-md-offset-3">
<table class="table">
<tr>
<td>Name:</td>
<td>
<input type="text" class="form-control" ng-model="person.name"> </td>
</tr>
<tr>
<td>Fathers name:</td>
<td>
<input type="text" class="form-control" ng-model="person.fname">
</td>
</tr>
<tr>
<td>Ed.Qual:</td>
<td>
<select class="form-control" ng-model="person.ed">
<option value="MCA">MCA</option>
<option value="M.TECH">M.TECH</option>
<option value="B.TECH">B.Tech</option>
<option value="BSC">BSC</option>
</select>
</td>
</tr>
<tr>
<td>
<button ng-click="message = 'Good job (Magilchi).'" class="btn btn-primary btn-md"> Save </button>
</td>
<td>
<p>{{ message }}</p>
</td>
</tr>
</table>
</div>
</div>

Push new value to existing map in angular and map binding

Angularjs code.
$scope.model;
I defined my new object in angular like following
$scope.newRelation = {
relationType : null,
relatedModel : null
};
HTML
<div class="table-responsive">
<table class="table">
<tr>
<td><select class="form-control"
ng-model="newRelation.relationType" required
ng-options="modeltype as modeltype for modeltype in modeltypes"></select>
</td>
<td><select class="form-control"
ng-model="newRelation.relatedModel" required
ng-options="model.name as model.name for model in models"></select>
</td>
<td>
<button type="button" class="btn btn-default"
ng-click="addRelation()">Add Relation</button>
</td>
</tr>
</table>
</div>
Angular code
$scope.addRelation = function()
{
$scope.model.relations.push($scope.newRelation);
};
When i click form save the model.relations values are empty in the back end.Any clues ? Please let me know if i need to provide more information
You directly assigned object to list.
It'll keep reference.
make a copy
var obj=angular.copy($scope.newRelation);
Then push it
$scope.model.relations.push(obj);

Do not save data from editing ngModel

I have table which dynamically add data. When I edit data in first table it is not saved in the first or the second table but should be.
$scope.correctiveData = [
[
{"name":"Description of Corrective Action (1)","values":[]},
{"name":"Action Taken By (name) (1)","values":[]},
{"name":"Company (1)","values":[]},
{"name":"Date (1)","values":[]}
]
];
/*---------------First table-----------*/
<tr ng-repeat="col in correctiveData">
<td><textarea>{{col[0].values[0]}}</textarea></td>
<td><input type="text" value="{{col[1].values[0]}}"></td>
<td><select ng-model="col[2].values[0]">
<option ng-repeat="com in company">{{com.name}}</option>
</select>
</td>
<td>
<div class="input-append" id="date" class="datetimepicker" time ng-model="col[3].values[0]">
<input class="span2" class="inputIcon" type="text" data-format="MM/dd/yyyy HH:mm PP" ng-model="col[3].values[0]" required pattern="\d\d/\d\d/\d\d\d\d\s\d\d:\d\d\s(AM|PM)">
<span class="add-on">
<i class="icon-calendar"></i>
</span>
</div>
</td>
</tr>
/*--------------------Second table-------------------*/
<tr ng-repeat-start="data in correctiveData">
<td>{{data[0].name}}</td>
<td>{{data[0].values[0] || 'required'}}</td>
</tr>
<tr>
<td>{{data[1].name}}</td>
<td>{{data[1].values[0] || 'required'}}</td>
</tr>
<tr>
<td>{{data[2].name}}</td>
<td>{{data[2].values[0] || 'required'}}</td>
</tr>
<tr ng-repeat-end>
<td>{{data[3].name}}</td>
<td>{{data[3].values[0] || 'required'}}</td>
</tr>
When I wrote data from javascript like below
"{"name":"Description of Corrective Action (1)","values":['Some value']}"
it's show in both of tables but when I edit it it doesnt save.
Also tables are in two different templates wich loads from two different files but have same controller.

Categories