Retrieve child elements which are not within other child elements - javascript

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

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>

jQuery set dynamically created input fields based on difference between 2 fields

Is there a way I can set input values of each dynamically created input fields based on a difference between 2 dynamically created fields. Below is my code and what am trying to achieve.
HTML
<table id="requested-wh-stock-table" class="table table-bordered table-hover dataTable" width="100%">
<thead>
<tr>
<th>Luminaire</th>
<th>Order Quantity</th>
<th>Delivered Qty</th>
<th>Back Order Qty</th>
</tr>
</thead>
<tbody>
#foreach ($salesorder as $request)
<tr>
<td><input type="text" class="form-control" name="luminaire" value="{{$request->luminaire}}" readonly></td>
<td><input type="number" class="form-control" name="order_quantity" id="order_quantity"
value="{{$request->quantity}}" readonly /></td>
<td><input type="number" class="form-control" name="delivered_quantity" id="delivered_quantity" value="" />
</td>
<td><input type="number" class="form-control" name="backorder_quantity" id="backorder_quantity" value=""
readonly /></td>
</tr>
#endforeach
</tbody>
</table>
jQuery
//Update Backorder qty based on Delivered Quantity amount
$(document).ready(function () {
$("#delivered_quantity").change(function () {
var backorder_quantity = $("#order_quantity").val() - $("#delivered_quantity").val();
$("#backorder_quantity").val(backorder_quantity);
});
});
Currently, it only updates the first field, it does not update other fields, is there a way I can loop through all fields and update Backorder field as I change the Delivered Quantity field?
If you are dynamically updated these fields within a loop, you will run into naming conflicts with the IDs. Instead, I would set the class name of each of the fields, and not the IDs:
<tr>
<td><input type="text" class="form-control" name="luminaire" value="10" readonly></td>
<td><input type="number" class="form-control order_quantity" name="order_quantity" id="order_quantity" value="20" readonly/></td>
<td><input type="number" class="form-control delivered_quantity" name="delivered_quantity" id="delivered_quantity" value=""/></td>
<td><input type="number" class="form-control backorder_quantity" name="backorder_quantity" id="backorder_quantity" value="" readonly/></td>
</tr>
Now you can attach an event listener in jQuery to every element with that class name, and you can reference it using the "this" keyword. Then sense they all have a parent that is a table row, we can use that to refer to the other input elements that are children of that parent element. Once we find the children in the row of the elements that you're targeting, we can update the values:
$(document).ready(function() {
$('.delivered_quantity').on('change', function() {
let parent = $(this).parents('tr');
let order_quantity = parent.find('.order_quantity');
let backQuant = parent.find('.backorder_quantity');
let backorder_quantity = order_quantity.val() - $(this).val();
backQuant.val(backorder_quantity);
});
});
We attach the change event to EACH of the delivered_quantity elements that you could potentially be targeting in your table. When any of them experience this event, the callback function above will occur. Next we get the parent element that is a table row. We then get the order_quantity and backorder_quantity elements that are children within the table row. We then calculate the backorder_quantity variable and then update the backorder_quantity element to match that value. Note that I had to put in some random values for your luminair and order_quantity sense we don't know what those values will be sense you're using them in a loop.
You should not use id on elements which are not unique. Since the inputs will be rendered multiple times (in rows), then I suggest to not use the id attribute. So let's try to use the name attribute instead for finding the DOM.
On the change event listener of delivered_quantity input, get the other fields in the same row. To do that, from this object inside the event handler, do seek the closest tr and then find the particular field from it's child.
$(document).ready(function() {
$("[name=delivered_quantity]").change(function() {
var order_quantity = $(this).closest('tr').find("[name=order_quantity]").val()
var delivered_quantity = $(this).closest('tr').find("[name=delivered_quantity]").val();
var backorder_quantity = order_quantity - delivered_quantity
$(this).closest('tr').find("[name=backorder_quantity]").val(backorder_quantity);
});
});

How to clone form elements with auto increamented id to all elements

