AngularJS: add, remove and reorder items controlled by array - javascript

I have an ng-repeat in a table. There are several of these tables inside a larger table. Each row has buttons for "add another" (which I have working) and remove current row/move row up-down (which I don't).
They are created with an array, each group in the table has its own array, with one member to start.
vm.choices1 = [{ id: 'choice1' }];
vm.choices2 = [{ id: 'choice1' }];
vm.choices3 = [{ id: 'choice1' }];
Clicking the plus button passes the current array and pushes a new item onto it, thus adding a repeater.
vm.addNewChoice = function(arr) {
var newItemNo = arr.length + 1;
arr.push({
'id': 'choice' + newItemNo
});
};
This works fine. Of course, now I have been asked to add the delete button and up/down buttons.
I get in concept what I need to do: I suppose somehow when the delete button is clicked I need to pass that index number to a delete function and pop that index from the array thats passed:
vm.deleteChoice = function(arr) {
arr.splice(index, index+1); //??
};
But I'm not sure how to get and pass the clicked index to the function. I used to know how to do this in jQuery, but not in Angular. If I can get the index of the clicked item into my function, I'm sure I can figure out the u/down buttons from there too.
Basic punker: http://plnkr.co/edit/WPdnmYbDSXC0LsbeMduM?p=preview

The directive ng-repeat creates a scope for every item that it iterates through. Part of the data assigned to this scope is the attribute $index which will be equal to the index in the array of the item/object.
Source: https://docs.angularjs.org/api/ng/directive/ngRepeat

Related

Using mustache.js to render array data into a table rows; stuck at implementing row editing

I am creating an app for tracking expenses.
Here is a codepen with all of the source code.
I have a global object called object that I render out to a Mustache template:
var object = {
info : [
{date: "14, July", day: "Saturday", item: "Lunch", price: 40}
{date: "15, July", day: "Sunday", item: "Airport", price: 80}
{date: "14, July", day: "Saturday", item: "Snacks", price: 25}
],
withdrew: 0
};
{{#info}}
<tr>
<td>{{date}}</td>
<td>{{day}}</td>
<td>{{item}}</td>
<td>{{price}} /-</td>
<td>Edit | Delete</td>
</tr>
{{/info}}
Mustache.render(template, object);
As the user enters details in the Add Item div (to the right of the screenshot), the info array in object gets filled (it currently has 3 entries).
The part I'm stuck on is editing a row when the user clicks either the "Edit" or "Delete" button in the "MODIFY" table column. I have the following click listener bound to the table:
var modifyBtn = document.querySelector('table');
modifyBtn.addEventListener('click', function(e){
console.log(e.target);
});
With this, I am able to get both of the button nodes correctly with console.log(), but how do I make them point uniquely to the original array elements that each row is generated from?
Any help is highly appreciated.
There are a couple of ways to do this, but the way that immediately stands out to me is the following:
Add the indexes to the array elements for the Mustache render function
Render out the indexes as data- attributes in the HTML
Retrieve this data in the click listener
Modify the original info array using this data
Let's run through this.
Step 1: Add the indexes to the array elements for the Mustache render function
First things first, we want to retrieve the array indexes in the HTML. This data isn't available by default when rendering with Mustache, so we need to add it in ourselves. This can't be a permanent addition to the array objects. The actual indexes can change at any time (say, if an element is removed). Instead, we only want to render out the indexes for the HTML and nowhere else.
Create the following addIndexes() function:
function addIndexes(object) {
object = JSON.parse(JSON.stringify(object));
var info = object.info;
for (var i = 0; i < info.length; i++) {
info[i].index = i;
}
return object;
}
The first line clones the object. Remember, we don't want to modify the original object or array. This allows us to work on a copy that doesn't affect the original.
We run through the array and add the index of that array to a property called index.
var object = {
info : [
{date: "14, July", day: "Saturday", item: "Lunch", price: 40},
{date: "15, July", day: "Sunday", item: "Airport", price: 80},
{date: "14, July", day: "Saturday", item: "Snacks", price: 25}
],
withdrew: 0
};
function addIndexes(object) {
object = JSON.parse(JSON.stringify(object));
var info = object.info;
for (var i = 0; i < info.length; i++) {
info[i].index = i;
}
return object;
}
console.log("--- The object AFTER addIndexes() ---");
console.log(addIndexes(object));
console.log("--- The object BEFORE addIndexes() ---");
console.log(object);
Now, simply modify the Mustache.render() method to use our new addIndexes() function:
function refreshDOM() {
var outputBody = Mustache.render(template, addIndexes(object));
...
}
Step 2: Render out the indexes as data- attributes in the HTML
Now we need to attach those indexes in our HTML template. In your main index.html, go to the render function and change the button code to read this:
<td>Edit | Delete</td>
Now we are outputting the index to the data-index attribute, which we can retrieve later. This renders out each button element to look something like this:
<td>Edit | Delete</td>
<td>Edit | Delete</td>
And so on, for each row.
Step 3: Retrieve this data in the click listener
We can now actually handle this data. Change your modifyBtn click event listener to be the following:
var modifyBtn = document.querySelector('table');
modifyBtn.addEventListener('click', function(e){
var el = e.target;
var action = el.id;
var index = parseInt(el.dataset.index);
switch (action) {
case "editInfo": editInfo(index); break;
case "deleteInfo": deleteInfo(index); break;
}
});
We're now getting the el (which is just the element) from event.target.
We can get a string with the action (like "editInfo") from your el.id.
The index value is data-index which can be retrieved with el.dataset.index. Note that this gets placed in HTML as a string (which we can't use), so we have to run parseInt() on it to get an integer from the string.
With a switch statement, we can now perform an action depending on the ID.
Neither of these handler functions (editInfo() nor deleteInfo()) are currently declared, so on to the final step!
Step 4: Modify the original info array using this data
I don't know what you want to do with the editInfo ID, so I'll leave that one up to you. I've created a simple deleteInfo function directly above the click listener:
function deleteInfo(index) {
object.info.splice(index, 1);
refreshDOM();
}
This will remove the specified index from the global object.info array and then call refreshDOM() to update.
We're now complete! Sorry this was such a long-winded answer, I wanted to break down each step as we went along. Let me know if this all made sense and if you have any questions!

Adding objects to array

I am trying to push objects to an array from a click event. The array appears to hold only one object although the template shows more than one.
I have html with data attributes to pass franchise id and name values to a twig template from the controller like this:
<a
data-id="{{ franchise.franchise_id }}"
data-name="{{ franchise.franchise_name }}"
class="btn add-to-list">
Add to List
</a>
This code repeats based on the query results.
When the anchor tag is clicked, id and name are stored in variables, which are put into an object (franchise), which is then pushed onto the array (franchises). Here's the jQuery:
$(".add-to-list").on('click', () => {
let franchises = [];
let franchise;
let id = $(this).data("id");
let name = $(this).data("name");
franchise = {
'id': id,
'name': name
};
franchises.push(franchise);
$.each(franchises, (index, value) => {
$("#choices").append('<a class="list-group-item" id='+value.id+'> ' + value.name + '<span class="pull-right">×</span></a>');
});
});
The franchises array is looped thru and the data displays in the template. The results show more than one item in the array, but console.log(franchises.length) shows one item after each click of the anchor tag. It seems like after the second item is clicked and it displays in the template that the console log should show one array with two objects rather than array with the object data that I just clicked.
The console.log displays this in conjunction with two clicks on different anchor tags:
Add to List clicked!
====== start ============
{id: 2, name: "Crunch"}
franchises.length: 1
====== end ===========
Add to List clicked!
====== start ============
{id: 1, name: "Planet Fitness"}
franchises.length: 1
====== end ===========
If anyone can identify my error(s), I would appreciate the help.
Thanks!
Every time a .add-to-list element is clicked, you're creating a new variable called franchises. You need to declare the variable outside of the click handler.
You are recreating a franchises array every time the anonymous arrow function is called if you want to have a franchise array that is added to you must declare the franchise array outside the definition of the function.
so the code would be as follows:
let franchises = [];
$(".add-to-list").on('click', () => {
let franchise;
let id = $(this).data("id");
let name = $(this).data("name");
franchise = {
'id': id,
'name': name
};
franchises.push(franchise);
$.each(franchises, (index, value) => {
$("#choices").append('<a class="list-group-item" id='+value.id+'>
' + value.name + '<span class="pull-right">×</span></a>');
});
});
So now the franchises array is defined outside the scope of the anonymous function and is still accessible to the anonymous function. Note that polluting the global namespace is considered bad practice so you may want to look into understanding closures in JavaScript here is a link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

JQuery Access Arrays - Logical mistake possible

so i'm sitting here for hours and now i have to get the Eyes of the Internet to help me out...
So i'm trying to create a Shift Plan. I created a Table (each cell a jQuery Dropdown button with the possible Shifts). on every selection i write the selected Shifts into an array.
The following code snippet is how i do that. I need this in the on click because it will show the total shift time in n extra field.
Later i want save the arrays to a Database an because of that i need to access the calFirstRow array for each employee.
var calFirstRow = [,];
$(document).ready(function(){
$('.dropdown-menu a').on('click', function(){
// Do the following stuff for every Employee
$.each( jqEmployees, function(index, value){
var name = value['NAME'];
// create array from first row
$( ".first-row-"+ name +" td button" ).each(function( index ) {
calFirstRow[name, index] = $( this ).text();
});
});//End each Employee
});//End Dropdown click
Here i try to Access the array
});//End ready
the problem is, no matter what i do, in every calFirstRow[name] i get the array from the last Employee.
If i print the Array i get something like [User1: Array, User2: Array] but in each, User1 and 2 is the data of User2 saved...
Im new to jQuery and i maybe miss somethin really fundamental....
It looks like you're trying to treat calFirstRow as a two dimensional array, but the way you're accessing it doesn't make any sense. If you pop open your browser's development console and type [,], you'll see this:
> [,]
< [undefined × 1]
It's got only a single dimension, and JavaScript treats the comma as coming after an element, which you haven't provided, so you have an array containing a single undefined.
The next problem happens when you access calFirstRow[name, index]. Arrays can only be accessed by a single integer index, and JavaScript doesn't know what to do with name, so it ignores it and just uses index:
> a = ['a', 'b', 'c']
> a[1, 2]
< 'c' // The element at index 2
> a[2, 0]
< 'a' // The element at index 0
The result is that your loops aren't creating a two dimensional array that matches your table, they're just overwriting calFirstRow again and again, which is why your last user's data ends up in it. It looks like it's in every row, but really when you access calFirstRow['User1', 0] or calFirstRow['User2', 0], you're just accessing calFirstRow[0] twice.
Since you're trying to index by the employee's name, and then their column in the table, I think you want to start with an object. calFirstRow doesn't seem like the name for what you really want, so let's call it shiftData:
var shiftData = {}
The keys in the object will be your employees' names, and the values will be their array of shifts. For example:
> shiftData['Kristjan'] = [1,2,3]
< [1, 2, 3]
> shiftData['Joel'] = [4,5,6]
< [4, 5, 6]
> shiftData
< Object {Kristjan: Array[3], Joel: Array[3]}
Joel: Array[3]
0: 4
1: 5
2: 6
length: 3
Kristjan: Array[3]
0: 1
1: 2
2: 3
length: 3
In your click handler, you can map over each employee's row in the table and set their named entry to the result.
$.each( jqEmployees, function(employeeIndex, value){
var name = value['NAME'];
shiftData[name] = $( ".first-row-"+ name +" td button" ).map(function( columnIndex ) {
return $( this ).text();
});
});
This will result in a shiftData object like I showed in the console above. Each iteration of map returns the text of the td button in the user's row and column. Those values are aggregated into an array and assigned to shiftData[name].

Getting value from javascript tags in array form

Here I created tag using web tutorials.
JSFIDDLE : http://jsfiddle.net/karimkhan/A5TJh/1/
Inside:
for (var i in tags){
tagString.push(tags[i].value);
}
if I alert(tags[i]) it alerts correctly.
But when I use tags before end of function that it alerts undefined.
My purpose is to store all tags in an array and push POST this array to PHP file. But the issue is I am not able to retrieve tags value in array. As it is already in tags array, I thought I could access it directly.
You need values like 1, 2, 3 or tags names like tag1, tag2, tag3?
Call someMethod with argument like tagString
instance.tagit({
tagSource: availableTags,
tagsChanged: function () {
//Get the tags
var tags = instance.tagit('tags');
var tagString = [];
//Pull out only value
for (var i in tags) {
tagString.push(tags[i].value);
}
someMethod(tagString);
//Put the tags into the input, joint by a ','
input.val(tagString.join(','));
function someMethod(tags) {
console.log(tags);
// call POST action
}
}
});
I think it's easier to just retrieve the values from the DOM, rather than try to store them in an array. If you store them in array as they are being created, then you have to keep an eye on changes, such as if the user deletes one of the tags.
So I would just retrieve the values when the user performs their submit action. The values are stored in a hidden unordered list and each has a class of .tagit-choice, so just iterate over the list and retrieve the text values:
DEMO
$('.tagit-choice').each(function () {
alert($(this).contents(':not(a)').text());
});
Naturally, you can use the each method to create an array once you're ready to post like this:
tagsForPost = [];
$('.tagit-choice').each(function (i) {
tagObj = {value: i, label: $(this).contents(':not(a)').text()};
tagsForPost.push(tagObj);
});
console.log(tagsForPost);

Add a row in Dojo datagrid

Struggling to find a bit of code to easily understand.
How do you add a row and clear all rows in a Dojo datagrid (version 1.4.2). Lets say the data is 2 columns with customerID and address.
I am using
dojo.data.ItemFileWriteStore
to store values in - but again not quite sure how this should be used.
It can't be that hard.
Cheers.
You can get the data store reference from the grid using grid.store, then you can use store.newItem() to create a new item in the store. This new item is added as a new row in the grid. For example, store.newItem({customerID : 1, address : "Somewhere"}).
To clear all the rows, you can either loop all the items in the data store and use deleteItem() to remove all the items, or use the internal function _clearData() in data grid to remove all the rows, or use setStore() to set a new empty store to the grid. I prefer to use a empty store to reset the grid.
The above answers are correct, but you also need to call save() on the write store to "commit" the change. When you save, a widget using the store (datagrid for example) will refresh itself.
Also, newItem() returns the new item you just created so if you don't want to pass an object to newItem just modify its return value, then save() the store.
Pseudo code:
var i = store.newItem({});
store.setValue(i,"newattribute1","new value");
store.setValue(i,"newattribute2","new value 2");
store.save();
Here is the relevant docs for ItemFileWriteStore which tell how to use newItem(), setValue(), and save().
Instead of deleteItem, you should use setStore(new ItemFileWriteStore()), but I suspect there is a memory leak when you do this, be careful. This makes a new blank store to be used with the grid.
I have finish one example about this... the code is here
//First we create the buttons to add/del rows
var addBtn = new dijit.form.Button({
id: "addBtn",
type: "submit",
label: "Add Row"
},
"divAddBtn");//div where the button will load
var delBtn = new dijit.form.Button({
id: "delBtn",
type: "submit",
label: "Delete Selected Rows"
},
"divDelBtn");
//Connect to onClick event of this buttons the respective actions to add/remove rows.
//where grid is the name of the grid var to handle.
dojo.connect(addBtn, "onClick", function(event) {
// set the properties for the new item:
var myNewItem = {
id: grid.rowCount+1,
type: "country",
name: "Fill this country name"
};
// Insert the new item into the store:
// (we use store3 from the example above in this example)
store.newItem(myNewItem);
});
dojo.connect(delBtn, "onClick", function(event) {
// Get all selected items from the Grid:
var items = grid.selection.getSelected();
if (items.length) {
// Iterate through the list of selected items.
// The current item is available in the variable
// "selectedItem" within the following function:
dojo.forEach(items, function(selectedItem) {
if (selectedItem !== null) {
// Delete the item from the data store:
store.deleteItem(selectedItem);
} // end if
}); // end forEach
} // end if
});

Categories