Views and appending table rows in backbone.js - javascript

I am trying to create a code using backbone.The user inputs text into two fields Item and price. This info then is appended to a table and creates a new row for every input pair entered.
I've created a template for the table. My question when creating my view since the data from the text fields of item and price need to be sorted into the corresponding cells, how do I append the new rows correctly. Can I just make a view for the row? Or do I need to do it for each cell?
<div id="container">
<script id="grocery-list-template" type="text/template">
<table>
<thead>
<tr>
<th>Item</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td><%=something to get item%></td>
<td><%=something to get the price%></td>
<td></td>
<td><button class="complete"></button><button class="remove"></td>
</tr>
</tbody>
</table>
</script>
</div>
This is essentially what Im trying to do, but I dont know how to specify the row AND the cell? Do I have to make a view for each cell using tagname: td or can I just keep tagname: tr and route the data into the apropriate cells? I found an example that just appended a list using li elements so I was trying to adapt some of it to my code
var TableView = Backbone.View.extend({
tagname: "tr",
classname: "itemAndprice",
template:_.template($("grocery-list-template").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.attr("id", this.model.id);
if (this.model.get('done')) this.$el.addClass('done');
$("#GroceryList").append(this.$el);
return this;
},

You can create a view for the whole row. For each row you would need a row-model with item and price attributes which you would pass into the row-view's constructor. This pretty much looks like the TableView in your code (except I would expect that to be called RowView instead, the TableView being a different, 'parent' view)
Given that your model contains item and price attributes, the row-view-template should contain
....
<td><%=item%></td>
<td><%=price%></td>
....
Then this.model.toJSON() would give you a hash with item and price attributes which would be passed into this.template, populating <%=item%> and <%=price%> with the relevant values.
You can also take a look at underscore's template function for further information.
Note however that the row-view and the table-view should be two different views. From your code it seems that you have a template appropriate for a table-view (which also contains a single row that shouldn't be there) and a view appropriate as a row-view.
Having two views you would pass a collection of row-models in the table-view which would iterate over the collection and add a row(-view) for each model. When the user creates a new item you'd add a model into the collection and re-render the table.
Hope this helps.

Related

html table is duplicating defaultValue's for no apparent reason

I have some data that is displayed in a table. When I add a table data cell (td) with JS the defaultValue and innerText of the previous table data cell (td) is being copied in the new table data (td). I don't understand why this happens? I just add a new html element which, in essence, has nothing to do with the already existing element I would say.
const [dataSet, setDataSet] = useState([
{
City: "Amsterdam",
Provence: "North-Holland"
},
{
City: "Rotterdam",
Provence: "South-Holland"
}
]);
const newCity = () => {
const city = [
{
City: "Groningen",
Provence: "Groningen"
}
];
setDataSet(city);
};
<button onClick={newCity}>Add city</button>
<table>
<tr>
<th>City</th>
<th>Provence</th>
</tr>
{dataSet.map(city => (
<tr>
<td>
<input defaultValue={city.City}/>
</td>
<td>
<input defaultValue={city.Provence}/>
</td>
</tr>
)}
</table>
So when I click on the 'newCity button' a new table row (tr) is added, but instead of populating the table data cells (td) with the correct data (City: 'Groningen', Provence: 'Groningen') it populates the td with the data from the previous table row (tr) (City: 'Amsterdam', Provence: 'North-Holland'). This seems really odd to me, since I only add a new html element that has no relation to the table row (tr) before it exempt for it being in the same table.
Here is a codebox link to see the behavior in action:
https://codesandbox.io/s/dry-water-rss2m8?file=/src/App.js
Why does it behave like this?
There are two important things to take into consideration here.
First, your program is outputting a warning:
Warning: Each child in a list should have a unique "key" prop.
Always deal with warnings. They are there for a reason!
Second, you are dealing with a default (as opposed to current) value.
When you replace the array with a new array, React generates new JSX and compares it to the existing DOM.
The first row has the same (lack of) key so it changes the default values of the existing row. (The second row no longer exists so it removes it).
Changing the default values doesn't have any effect through: The inputs already have values, which they continue to display.
You could give the generated rows keys:
<tr key={something}>
… but you will need to figure out what the something is.
You might use the city name, but it seems like that is a piece of data which might change — so changing the city name would make it look like a new row and not an update to an existing row.
You might consider using the uuid module to generate a unique ID when the object is created.
You could also make the inputs controlled by passing them values instead of default values. This would require that you update the value you pass to them as they are edited.
You need to set a key on the item
{dataSet.map((city) => (
<tr key={city.City}>
...
etc
I guess it is because you are working with uncontrolled components.
If you want your input values to reflect state changes you need to use the value and not the defaultValue, and you also probably want to have an onChange handler.
<input value={city.City} />
First, the native Input element doesn't have defaultValue attribute. Look at the attributes list in MDN. It has only value and placeholder.
Secondly, if you want to add new city to the dataSet, you must merge the city-object to the array, not replace it.
setDataSet(currentDataSet => {
return [...currentDataSet, city]
})

insertBefore not updating rowIndex/nextSibling properties

It may sound stupid or even trivial for most experienced users, but I just landed a few hours ago on front-end javascript and I must say I am a bit puzzled with the behavior of the insertBefore javascript function.
My intention here is plain and simple: I have a table with its rows and cells, and in each row I have a cell with a button with the only purpose of duplicating that cell (with all its contents) and place the new duplicated cell right next to the original one.
I have a javascript function for it such like this one:
// id -> the id of the table I want the row to be added
// caller -> the object of the element that called the function
function duplicateRow(id, caller)
{
const table = document.getElementById(id);
const row = caller.parentNode.parentNode; // Caller is always a button inside a cell inside a row
const clone = row.cloneNode(true);
table.insertBefore(clone, row.nextElementSibling);
}
This function is called like this (from an extract of my HTML):
<tr>
<td>
<input type="text" name="competence-name">
</td>
<td>
<button name="duplicate-row-button" onclick="duplicateRow( 'competencies-table', this )"></button>
</td>
</tr>
So, what I would expect from it is that, at each click on the duplicate row button, it would create an exact copy of the row where the button is being clicked and add it right after that row.
My problem here is not with the duplicating (that is done just right and smooth as one would expect) but with where the new row is placed:
The first time, when there is only one row, it is placed at the end (since nextSibling is null).
The second time clicking the button on the first row (despite now having a sibling right after it), the new row is again placed at the end of the table (as if nextSibling for the first row was still null).
And so on (even strager placements happen when mixing duplications with the newly added rows).
Shouldn't the nextSibling and/or rowIndex properties be updated when adding a new node to the DOM? Is there a way of forcing them to update? What is it that I have wrong? My code, my understanding of how it should work?
I am surely open to any possible explanation/solution/alternative to achieve what I need, and thank you all in advance!
The problem is that initial table row is wrapped in a tbody element (for which you can omit both start and end tag), which is required according to the content model of tables. However, when you programmatically add more rows, they are inserted outside the tbody and your initial row is the only child of that implicit tbody, so the DOM tree looks like this:
<table>
<tbody>
<tr></tr>
</tbody>
<tr></tr>
<tr></tr>
</table>
To solve it I suggest to add a clone to cloned row's parent:
function duplicateRow(caller){
const row = caller.parentNode.parentNode; // Caller is always a button inside a cell inside a row
const clone = row.cloneNode(true);
row.parentNode.insertBefore(clone, row.nextElementSibling);
}
<table id="competencies-table">
<tr>
<td>
<input type="text" name="competence-name">
</td>
<td>
<button name="duplicate-row-button" onclick="duplicateRow( this )">Duplicate</button>
</td>
</tr>
</table>

Deleting a Row from Datatable in Jquery

I used Backbone collection to create a table and applied jquery data-table for pagination. I have an icon to delete the row. When clicked it is deleting from collection but deleted row is still present in data table.
More information:
I have the collection with person details (firstname:Tony). And this data will sit in template "{{firstname}}". In that way I'm setting the entire row in template. Suppose my collection is named as "addedGridRowCollection" to which each individual person detail is set to model(ex:addvisModel) and in turn model is set to collection. Hence the code is as ---
var addedVisitorGridView = new AddedVisitorGridView({
collection: addedGridRowCollection
});
newVisitorLayoutView.gridRegion.show(addedVisitorGridView);
addedVisBasicModel.set("firstname", "Tony"); addedGridRowCollection.add(addedVisBasicModel);
applyDataTable(); // applying jquery data table
$(addedVisitorGridView.el).show();
Now in delete trigger: when I clicked on Delete button,The following is the code I am using:
var indx= model.get('index');
var visModel = addedGridRowCollection.findWhere({index: indx});
addedGridRowCollection.remove(visModel);
here it removes from collection but not from the data-table (Jquery).

Delete multiple ng-repeat elements on click of a single button

I have a table showing project names as shown in below image ,
On delete button click, I am passing the selected check-boxes data as array of objects to my controller
[{id:1,name:'Name 8'},{id:2,name:'Name 7'}]
Then deleting the names from the table at the server side. This all works fine but how do i remove the rows from DOM after deleting them?? I went through this post which says how to remove ng-repeat elemets from DOM but in that case the elements are removed one at a time, by passing the $index to the splice() function
In my case i need to remove multiple rows. If ill have to use the splice function in my controller how do i get the index from the selected rows object? Or is there any better way of doing it.
Hope my question is clear!
Update: jsFiddle
Solution: Well i had to modify #wickY26 answer a bit to suite my scenario. Here is my update jsFiddle
What i did was , in the delete() change code to
angular.forEach($scope.projects, function (row, index) {
if($scope.projects[index].checked) {
$scope.projects.splice(index,1);
}
});
You can keep selected rows on an object via binding checkbox with ng-model,
so example table html should be like this
HTML
<table class="table table-bordered">
<tbody>
<tr ng-repeat="row in data" ng-class="{'success' : tableSelection[$index]}">
<td>
<input type="checkbox" ng-model="tableSelection[$index]" />
</td>
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>
and if you define a function in your controller which travel through your data and splice array depends on tableSelection object boolean values...
UPDATE
after your comment I debug my code and see I cannot remove multiple rows same time, so I look at my code and change some part of it...
at my example you cannot delete multiple rows same time because everytime you splice an element from data array you shift indexes of array for rest, so right way to do it starting from last index,
Here new CONTROLLER
$scope.removeSelectedRows = function() {
//start from last index because starting from first index cause shifting
//in the array because of array.splice()
for (var i = $scope.data.length - 1; i >= 0; i--) {
if ($scope.tableSelection[i]) {
//delete row from data
$scope.data.splice(i, 1);
//delete rowSelection property
delete $scope.tableSelection[i];
}
}
};
I update my PLUNKER added comments and some other functionallty as well...

Knockout.js - Adding properties to data sent serverside

I've got some data sent via a server, formatted like this:
[
{"Username":"user1#domain.com", "id":1},
{"Username":"user2#domain.com", "id":2},
{"Username":"user3#domain.com", "id":3}
]
I bind it to a table, but I'd like the ability to add a class to the table row when the checkbox is checked (to indicate it's been selected). Here's what will eventually work, and I know the problem is that Selected is not a property currently in my data.
<table>
<tbody data-bind="foreach: Items">
<tr data-bind="css:{selected: Selected}">
<td>
<input type='checkbox' data-bind="attr:{name: id}, checked: Selected" />
</td>
<td data-bind="text: Username"> </td>
</tr>
</tbody>
</table>​
Since the concept of Selected is something purely for the UI, it seems a little dumb to have the server send that over the wire for each item in my data.
What I want to happen is basically this: http://jsfiddle.net/xSSMX/ but without having to add the observable Selected property on each item.
How can I add a property to each existing item in my data to achieve this?
You could just use map to add the field to the array that you get from the server like this...
data = data.map(function(item) {
item.Selected = ko.observable(false);
return item;
});
Which will add Selected on to each item. Although if I'm not mistaken map isn't supported in all browsers so you'd have to add support which you could do with a function similar to the one found here... http://www.tutorialspoint.com/javascript/array_map.htm. Or you could achieve the same effect using jQuery's $.each.
When the server sends over the data, you can create a userModel(id, userName). This model by default will have selected on false. I included a jsFiddle to make this a bit more clear.
http://jsfiddle.net/xSSMX/1/

Categories