The code below is my latest attempt. There have been many others:
Javascript:
...
var issueArray = [];
_.each(issueGroups, function(i) {
var x = {
issue: i[0].issue,
comment: i[0].comment,
count: i.length,
new_row: 1
};
issueArray.push(x);
});
issueArray[0].new_row = 0;
var x = {
test: t[0].test,
issues: issueArray,
rowspan: issueArray.length
};
finalResult.push(x);
});
return finalResult;
The important thing to note here is that for each element of finalResult there is an array called issues that has a member called new_row which is true for all but the first row.
Template:
...
<tbody>
{{#each failuresByTest}}
<tr>
<td rowspan="{{rowspan}}">{{test}}</td>
{{#each issues}}
{{#if new_row}}
</tr>
<tr>
<td>{{issue}}</td>
<td>{{comment}}</td>
<td>{{count}}</td>
{{else}}
<td>{{issue}}</td>
<td>{{comment}}</td>
<td>{{count}}</td>
{{/if}}
{{/each}}
</tr>
{{/each}}
</tbody>
...
My data is such that the first element of finalResult has 3 issues. I expect it to look like this:
<tr>
<td rowspan="3">Test1</td>
<td>123</td>
<td>Bug 1</td>
<td>2</td>
</tr>
<tr>
<td>456</td>
<td>Bug 2</td>
<td>21</td>
</tr>
<tr>
<td>654</td>
<td>Bug 4</td>
<td>3</td>
</tr>
But it ends up looking like this:
<tr>
<td rowspan="3">Test1</td>
<td>123</td>
<td>Bug 1</td>
<td>2</td>
<td>
<table>
<tr>
<td>456</td>
<td>Bug 2</td>
<td>21</td>
</tr>
<tr>
<td>654</td>
<td>Bug 4</td>
<td>3</td>
</tr>
</table>
</td>
</tr>
However Meteor doesn't like this. I get:
=> Errors prevented startup:
While processing files with templating-compiler (for target web.browser):
client/templates/runs/run_page.html:128: Unexpected HTML close tag
</tr> <tr> <td><a h...
This side-tracked me so badly that I ended up putting the row tags wrongly to fix this and then it displayed wrongly. I have now edited the question accordingly since this is my real problem.
How do I solve this and persuade Meteor that actually I do know better than it!
Your template logic is flawed:
You are trying to insert a new <tr> inside a <td>. Since <tr> can only be contained inside tables, the browser will automatically add a table around it so that the html is valid.
<tbody>
{{#each failuresByTest}}
<tr>
<td rowspan="{{rowspan}}">{{test}}</td>
{{#each issues}}
{{#if new_row}}
<tr>
<td>
So depending on how each failureBytest should be rendered, you should either create a new table for each failure, or create a new row outside the previous row inseatd of inside it's cells.
I worked around Meteor's controlling behaviour with the following.
Javascript:
var issueArray = [];
_.each(issueGroups, function(i) {
var x = {
issue: i[0].issue,
comment: i[0].comment,
count: i.length,
new_row: 0
};
issueArray.push(x);
});
issueArray[0].new_row = 1;
_.extend(issueArray[0], {rowspan: issueArray.length});
_.extend(issueArray[0], {test: t[0].test});
var x = {
issues: issueArray
};
finalResult.push(x);
Note that the test name and rowspan have now moved into the first array element. I can probably remove new_row.
Template:
<tbody>
{{#each failuresByTest}}
{{#each issues}}
<tr>
{{#if new_row}}
<td rowspan="{{rowspan}}">{{test}}</td>
{{/if}}
<td>{{issue}}</td>
<td>{{comment}}</td>
<td>{{count}}</td>
</tr>
{{/each}}
{{/each}}
</tbody>
I really do not like putting "parent data" in the first array element. I prefer for all elements to be of exactly the same type/structure without duplicate/extra information added.
Related
I want to throw the last 2 digits of the clock, I use jquery but it always fails in looping
Only one row changes, i want to change all rows.
this is my code
$(document).ready(function(){
var jam = $("#jamberangkat").text();
var datearray = jam.split(":");
var newjam = datearray[0] + ":" + datearray[1];
return $("#jamberangkat").text(newjam);
});
<table>
<tbody>
<tr>
<td id="jamberangkat" style="width:20%;">{{ $data->jam }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
this is my result
It seems like you want to iterate through all the <td> with the ID of jamberangkat. Remember that IDs must be unique throughout the DOM, so you will need to use a class to identify that cell that you want to change.
Most jQuery methods are actually implicit iterators, so you can simply use a callback in $('.jamberangkat').text(<callback>) to perform the text filtering for you for all elements that matches the selector. What you want in that filter is:
Extract the text value of the element. This is accessible as the second argument in the .text() method: refer to the jQuery API documentation for .text()
Split the text by : as you have intended
Keep only the first two elements (in your code I deduced you only want to keep the first and second fragment of the array). This can be done by using .slice(0, 2)
Rejoin the spliced array (which now retains only the first two elements) using .join(':').
See proof-of-concept example below:
$(document).ready(function(){
$(".jamberangkat").text(function(index, txt) {
return txt.split(':').slice(0,2).join(':');
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<td class="jamberangkat" style="width:20%;">12:34:56</td>
<td>Random text</td>
</tr>
<tr>
<td class="jamberangkat" style="width:20%;">12:34:56</td>
<td>Random text</td>
</tr>
<tr>
<td class="jamberangkat" style="width:20%;">12:34:56</td>
<td>Random text</td>
</tr>
<tr>
<td class="jamberangkat" style="width:20%;">12:34:56</td>
<td>Random text</td>
</tr>
</tbody>
</table>
I have a register that lists people alphabetically from A to Z... Each person, Mr A for example, has a set of corresponding data Histories.
I have a table with a simple ng-repeat that displays the data for 12 cells (representing 12months).
If 12 months/cells worth of data is supplied, i show all the data, if only 5 months of data (anything less than 12) is provided, i call a service srvEmptyCells that calculates the remaining cells and displays in a darker colour.
The problem with this is, that i notice i am repeating the ng-repeat:
emptyCell in getEmptyCells
many many times which is impacting page performance given i have over 100 users.
Is there a way i save the number of empty cells for each particular user? And remove the need for the extra ng-repeats? Would a directive improve things?
Heres a plunker: http://plnkr.co/edit/2UKDD1fvfYGMjX9oJqVu?p=preview
HTML:
<table ng-repeat="data in myData" class="my-table">
<caption>
{{ data.Name }}
</caption>
<tbody>
<tr>
<th>Rate</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Rate}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
<tr>
<th>Effort</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Effort}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
<tr>
<th>Advance</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Advance}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
<tr>
<th>Previous</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Previous}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
<tr>
<th>Current</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Current}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
<tr>
<th>Code</th>
<td ng-repeat="history in data.Histories.slice(0, 12)" class="my-table-cell">
{{history.Code}}
</td>
<td ng-repeat="emptyCell in getEmptyCells(data.Histories.slice(0, 12).length)" class="empty"></td>
</tr>
</tbody>
</table>
JS:
app.controller('MainCtrl', function($scope, $http, factoryGetJSONFile, srvEmptyCells) {
$scope.name = 'World';
factoryGetJSONFile.getMyData(function(data) {
$scope.myData = data.MyData.Entries;
});
$scope.getEmptyCells = srvEmptyCells.getEmptyCells;
});
app.factory('srvEmptyCells', function() {
return {
getEmptyCells: function(len) {
var emptyCells = [];
for(var i = 0; i < 12 - len; i++){
emptyCells.push(i);
}
return emptyCells;
}
};
});
You could modify $scope.myData by adding a cached version of data.Histories.slice(0, 12) and getEmptyCells(data.Histories.slice(0, 12).length) and using ng-repeat on those instead.
This way you'd save repeating those calls over and over again.
A better way, if you can modify the response, would be to do this service side rather than on the client. Why to return more than 12 histories if you won't need them?
It is hard to know if this will fix your performance issue, as there are many ng-repeats per row, but it will be faster than before at least.
Sample on caching client side;
factoryGetJSONFile.getMyData(function(data) {
$scope.myData = data.MyData.Entries;
for (int i=0; i < $scope.myData.length; i++) {
var current = $scope.myData[i];
current.nonEmptyCells = current.Histories.slice(0, 12);
current.emptyCells = srvEmptyCells.getEmptyCells(current.nonEmptyCells.length);
}
});
then change your ng-repeats in the html to:
<td ng-repeat="history in data.nonEmptyCells" class="my-table-cell">
and
<td ng-repeat="emptyCell in data.emptyCells" class="empty"></td>
updated plunkr: http://plnkr.co/edit/92S2rNNhBEyXZVsCI2M8?p=preview
I believe you can reduce the number of ng-repeat's to only 2.
You just need to change a little bit your approach. Instead of doing splice on every ng-repeat, use a list/array of indexes with a predefined length. Then, iterate over that list of indexes and for each index check if there is an item in the same position of the data.Histories array. If there is, then it's a 'my-table-cell' otherwise it's an 'empty' cell (ng-class helps you defining that).
Then use the same logic for the fields, instead of having 1 ng-repeat for each field, try to define a list/array of fields and then use only 1 ng-repeat to iterate over that list.
The final result should be something like this:
<table ng-repeat="data in myData" class="my-table">
<caption>
{{ data.Name }}
</caption>
<tbody>
<tr ng-repeat="field in fields">
<th>{{field}}</th>
<td ng-repeat="idx in indexes" ng-class="{ 'my-table-cell': data.Histories[idx][field], 'empty': !data.Histories[idx][field]}">
{{data.Histories[idx][field]}}
</td>
</tr>
</tbody>
</table>
And on the MainCtrl add the following lines:
// the list of indexes could be populated dynamically...
$scope.indexes = [0,1,2,3,4,5,6,7,8,9,10,11];
$scope.fields = ['Rate', 'Effort', 'Advance', 'Previous', 'Current', 'Code'];
Demo
I am not so good in English, to express myself, so I show it with examples to you!
Here is my HTML table with code:
<table border="1">
<tr id="tr1_1">
<td rowspan="2">1</td>
<td>tr1.1 td2</td>
<td>tr1.1 td3</td>
<td rowspan="2">
up/
down
</td>
</tr>
<tr id="tr1_2">
<td>tr1.2 td2</td>
<td>td1.2 td3</td>
</tr>
<tr id="tr2_1">
<td rowspan="2">2</td>
<td>tr2.1 td2</td>
<td>tr2.1 td3</td>
<td rowspan="2">
up/
down
</td>
</tr>
<tr id="tr2_2">
<td>tr2.2 td2</td>
<td>td2.2 td3</td>
</tr>
</table>
(You can see the result here)
For example, if I click on the second two rows' "up" link, the result should be this:
<table border="1">
<tr id="tr2_1">
<td rowspan="2">2</td>
<td>tr2.1 td2</td>
<td>tr2.1 td3</td>
<td rowspan="2">
up/
down
</td>
</tr>
<tr id="tr2_2">
<td>tr2.2 td2</td>
<td>td2.2 td3</td>
</tr>
<tr id="tr1_1">
<td rowspan="2">1</td>
<td>tr1.1 td2</td>
<td>tr1.1 td3</td>
<td rowspan="2">
up/
down
</td>
</tr>
<tr id="tr1_2">
<td>tr1.2 td2</td>
<td>td1.2 td3</td>
</tr>
</table>
(You can see the final result here)
So how could I do this? I know the prev() and before() methods in javascript, but it merge only 1 row with 1 row, but I want to merge 2 rows WITH 2 rows!
I hope, someone can help me! Thank you!
Try this jQuery code :
$('.up').click(function() {
tr = $('tr[id^="'+$(this).closest('tr').attr('id').slice(0, 3)+'"]')
tr.insertBefore(tr.eq(0).prev().prev())
return false;
})
$('.down').click(function() {
tr = $('tr[id^="'+$(this).closest('tr').attr('id').slice(0, 3)+'"]')
tr.insertAfter(tr.eq(1).next().next())
return false;
})
And have a look to your fiddle edited : http://jsfiddle.net/lulu3030/UQz8u/6/
Just some explanations :
closest('tr') method find the nearest parent which has a tr tag
slice(0, 3) get the first 3 characters of a string
=> variable tr selects all elements which has the same 3 first id characters
insertBefore and insertAfter methods allows to move selected elements
Could be done like that too:
DEMO
$(function () {
$('.up, .down').on('click', function () {
var $tr = $(this).closest('tr'),
$flag = $('<tr/>').insertBefore($tr),
$rows = $tr.add($tr.next('tr')).detach()
methods = $(this).is('.up') ? ['insertBefore', 'prevAll'] : ['insertAfter', 'nextAll'];
if ($flag[methods[1]]('tr').eq(1).length) {
$rows[methods[0]]($flag[methods[1]]('tr').eq(1));
$flag.remove();
} else $flag.replaceWith($rows);
});
});
See the code below, If you click on the sub-title row it then will hide the rows with it. It work well.
On the second sub-title row (<tr class="sub-title default-hide">) - I want this to toggle/hidden by default when the page loaded.. How to do this without writing duplicate code like below?
$(".sub-title").on("click",function() {
tr = $(this).find('span').hasClass("arrow2");
trSpan = $(this).find('span');
$(this).nextUntil(".sub-title").each(function() {
if (!$(this).hasClass('head-order')) {
$(this).toggle();
if (tr) {
trSpan.removeClass('arrow2').addClass('arrow1');
} else {
trSpan.removeClass('arrow1').addClass('arrow2');
}
}
});
});
HTML
<table border="1">
<tbody>
<tr class="head">
<td> title </td>
</tr>
<tr class="sub-title">
<td>Sub Title 1 <span class="arrow2"> </span></td>
</tr>
<tr> <td>Item 1</td> </tr>
<tr> <td>Item 2</td> </tr>
<tr> <td>Item 3</td> </tr>
<tr class="sub-title default-hide">
<td>Sub Title 2 <span class="arrow2"></span></td>
</tr>
<tr> <td>Item 4</td> </tr>
<tr> <td>Item 5</td> </tr>
<tr> <td>Item 6</td> </tr>
</tbody>
</table>
I created a jsFiddle example with the information you provided.
I edited the code a bit, using a default arrow-class and just adding the class close to it, to define the new style, which should make the code a little shorter.
$(".sub-title").on("click",function() {
var trSpan = $(this).find('span');
trSpan.toggleClass('closed');
$(this).nextUntil(".sub-title").each(function() {
if (!$(this).hasClass('head-order')) {
$(this).toggle();
}
});
});
To make the "default-hidden" - element closed on pageload, all I do is to trigger a click-event on it after binding the click-Handler.
$('.default-hide').trigger('click');
See the fiddle for a working example
Create a named function and call it a couple times:
var toggleArrow = function(el) {
tr = $(el).find('span').hasClass("arrow2");
trSpan = $(el).find('span');
$(el).nextUntil(".sub-title").each(function() {
if (!$(el).hasClass('head-order')) {
$(el).toggle();
if (tr) {
trSpan.removeClass('arrow2').addClass('arrow1');
} else {
trSpan.removeClass('arrow1').addClass('arrow2');
}
}
});
};
$(".sub-title").on("click", function(){ toggleArrow(this); });
$(".default-hide").each(function(i, el){ toggleArrow(this); });
You can trigger the click event manually for the default-hide rows.
Like this
$('.default-hide').trigger('click');
I am having problem updating table cell value using jQuery 1.4.2. it all works in Firefox and Safari but IE8 and IE9 is simply not doing anything. There is no warning, error or anything that would give me some hint where to look for it.
Table looks following:
<table id="test">
<tr id="1">
<td id="name">sample name</td>
<td id="schedule">sample value</td>
<td id="day">sample value</td>
</tr>
<tr id="2">
<td id="name">sample name</td>
<td id="schedule">sample value</td>
<td id="day">sample value</td>
</tr>
<tr id="3">
<td id="name">sample name</td>
<td id="schedule">sample value</td>
<td id="day">sample value</td>
</tr>
</table>
I am executing ajax call and getting json data:
{"Test": [
{"id":"1", "name":"John", "day":"Monday"},
{"id":"2", "name":"Marry", "day":"Thursday"}
]}
once data is received there is a loop which iterates through the json dataset and updates appropriate column with received data as following:
$.each(json.Tests, function(){
/* update test with details */
var test = this.hash;
/*set values for each test */
$("table#test tr[id=" + test + "]").find("#name").html(this.name);
$("table#test tr[id=" + test + "]").find("#schedule").html(this.status);
$("table#test tr[id=" + test + "]").find("#day").html(this.changed);
});
As I mentioned, this has been tested in Safari and Firefox all works fine but IE8 and IE9 seems not to do anything.
I think the id attribute should be reserved for unique identifiers in my opinion. How about changing the id attribute of the td elements to a class attribute or even name attribute. I suspect that IE is getting confused.
Also, if you keep ids unique and change the id attribute of the td to a class then you can change your code to something like:
$("#" + test + " td.name").html(this.name);
And because a number could represent pretty much anything also prefixing those ids with some sort of identifier prefix would be good. Something like:
$("#thing-" + test + " td.name").html(this.name);
And the html would look like this:
<table id="test">
<tr id="thing-1">
<td class="name">sample name</td>
<td class="schedule">sample value</td>
<td class="day">sample value</td>
</tr>
<tr id="thing-2">
<td class="name">sample name</td>
<td class="schedule">sample value</td>
<td class="day">sample value</td>
</tr>
<tr id="thing-3">
<td class="name">sample name</td>
<td class="schedule">sample value</td>
<td class="day">sample value</td>
</tr>
</table>
Hope that helps!
Ids aren't supposed to start with a number. Perhaps IE9 isn't as forgiving as the other browsers.
Have you an URL for us to Test your Script?