I have a form under a . I want to clone this and append dynamically in another and so on dynamically. Also I need to assign auto incremented id to all form elements too. Apart from pure javascript I can not use any jQuery or any other library.
Here is my HTML
<tr id="repeat">
<td><input type="text" id="fieldName" /></td>
<td>
<select name="fieldType" id="fieldType">
<option value="string">String</option>
</select>
</td>
<td><input type="radio" id="mandatory" name="mandatory" value="true" /><input type="radio" id="mandatory" name="mandatory" value="false" /></td>
<td>Delete Button</td>
</tr>
Here is my JavaScript
var i = 0;
this.view.findById("start").addEventHandler("click", function () {
var original = document.getElementById('repeat');
var clone = original.cloneNode(true);
original.parentNode.appendChild(clone);
})
Presently I can cloned the form elements in <tr id="repeated1"> dynamically and so on, but unable to assign auto incremented id to input box and select box . Also unable to assign auto incremented name to the radio buttons dynamically
You can change Id or another attribute as you want.
but for your code my solution is using querySelectorAll to get element and change it's Id, something like below code, it is tested and works nice:
Based on this HTML design code and JS function:
function MakeElementsWithDifferentId() {
for (var i = 1; i < 10; i++) {
var original = document.getElementById('repeat');
var clone = original.cloneNode(true);
clone.id="repeat"+i;
clone.querySelectorAll('[id="fieldName"]')[0].id ="fieldName"+i;
clone.querySelectorAll('[id="fieldType"]')[0].id ="fieldType"+i;
clone.querySelectorAll('[id="mandatory"]')[0].id ="mandatory"+i;
clone.children[2].children[0].name="mandatoryName"+i; //To change the radio name also
original.parentNode.appendChild(clone);
}
}
MakeElementsWithDifferentId();
<table>
<tr id="repeat">
<td><input type="text" id="fieldName" /></td>
<td>
<select name="fieldType" id="fieldType">
<option value="string">String</option>
</select>
</td>
<td><input type="radio" id="mandatory" name="mandatory" value="true" /> </td>
<td>Delete Button</td>
</tr>
</table>
the MakeElementsWithDifferentId() function make 10 batch elements with different Ids.
the JSFiddle Test
after run you can right click on element that you want and see the Id by inspect element.
Note:
Instead of clone.querySelectorAll('[id="fieldName"]')[0] it's better to get element by querySelector like clone.querySelector('[id="fieldName"]')
Hope will help you.

How to add dynamic row to a table using angularjs

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);
}
});

Select the element next the selected

I have next code:
<tr>
<td>
<select class="select_big" name="additional_field" id="<?=$field['id'];?>" required>
<option value="0">Выберите значение</option>
...
</td>
</tr>
<tr>
<td>
<select class="select_big" name="additional_field" id="<?=$field['id'];?>" required>
<option value="0">Выберите значение</option>
...
</td>
</tr>
<tr>
<td>
<select class="select_big" name="additional_field" id="<?=$field['id'];?>" required>
<option value="0">Выберите значение</option>
...
</td>
</tr>
<tr>
<td>
<select class="select_big" name="additional_field" id="<?=$field['id'];?>" required>
<option value="0">Выберите значение</option>
...
</td>
</tr>
When I select the second "select" element, I need to disable all the next one.
If I select 1 need to disable 3 and 4, if I select 2 I need to disable only 4.
Count of element could be different.
How I can get all elements next the select?
I try next:
<script type="text/javascript">
$("[name=additional_field]").change(function(e) {
field_data = '#' this.id.replace(/[* .]/g, "\\$&");
$(field_data).parent().parent().nextAll().each(function(i) {
console.log($(this)('[name=additional_field]'));
});
});
</script>
But I receive next error:
Uncaught TypeError: object is not a function
Help me please.
I think you can do it simpler without confusing traversal of the parent nodes:
var $sel = $('.select_big').change(function() {
$sel.filter(':gt(' + $sel.index(this) + ')').prop('disabled', +$(this).val());
});
Demo: http://jsfiddle.net/VFcF9/
It will also reenable selectboxes if you select default option back.
The error you were getting is because of this line:
$(this)('[name=additional_field]')
The first part, $(this), returns a jQuery object, and that object is not a function so you can't follow it with more parentheses.
As for your requirement to disable all of the following select elements, perhaps:
$("[name=additional_field]").change(function(e) {
$(this).closest("tr").nextAll("tr").find("[name=additional_field]").prop("disabled", true);
});
This should do it
$("[name=additional_field]").change(function(e) {
var f = $(this).closest('tr') //find the closest parent tr
.nextAll('tr') //find all tr that follows
.find('select.select_big') //from them, get all select
//f should be all the selects the follow what changed
});

Categories