SPA - search html table - javascript

I'm building a single page application in which i have a html table and i need to implement a search box that loops through the rows of the table and hides the ones that don't match the search-box text. The problem is that being a SPA all the javascript code i found on the internet that does this thing is based on $(document).ready(function() so it doesn't work. I tried the folowing approach:
In my viewmodel.js i have:
function filter2(search, tblData) {
window.phrase = document.getElementById(search).value;
var words = window.phrase.toLowerCase().split(" ");
var table = document.getElementById(tblData);
var ele;
for (var r = 1; r < table.rows.length; r++) {
ele = table.rows[r].innerHTML.replace(/<^>+>/g, "");
var displayStyle = 'none';
for (var i = 0; i < words.length; i++) {
if (ele.toLowerCase().indexOf(words[i]) >= 0)
displayStyle = '';
else {
displayStyle = 'none';
break;
}
}
table.rows[r].style.display = displayStyle;
}
}
and in my view.html:
<input type="text" id="search" placeholder="Search..." data-bind="click: filter2"/>
,where tblData is my html table and search is my searchbox.
This is not working, if anyone has any idea please share. Thank you in advance.
EDIT: This is the html for my table:
<table id="tblData"class="table table-striped" >
<thead>
<tr><th>Domain Name</th><th>Full name</th><th style="text-align:center">Email</th></tr>
</thead>
<tbody data-bind="foreach: employee">
<tr>
<td style="width:100px" data-bind="text: username"></td>
<td style="width:120px"data-bind="text: fullName"></td>
<td style="text-align:right;width:120px" data-bind="text: email"></td>
</tr>
</tbody>
</table>

Don't do vanilla javascript DOM manipulations if you use knockout. Filtering is quite simple, you just have to keep an observableArray of all your elements, and declare a computed that returns the filtered elements.
For a simple example, see this model:
function Model() {
var self = this
this.input = ko.observable("");
this.all = ko.observableArray(["John","James","Mark"]);
this.filtered = ko.computed(function() {
return ko.utils.arrayFilter(self.all(), function(item) {
return item.indexOf(self.input()) !== -1;
});
});
}
with this HTML:
<input placeholder="Type to filter" data-bind="value: input, valueUpdate: 'keyup'"/>
<ul data-bind="foreach: filtered">
<li data-bind="text: $data"></li>
</ul>
You can test it here: http://jsfiddle.net/qFYbW/1/

Related

for loop not returning data when iterating through multiple values

I have a function which its job its to delete a value selected by the user, it loops through an array of selected values and the data, when it iterates through both it checks if the selected value is equal to any property in the data, if it returns true, I get a new array of data.
When I select one value in the checkbox I do indeed see the correct data being returned, but when I select multiple my function does not work. I have being pondering all day, I will really appreciate any input.
HTML
<div class="ibox-content">
<table class="table table-striped">
<thead>
<tr>
<th>Select</th>
<th class="col-xs-3">Issue Description</th>
<th class="col-xs-3 text-center">Category</th>
<th class="col-xs-3 text-center">Jira</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="issue in wiq.data">
<td><input type="checkbox" checklist-model="wiq.selections" checklist-value="issue.jira"> </td>
<td>{{issue.issue}}</td>
<td class="text-center">{{issue.description}}</td>
<td class="text-center">{{issue.jira}}</td>
</tr>
<pre>{{wiq.selections}}</pre>
</tbody>
</table>
<form>
<button type="button" name="button" class="btn btn-success pull-right" style="margin-top:2em;" ng-click="wiq.acknowledge()">Acknowledge</button>
</form>
</div>
Controller.js
ctrl.selections = []
ctrl.data = [
{issue:"CMDY has issue", description:"issue",jira:"CDVR-173"},
{issue:"SPK has issue", description:"issue",jira:"CDVR-125"}
]
ctrl.acknowledge = function() {
var data = [];
for (var i = 0; i < ctrl.data.length; i++) {
for (var j = 0; j < ctrl.selections.length; j++) {
if (ctrl.selections[j] != ctrl.data[i].jira) {
data.push(ctrl.data[i]);
}
}
}
ctrl.data = data;
console.log(ctrl.data)
};
When you have multiple selection, you loop multiple times, so you call "push" too many times.
try :
function filterData(somedata, selections) {
return somedata.filter(item => (selections.findIndex(o => o === item.jira ) == -1));
};
then
ctrl.acknowledge = function() {
ctrl.data = filterData(ctrl.data,ctrl.selections);
console.log(ctrl.data)
};
Simple one liner will solve your problems, try this:
ctrl.acknowledge = function() {
var data = ctrl.data.filter(val => ctrl.selections.indexOf(val.jira) === -1);
ctrl.data = data;
console.log(ctrl.data)
};
Here's jsfiddle:
http://jsfiddle.net/pegla/xj9gqqxn/7/

JQuery .appendTo() adding rows multiple times

Bit of a strange problem, but I'm not sure how to fix it.
While inside of the code below, I'm trying to take all of the element of a table a sort them based on the row heading that was clicked.
The starting HTML:
<table id="PracticeTable" class="col-md-10 col-md-offset-2">
<tr id="Column Names">
<td class="ClientID col-xs-1">Client ID</td>
<td class="ClientName col-xs-1">Client Name</td>
<td class="PracticeID col-xs-1">Practice</td>
</tr>
#for (int i = 0; i < ViewBag.ClientDatabase.getDatabase().Count; i++)
{
#:<tr class="DataRow">
<td class="ClientID col-xs-1" data-name="#ViewBag.ClientDatabase.getByIndex(i).Id">#ViewBag.ClientDatabase.getByIndex(i).Id</td>
<td class="ClientName col-md-2 col-xs-1" data-name="#ViewBag.ClientDatabase.getByIndex(i).getClientName">#ViewBag.ClientDatabase.getByIndex(i).getClientName</td>
<td class="PracticeIDs col-xs-1" data-name="#ViewBag.ClientDatabase.getByIndex(i).getPracticeID">#ViewBag.ClientDatabase.getByIndex(i).getPracticeID</td>
<td class="EditButtons col-xs-1" data-bind="visible: editVisible">Edit</td>
#:</tr>
}
Example of one of the click functions:
jQuery(document).ready(function () {
$(".ClientName").click(function () {
alert("Name Clicked");
var clients = $(".DataRow");
var compareLine = $(clients).children(".ClientName");
compareLine.sort(function (a, b) {
var an = $(a).attr("data-name");
var bn = $(b).attr("data-name");
alert(an + " " + bn);
if (an > bn) {
return 1;
}
if (an < bn) {
return -1;
}
return 0;
});
compareLine.detach().appendTo(clients);
});
});
When I attempt to append the lines as above, the names from Client Name appear in every row. Is there something I'm missing? I tried to loop through the data in compareLine to add them individually, but I couldn't figure it out.
EDIT: Removing duplicated code from a fix attempt that wasn't fully removed.
If you want to sort the rows, I think you just need to make a small change:
jQuery(document).ready(function () {
$(".ClientName").click(function () {
alert("Name Clicked");
var clients = $(".DataRow");
clients.sort(function(a, b) {
var an = $(a).children(".ClientName").attr("data-name");
var bn = $(b).children(".ClientName").attr("data-name");
alert(an + " " + bn);
if (an > bn) {
return 1;
}
if (an < bn) {
return -1;
}
return 0;
});
clients.detach().appendTo("#PracticeTable");
});
});
Seems like your sort function was designed to be working with the rows, but you were sorting a collection of the columns.

Knockout.js Pre-seed ObservableArray

I'm not sure if I'm trying to do too much here, but here is the scenario. I have an asp.net mvc page that, on the first time loading, returns a table of data in a view using the standard foreach mechanisms in the mvc framework. If the user has javascript enabled, I want to use knockout to update the table going forward. Is there a way to have knockout read the data from the dom table and use that data as the initial observable collection. From then on out, I would use knockout and ajax to add, edit, or delete data.
In a nutshell, I need to parse an html table into a knockout observable collection.
I've had a go at coding this up:
Here's the basic markup:
<table id="table" data-bind="template: { name: 'table-template' }">
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody>
<tr>
<td>Richard</td>
<td>Willis</td>
</tr>
<tr>
<td>John</td>
<td>Smith</td>
</tr>
</tbody>
</table>
<!-- Here is the template we'll use for re-building the table -->
<script type="text/html" id="table-template">
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: surname"></td>
</tr>
</tbody>
</script>
Javascript:
(function() {
function getTableData() {
// http://johndyer.name/html-table-to-json/
var table = document.getElementById('table');
var data = [];
var headers = [];
for (var i = 0; i < table.rows[0].cells.length; i++) { 
headers[i] = table.rows[0].cells[i].innerHTML.toLowerCase().replace(/ /gi, '');
}
// go through cells
for (var i = 1; i < table.rows.length; i++) {
var tableRow = table.rows[i];
var rowData = {};
for (var j = 0; j < tableRow.cells.length; j++) {
rowData[headers[j]] = tableRow.cells[j].innerHTML;
}
data.push(rowData);
}
return data; 
}
var Vm = function () {
this.data = ko.observableArray(getTableData());
};
ko.applyBindings(new Vm(), document.getElementById('table'));
})();
You can extend this concept using the mapping plugin to create observables for each row: http://knockoutjs.com/documentation/plugins-mapping.html
View a demo here: http://jsfiddle.net/CShqK/1/
EDIT: I'm not saying this is the best approach, as it can be costly to traverse a large table to get the data. I would probably just output the JSON in the page as suggested by others in this thread.
How about just feeding your observable array with the data instead of parsing the html table
myArray: ko.observableArray(#Html.Raw(Json.Encode(Model.myTableData)))
If you really need to go the parsing the html way, you can use the following code
var tableData = $('#myTable tr').map(function(){
return [
$('td,th',this).map(function(){
return $(this).text();
}).get()
];
}).get();
$(document).ready(function() {
var myData = JSON.stringify(tableData);
alert(myData)
});
Here is a fiddle showing the code in action:
http://jsfiddle.net/FWCXH/

Remove table row by finding content with Javascript

i have this code:
<table>
<tbody>
<tr>
<td align="left">X</td>
<td>X1</td>
</tr>
<tr>
<td width="150" align="left">Y</td>
<td>Y1</td>
</tr>
<tr>
<td align="left">Status: </td>
<td colspan="4">
<select name="status" size="1">
<option selected="selected" value="2">one</option>
<option value="1">two</option>
</select>
</td>
</tr>
<tr>
<td width="150" align="left">Z</td>
<td>Z1</td>
</tr>
</tbody>
</table>
and want to remove with Javascript this line:
<tr>
<td align="left">Status: </td>
<td colspan="4">
<select name="status" size="1">
<option selected="selected" value="2">one</option>
<option value="1">two</option>
</select>
</td>
</tr>
the problem is that i don't have any id's or classes for identification.
is there a way to remove the row by searching "Status: " or name="status" with javascript for Firefox only (using it for Greasemonkey)?
i can't use jQuery and i can't edit the code to set any id's
best regards
bernte
If you can afford not being compatible with IE7, You can do that :
​var elements = document.querySelectorAll('td[align="left"]');
for (var i=0; i<elements.length; i++) {
var text = elements[i].textContent || elements[i].innerText;
if (text.trim()=='Status:') {
elements[i].parentNode.parentNode.removeChild(elements[i].parentNode);
}
}
Demonstration
To be compatible with IE7, you'd probably have to iterate on all rows and cells, which wouldn't really be slower but would be less clear.
Note that I used trim which doesn't exist on IE8. To make it work on this browser if needed, you might add this usual fix (from MDN) :
if(!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g,'');
};
}
function removeRowByCellValue(table,cellValue) {
var cells = table.getElementsByTagName("TD");
for(var x = 0; x < cells.length; x++) {
// check if cell has a childNode, prevent errors
if(!cells[x].firstChild) {
continue;
}
if(cells[x].firstChild.nodeValue == cellValue) {
var row = cells[x].parentNode;
row.parentNode.removeChild(row);
break;
}
}
}
First get a reference to your table, since you do not have an ID you can use getElementsByTagName.. (here i'm assuming that it is the first table in your document)
var myTable = document.getElementsByTagName("table")[0];
Then you can invoke the function with the following parameters
removeRowByCellValue(myTable,"Status: ");
var xpathResult = document.evaluate("//td[starts-with(.,'Status:')]", document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var element = xpathResult.singleNodeValue.parentNode;
while (element.firstChild)
element.removeChild(element.firstChild);
Jsbin http://jsbin.com/urehuw/1/edit
And if you'd like to be compatible with older IE:
function closest(el, tag) {
if (!el || !tag) {
return false;
}
else {
return el.parentNode.tagName.toLowerCase() == tag ? el.parentNode : closest(el.parentNode, tag);
}
}
// gets all 'select' elements
var sel = document.getElementsByTagName('select');
// iterates through the found 'select' elements
for (var i=0, len = sel.length; i<len; i++) {
// if the 'select' has the name of 'status'
if (sel[i].name == 'status') {
// uses the closest() function to find the ancestor 'tr' element
var row = closest(sel[i], 'tr');
// access the 'tr' element's parent to remove the 'tr' child
row.parentNode.removeChild(row);
}
}
JS Fiddle demo.
simply use this
var gettag =document.getElementsByTagName("tr")[3] ; // the third tag of tr
document.removeChild(gettag);

Nested tag name with getElementsByTagName doesn't work

I have the following div that contains the table and its data queried from database
<div id="content">
<table>
<tbody>
<tr>
<th class="header" colspan="2">Food items include:</th>
</tr>
<tr>
<td id="15" class="fruits">Papaya+salt</td>
<td><p>This includes papaya and salt</p></td>
</tr>
<tr>
<td class="meat">Baked chicken</td>
<td><p>This includes a chicken thide and kethup</p></td>
</tr>
<tr>
<td id="1" class="Juices">Strawberry Sting</td>
<td><p>Sugar, color and water</p></td>
</tr>
<table>
</div>
That table is defined in a page.aspx
and here is my code used to sort that table data alphabetically
OldFunc = window.onload;
window.onload = OnLoad;
function OnLoad(){
try{
var pathName = window.location.pathname.toLowerCase();
if( pathName=="/Resources/Glossary.aspx") {
sort_it();
}
OldFunc();
}
catch(e) {
}
}
function TermDefinition(def_term,def_desc)
{
this.def_term=def_term;
this.def_desc=def_desc;
}
function sort_it()
{
var gloss_list=document.getElementsByTagName('td');
var desc_list=document.getElementsByTagName('td p');
var gloss_defs=[];
var list_length=gloss_list.length;
for(var i=0;i<list_length;i++)
{
gloss_defs[i]=new TermDefinition(gloss_list[i].firstChild.nodeValue,desc_list[i].firstChild.nodeValue);
}
gloss_defs.sort(function(a, b){
var termA=a.def_term.toLocaleUpperCase();
var termB=b.def_term.toLocaleUpperCase();
if (termA < termB)
return -1;
if (termA > termB)
return 1;
return 0;
})
for(var i=0;i<gloss_defs.length;i++)
{
gloss_list[i].firstChild.nodeValue=gloss_defs[i].def_term;
desc_list[i].firstChild.nodeValue=gloss_defs[i].def_desc;
}
}
Please lookat the the two getElementsByTagName, I think I am misuse its content since nothing is done on the output.
Invalid:
desc_list=document.getElementsByTagName('td p');
You can't pass a css selector to that function, only a tag name like div\ span input etc'.
You might want to use:
desc_list = $('td p');
Since you tagged the question with jQuery, or document.querySelectorAll for vanilla js:
desc_list = document.querySelectorAll('td p');

Categories

Resources