I have built a semi-custom DataGrid based on a table with fields that auto-populate based on a Service call, An intended behavior in the DataGrid is the ability for the user to revise an entry in the system (in this case, Documents) so that the user can select an action and "Replace" the file, and its File Type. The actual markup looks like this:
<form action="#" method="post" ng-model="OtherDocumentsForm" style="margin-top: 15px;">
<h3 style="margin: 0;">Additional Documents</h3>
<table>
<tr>
<th>File Type</th>
<th>File Name</th>
<th>Date</th>
</tr>
<tr ng-repeat="record in DocumentsOther track by $index" id="row-{{record.file_url}}">
<td>
<span data-document-type="{{record.file_type}}" data-document-id='{{record.document_id}}'>{{record.file_type}}</span>
<select ng-model="newDocumentType" id="replace-file-doctype-{{record.document_id}}" placeholder="Choose file Type" ng-show="$scope.replaceFile" data-document-id="{{record.document_id}}" class="replaceComboBox"> <!-- Note replaceFile -->
<option label="" value="" selected="selected"></option>
<option label="PDF" value="pdf">PDF</option>
<option label="Image" value="image">Image</option>
</select>
</td>
<td>
<div ng-if="record">
{{record.document_name}}
<!-- Replace Action-->
<button class="replaceAssociatedRecord" ng-click="ClearAssociatedFile($event, this, '{{record.file_url}}', '{{record.document_id}}');" data-document-type="{{record.file_type_raw}}">Replace</button>
<input type="file" ng-model="replaceFileField" id="replace-file-{{record.document_id}}" data-document-id="{{record.document_id}}" data-document-type="{{record.file_type_raw}}" onclick="checkIfAssocFileDefined(this);" ng-show="replaceFile" class="replaceFileField" /> <!-- Note replaceFile -->
</div>
</td>
<td>
{{record.date_created | dateConsistent}}
</td>
</tr>
</table>
<button ng-click="addUncategorizedFile($event)" style="margin: 5px auto 15px; display: block;">Upload additional file</button>
</form>
As you can probably see, one of the issues that is present is that $scope.replaceFile is called across the board. This value gets set to true when $scope.ClearAssociatedFile() (code below) is called in the replace action, so every field gets unhidden. This was a serious oversight on my end and I am wondering, what would be the best approach to re-implement the components here so that I can split off replaceFile (tied to ng-show, and uninitalized inside the parent controller) so its independent enough to where it can be altered once the event of ClearAssociatedFile is selected?
$scope.ClearAssociatedFile = function(event, elem, parentClassName, inheritDocumentId) {
event.preventDefault();
event.stopPropagation();
//TODO reconfigure to use event
//document.getElementById("row-"+parentClassName).children[0].children[0].innerHTML = "";
event.currentTarget.parentElement.parentElement.parentElement.children[0].children[0].innerHTML = "";
//event.currentTarget.parentElement.parentElement.parentElement.children[0].children[1].style.display = "inline-block"
//document.getElementById("row-"+parentClassName).children[1].children[0].children[0].innerHTML = "";
event.currentTarget.parentElement.parentElement.parentElement.children[1].children[0].children[0].innerHTML = "";
//document.getElementById("row-"+parentClassName).children[1].children[0].children[1].style.display = "none";
event.currentTarget.parentElement.parentElement.parentElement.children[1].children[0].children[1].style.display = "none";
//event.currentTarget.parentElement.parentElement.parentElement.children[1].children[0].children[2].style.display = "inline-block";
//Revise the File Type field to its default.
document.getElementById("replace-file-doctype-"+inheritDocumentId).value = event.target.attributes[2].value;
//Show existing fields
//eval($scope.replaceFile+inheritDocumentId = true);
var replaceFileKey = "replaceFileItem"+$filter('convert_dashes')(inheritDocumentId);
$scope[replaceFileKey] = true;
};
In your controller, declare the following variable
$scope.replaceFileKeys = {};
Replace the last 2 lines in the ClearAssociatedFile function with the following
var replaceFileKey = inheritDocumentId;
$scope.replaceFileKeys[replaceFileKey] = true;
Update ng-show as follows
ng-show="$scope.replaceFileKeys[record.document_id]"
With the above change, only the item that you interact with is hidden. Let me now if you have questions about the change.
Related
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>
I'm learning a bit of web programming (cs50) and I'm facing a problem which I can't find a solution to..
As a project, I'm writing a website which act as a Password Manager.
Backend is written in Python, using Flask.
I have a page which list all the passwords an user have saved in the database, it's displayed as a table like so :
(element[0] refer to the name of the password, element[1] to the password itself and element[2] to it's rowid (used to try to give unique id's))
<table class="table table-striped table-dark">
<thead class="thead-light">
<tr>
<th>Password name</th>
<th>Password</th>
</tr>
<tbody>
{% for element in codes %}
<tr>
<form action="" method="post" id="form{{ element[2] }}">
<td name="name"><b>{{ element[0] }}</b></td>
<td id="password{{ element[2] }}" name="password" class="hidetext">{{ element[1] }}</td>
<td id ="{{ element[2] }}">
<select class="form-control" name="option">
<option disabled selected value>Options :</option>
<option value="1">Reveal password</option>
<option value="2">Modify password</option>
<option value="3">Delete password</option>
</td>
<td>
<button type="submit" onclick="apply();">Confirm</button>
</td>
</form>
</tr>
{% endfor %}
</tbody>
</thead>
</table>
So a row has 4 fields, the name, the passwords, a select menu, a button to apply the option chosen in the select menu.
The issues I'm encountering are mostly "How to access data of the row the user is interacting with". I tried to solve this using the DOM :
While on the website, in the console if I inspect the submit button, I can access all the data like so :
var row = $0.offsetParent;
var selection = $(row).previousElementSibling.value;
if (selection == 1) {
var pass = row.previousElementSibling.previousElementSibling;
$(pass).toggleClass('hidetext');
In my actual code, I replaced $0 by $(this) I thought it would do the trick but I'm obviously wrong.
To prevent the normal action of the button, I'm using this :
$(document).ready(function() {
$('form').each(function(){
$(this).submit(function(e){
e.preventDefault();
})
})
})
And for the two others options, I have pretty much the same issue the console give the same error (TypeError) as the first one.
else if (selection == 2) {
var formId = $(row).previousElementSibling.id;
$('#form' + formId).attr('action', '/modify_saved')
$(this).submit();
}
else if (selection == 3) {
var formId = $(row).previousElementSibling.id;
$('#form' + formId).attr('action', '/delete_password')
$(this).submit();
}
else {
return false;
}
Sorry if this is a little all over the place, I'm trying to put everything in order as I'm writing this message :-)
I am very new to java script/ JQUERY and would appreciate some help.
Basically I have a table with maybe 50-100 entries.
I'm trying to build a website where a user inputs their data into a form and I want the website to hide the table results that doesnt meet their requirements specified on the form OR to add a CSS style class like a green or red border to signal yes or no.
Similar to this website
https://www.prospectivedoctor.com/medical-school-chance-predictor-2/
I'm really struggling
Also, I can appreciate that my solution is quite clunky so would appreciate it if anyone has shorter/ more elegant way to code it so that I can expand it easily to multiple categories.
This is my (truncated) HTML:
<form>
<select id = "courset" name = "coursetype">
<option value = "PBL"> PBL</option>
<option value = "Integrated">Integrated</option>
<option value = "Traditional">Traditional</option>
<option value = "Unsure">Not Sure</option>
</select>
<div id = "submit">
<input type = "Submit">
</div>
</form>
<table id = 'empTable'>
<thead>
<tr>
<th scope = "col"> University</th>
<th scope = "col"> Course Type</th>
<th scope = "col"> GSCE Requirements</th>
<th scope = "col"> A Level Requirements</th>
<th scope = "col"> Admissions Test</th>
</tr>
</thead>
<tbody>
<tr id= 'Uni1'>
<td>Uni1</td>
<td class = 'tabcourse'>Integrated</td>
<td class = 'tabgcse'> 5 As </td>
<td class = 'tabalevel'>AAA </td>
<td class = 'tabtest'>UCAT</td>
</tr>
<tr id= 'Uni2'>
<td>Uni2</td>
<td class = 'tabcourse'>Traditional</td>
<td class = 'tabgcse'> 10 As </td>
<td class = 'tabalevel'>CCC </td>
<td class = 'tabtest'>None</td>
</tr>
</tbody>
This is my JQuery and JS:
$(document).ready(function() {
$('#submit').click(
function submission(){
var results = $("form").serializeArray();
function admission () {
function showTableData() {
var myTab = document.getElementById('empTable');
// LOOP THROUGH EACH ROW OF THE TABLE AFTER HEADER.
for (i = 1; i < myTab.rows.length; i++) {
// GET THE CELLS COLLECTION OF THE CURRENT ROW.
var objCells = myTab.rows.item(i).cells;
// LOOP THROUGH EACH CELL OF THE CURENT ROW TO READ CELL VALUES.
for (var j = 0; j < objCells.length; j++) {
objCells.item(j).innerHTML
if (objCells.item(j).innerHTML === results[16].coursetype){
myTab.rows.item(i).style.borderColor = 'green';
}
}
}
}
}
}
);
})
note that I used results[16] because imagine that coursetype is the 17th input item. I couldn't work out a better way to target the input.
*** UPDATE ***
So I amalgamated your solutions which worked pretty well. However, I've hit a block with this particular criteria. Note: I needed a way for the JQuery to recognise that if someone selects value = 8, then they're also eligible for any unis with entry criteria <8.
Here's my HTML
<form>
<select id = "Agrad" name = "A Level Grades">
<option value = "8">A*A*A* </option>
<option value = "7">A*A*A</option>
<option value = "6">A*AA</option>
<option value = "5">AAA</option>
<option value = "4">AAB</option>
<option value = "3">ABB</option>
<option value = "2">BBB</option>
<option value = "1">BBC</option>
<option value = "0">BCC or lower</option>
</select>
<div id = "submit">
<input type = "button" Value = "Submit">
</div>
</form>
<table id = 'empTable'>
<thead>
<tr>
<th scope = "col"> University</th>
<th scope = "col"> Course Type</th>
<th scope = "col"> GSCE Requirements</th>
<th scope = "col"> A Level Requirements</th>
<th scope = "col"> Admissions Test</th>
</tr>
</thead>
<tbody>
<tr id= 'Uni1' data-agrade = "5678">
<td>Uni1</td>
<td class = 'tabcourse'>Integrated</td>
<td class = 'tabgcse'> 5 As </td>
<td class = 'tabalevel'>AAA </td>
<td class = 'tabtest'>UCAT</td>
</tr>
<tr id= 'Uni2' data-agrade = "87654321">
<td>Uni2</td>
<td class = 'tabcourse'>Traditional</td>
<td class = 'tabgcse'> 10 As </td>
<td class = 'tabalevel'>CCC </td>
<td class = 'tabtest'>None</td>
</tr>
<tr id= 'Uni3' data-agrade = "8">
<td>Uni3</td>
<td class = 'tabcourse'>Integrated</td>
<td class = 'tabgcse'> 5 As </td>
<td class = 'tabalevel'>A*A*A* </td>
<td class = 'tabtest'>UCAT</td>
</tr>
</tbody>
Here's my JQuery
$(document).ready(function() {
$('input[type=button]').click(function() {
// A Grades
var selected_agrade = $("#Agrad").val();
$("tr").each(function(tr_index, tr) {
var needed_agrade = $("tr").attr("agrade");
if(needed_agrade.includes(selected_agrade)) {
$(tr).addClass('fit');
} else {
$(tr).removeClass('fit');
}
});
});
});
Can you see any obvious reason why it's not working?
Thanks!
I've edited your html a little(maybe typo fixed, courset -> course) and rewritten your js code. If you use jQuery, you can easily find dom elements and manipulate it. Also if you use each of jQuery, it's easy to iterate through elements. Note that it's only working with this truncated html so you may need to handle selectors like tr.
$('input[type=button]').click(function() {
var selected_course = $("#course").val();
$("tr").each(function(tr_index, tr) {
var course = $(tr).find(".tabcourse").text();
if(selected_course == course) {
$(tr).addClass('green_background');
} else {
$(tr).removeClass('green_background');
}
});
});
.green_background {
background-color:green;
}
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="crossorigin="anonymous"></script>
<form>
<select id = "course" name = "coursetype">
<option value = "PBL"> PBL</option>
<option value = "Integrated">Integrated</option>
<option value = "Traditional">Traditional</option>
<option value = "Unsure">Not Sure</option>
</select>
<div id = "submit">
<input type = "button" Value="Submit" >
</div>
</form>
<table id = 'empTable'>
<thead>
<tr>
<th scope = "col"> University</th>
<th scope = "col"> Course Type</th>
<th scope = "col"> GSCE Requirements</th>
<th scope = "col"> A Level Requirements</th>
<th scope = "col"> Admissions Test</th>
</tr>
</thead>
<tbody>
<tr id= 'Uni1'>
<td>Uni1</td>
<td class = 'tabcourse'>Integrated</td>
<td class = 'tabgcse'> 5 As </td>
<td class = 'tabalevel'>AAA </td>
<td class = 'tabtest'>UCAT</td>
</tr>
<tr id= 'Uni2'>
<td>Uni2</td>
<td class = 'tabcourse'>Traditional</td>
<td class = 'tabgcse'> 10 As </td>
<td class = 'tabalevel'>CCC </td>
<td class = 'tabtest'>None</td>
</tr>
</tbody>
</table>
There are two tried and true ways that many people might approach this problem. One way is to filter based on the value of a text node in a table cell <td>someValue</td> (the literal way of doing this), or filtering based on meta data embedded in the table cell opening tag using the data-something="value") convention.
My inclination is to avoid dealing with text nodes in a situation like this due to various anomalies that can make dealing with them trickier than you might think. For instance, what if a table cell has an empty text node. What if the table cell <td> failed to materialize for some reason. I say go up one level and deal with the table row, <tr>.
First, you might think about embedding some useful attribute into each table row:
<tr data-type="1">
</tr>
<tr data-type="2">`
</tr>
You will have to decide what you think is best. Using numbers for the attribute value is convenient, because then you can change the value attribute of the <option> tags in your <select> list to say something like:
<option value="1">PBL</option>
<option value="2">Integrated</option>
<option value="3">Traditional</option>
<option value="4">Not Sure</option>
However, for code clarity, this might be better.
<select id="courset" name="coursetype">
<option value="All">All</option>
<option value="PBL">PBL</option>
<option value="Integrated">Integrated</option>
<option value="Traditional">Traditional</option>
<option value="Not_Sure">Not Sure</option> <!-- A single word would be best -->
</select>
<tr data-type="Integrated">
</tr>
<tr data-type="Traditional">`
</tr>
With each row containing essential meta information, your task becomes much easier than dealing with text nodes. Simply ...
var tbody = document.getElementById('idOfTbody');
var tableRows = tbody.getElementsByTagName('tr');
Now that you have all of the table rows in a variable, the game is a foot!
I might note that in situations like this, you end up needing to toggle on and off the rows depending on which option you pick from the select list. Be sure to have a way for all of the rows to show (hence, the all option or something similar).
Now, all you must do is loop through the table rows, and change the CSS to display:none. I would define a class in my CSS and use something like.
someTableRow.className="remove";
Yes, you can do something like ...
someTableRow.style.display="none";
... but, you really should take advantage of CSS classes in a case like this. However, if you bump into a complex situation in which there is already a class on the row, and you don't know how to deal with (add and remove) multiple classes in the .className string, just use the second way.
You pick the kind of loop you want. Here's a basic outline of what to do.
Note, there are various array functions (.filter(), .map(). blah, blah) that might enable you to do a .foreach() and what have you, but here is the most fundamental way to do this.
function filterTableRowsByAttribute (tableRows, rowAttribute, filterValue)
{
var i,
row;
for (i=0; i < tableRows.length; i++) {
row = tableRows[i];
if (row.getAttribute(rowAttribute) !== filterValue) {
removeElement(row);
} else {
showTableRow(row);
}
}
}
function removeElement(element)
{
element.style.display="none";
}
function showTableRow (element)
{
element.style.display="table-row";
}
var filterValue = "Integrated"; // Grab that value from the selection list.
var tbody = document.getElementById('tbodyId'); // Give your tbody an id
var tableRows = tbody.getElementsByTagName('tr');
var targetAttribute = "data-type"
filterTableRowsByAttribute(tableRows, targetAttribute, filterValue);
Conclusion
Making decisions about table rows by an attribute is simpler than dealing with text nodes, or fiddling with the innerHTML. Nested loops are not necessary. Break the problem down into steps. Don't let jQuery bamboozle you. :-) Filtering table rows is easier when the table row elements themselves have enough information to filter by.
The alternative would be to examine the text node in the appropriate table data cell, and then walk up the down to hide the table row.
Gather all table data cells to be examined.
var tableCells = document.getElementsByClassName("tabcourse");
Inspect and test the value of the text node inside of table data cell.
function filterTableRowsByCell(tableCells, filterValue)
{
let i,
cell,
tableRows,
row;
tableRows = getTableRowsFromCells(tableCells) <!-- the extra step -->
for (i = 0; i < tableCells.length; i++) {
cell = tableCells[i];
row = tableRows[i];
if (cell.nodeValue !== filterValue) {
removeElement(row);
} else {
showTableRow(row);
}
}
}
I have an inventory page that also contains a password field.
I would like to hide the password when the page is loaded, best would be to have points displayed **** and after click password is shown or a pop up.
JS
var get_cert = function () {
$http.get($scope.url.cert_list).then(
function (response) {
$scope.certs = response.data;
}
);
}
// execute get function
get_cert();
HTML
<div class="panel panel-default">
<table class="table table-striped valign_middle">
<tr class="table_header">
<td>Name</td>
<td>Pass</td>
</tr>
<tr ng-repeat="cert in certs | filter:search | orderBy:['name']">
<td>{{cert.name}}</td>
<td>
<button class="w3-btn w3-black w3-hover-green w3-ripple" ng-click="get_cert()">Show</button>
<span ng-show="get_cert()">{{cert.password}}</span>
</td>
</tr>
</table>
<button ng-show="!cert.showPw" class="w3-btn w3-black w3-hover-green w3-ripple" ng-click="cert.showPw = true">Show</button>
<span ng-show="cert.showPw">{{cert.password}}</span>
You can use ng-click to do cert.showPw = true, which will append a property called showPw (a boolean) to the object. Combined with ng-show, you can easily switch between the two.
This way you'll keep your controller free of any additional logic needed. You may include ng-click on the span which holds the password which will set showPw = false to switch it back to a button.
See my JSFiddle for full example.
Create a input
<input type="password" name="???">
Then you can change its type to "text" with
$("#idOfInout")type = 'text';
I have a button that pops up an Angular UI Bootstrap popover, using a template.
You can view it in this pen
The popover template is a form with a table containing a series of text fields with ng-models:
<script type="text/ng-template" id="filterPopoverTemplate.html">
<div class="filters">
<form>
<table>
<tbody>
<tr>
<td><input type="text" size="5" ng-model="filterHsCodeRestricted"></td>
<td>HS Code Restricted</td>
</tr>
<tr>
<td><input type="text" size="5" ng-model="filterHsCode10"></td>
<td>HS Code 10</td>
</tr>
<tr>
<td><input type="text" size="5" ng-model="filterCOD"></td>
<td>COD</td>
</tr>
</tbody>
</table>
<div class="filter-buttons">
<button tabindex="0" class="btn btn-default btn-xs" ng-click="applyFilters()">Apply</button>
<button class="btn btn-default btn-xs" ng-click="resetFilters()">Reset</button>
</div>
</form>
</div>
</script>
I have a "reset" button which calls a function that I want to reset all the ng-models to empty strings:
$scope.resetFilters = function () {
$scope.filterHsCodeRestricted = '';
$scope.filterHsCode10 = '';
$scope.filterCOD = '';
};
However, if I type something into the field and click "reset", the model is not being set to the empty string. I've done this elsewhere and it works, just not inside a popover template, so I assume it has something to do with the fields being in a popover ng-template. How do I "access" those?
The problem is that you're using the model without the DotRule or controller-as-syntax.
The whole problem was already explained by Pankaj Parkar in this question.
So, to make it work, you have to create a new object, ex:
$scope.model = {};
Then, build your ng-model's like this:
ng-model="model.filterCOD"
And so on..
The problem with your code is :
You need to define another ng-controller inside your filterPopoverTemplate.html
app.controller('poptemp', function($scope) {
$scope.resetFilters = function() {
$scope.filterHsCodeRestricted = '';
$scope.filterHsCode10 = '';
$scope.filterCOD = '';
$scope.filterPOE = '';
$scope.filterECCN = '';
$scope.filterItemCondition = '';
};
});
Check the corrected code here