Dynamical ng-model in ng-repeat is undefined - javascript

I need help with dynamically generated models inside ng-repeat. I have a HTML with ng-repeating table rows. Each row has an option to update image name and price with update button.
HTML
<tr role="row" class="odd" ng-repeat="i in slideShowImages track by $index">
<td>
<img ng-src="{{i.thumb_path}}" width="100px" height="auto" >
</td>
<td>
<div class="form-group">
<input type="text" name="imageName" ng-model="i.imageName[$index]"
class="form-control" id="imageName" required>
</div>
</td>
<td>{{i.date_taken | myDate}}</td>
<td>
<div class="form-group">
<input type="text" name="imagePrice" ng-model="i.imagePrice[$index]"
class="form-control" id="imagePrice" required>
</div>
</td>
<td>
{{i.position}}
</td>
<td>
<a ng-click="updateImage(i.id_thumb, $index)">
<button class="btn btn-block btn-success">Update</button>
</a>
</td>
<td>
<a ng-click="deleteImage(i.id_thumb, $index)">
<button class="btn btn-block btn-danger">Delete</button>
</a>
</td>
</tr>
And this is my controller function which is trying to get the values
$scope.updateImage = function(id, $index){
$scope.models = {};
console.log(id);
console.log($index);
console.log($scope.i.imageName[$index]);
console.log($scope.i.imagePrice[$index]);
};
// result of console logs
4
0
angular.js:13550 TypeError: Cannot read property 'imageName' of undefined
at Scope.$scope.updateImage (locationsCtrl.js:243)
at fn (eval at compile (angular.js:14432), <anonymous>:4:486)
at expensiveCheckFn (angular.js:15485)
at callback (angular.js:25018)
at Scope.$eval (angular.js:17229)
at Scope.$apply (angular.js:17329)
at HTMLAnchorElement.<anonymous> (angular.js:25023)
at HTMLAnchorElement.dispatch (jQuery-2.1.4.min.js:3)
at HTMLAnchorElement.r.handle (jQuery-2.1.4.min.js:3)
Guess i'm doing something wrong, based on error that imageName and imagePrice can't be undefined. I hope you guys can help me. If you need any additional information's please let me know and i will provide. Thank you in advance

You had ng-repeat with i in slideShowImages which says that, on each iteration i will have current element of collection over the UI only(not in controller scope). So you can not get $scope.i value inside controller. You have to pass that value from updateImage as a parameter like ng-click="updateImage(i)". Then you can play with i object which is available on UI.
HTML
<td>
<a ng-click="updateImage(i)">
<button class="btn btn-block btn-success">Update</button>
</a>
</td>
<td>
<a ng-click="deleteImage(i, $index)">
<button class="btn btn-block btn-danger">Delete</button>
</a>
</td>
Controller
$scope.updateImage = function(i){
console.log(i.imageName);
console.log(i.imagePrice);
};

You cannot ge the i object of ng-repeat in the controller.
What else one can do is filter using any other key in the json.
Example:
$scope.currentImageName = $filter('filter')(slideShowImages,{ id: $index});
Here assuming slideShowImages has a field id of value - $index.
Also, add the dependency injection$filter in the controller definition.

Related

Angular2 Javascript Push - Add item to array

I'm building a form in Angular2 which contains a field that is an array of objects. I've so far built the table with a Delete Row button per row and an Add Row button. These use the JavaScript push() and slice() methods.
There is a big bug though:
When adding a new row, the content of the previous rows is deleted.
That is to say, the content of the row is deleted, not the row itself.
Any ideas why?
Component Code:
public addRow(): void {
this.table.push({});
}
public deleteRow(row: object): void {
this.table.splice(this.table.indexOf(row), 1);
}
HTML Template
<form #TimesheetForm="ngForm" (ngSubmit)="saveTimesheet()">
<div class="row">
<div class="col text-right">
<button type="button" class="btn" (click)="addRow()"><i class="fa fa-plus-square" aria-hidden="true"></i> Add Row</button>
</div>
</div>
<table class="table">
<thead>
<td class="table__header">Date</td>
<td class="table__header">Time</td>
<td class="table__header">Actions</td>
</thead>
<tbody>
<tr *ngFor="let row of table">
<td class="table__item">
<input class="input" [(ngModel)]="row.date" name="date">
</td>
<td class="table__item">
<input class="input" [(ngModel)]="row.time" name="time">
</td>
<td class="table__item">
<button type="button" class="btn btn--negative" (click)="deleteRow(row)"><i class="fa fa-times" aria-hidden="true"></i> Delete</button>
</td>
</tr>
<tr *ngIf="school.rows.length == 0">
<td colspan="3">No rows exist yet. Click Add Row to start logging your timesheet.</td>
</tr>
</tbody>
</table>
</div>
<div class="row">
<div class="col">
<button class="btn btn--positive" type="submit"><i aria-hidden="true" class="fa fa-check"></i> Save</button>
</div>
<div class="col text-right">
<button class="btn btn--negative"><i aria-hidden="true" class="fa fa-times"></i> Cancel</button>
</div>
</div>
</form>
With template driven forms, we need to remember that the name attribute needs to be unique, otherwise the fields will be evaluated as the same field. So what your form does now, is not adding new form controls when you add new rows, instead it manipulates the one and same form field. If you were to put something like <pre>{{TimesheetForm.value | json}}</pre> in your template, you can see, that despite pushing new rows, there is only one form control name date and one form control named time.
So what we need to do is to provide an unique name, we can do that by using the index of the items in your table array. So do the following:
<tr *ngFor="let row of table; let i = index">
<td>
<input [(ngModel)]="row.date" name="date{{i}}">
</td>
<td>
<input [(ngModel)]="row.time" name="time{{i}}">
</td>
<!-- more code here -->
</tr>

Button Doesnt Work Inside Table Row

I have a HTML someting like this. In this html, number of tr is not certain. It could be 1 or 1000. Every row has a button has a name, "apply". And i have a jquery to handle click operation but in this case, jquery recognizes only first row's button. But i dont know how to do.
<tr id="jobs" th:each="job : ${job}">
<p id="jobID" th:text="${job.jobId}" style="display: none"></p>
<div class="col-md-3" id="JobTitle">
<span class="label label-default">Job Title</span>
<td th:text="${job.jobTitle}" />
</div>
<div class="col-md-3" id="jobDescription">
<span class="label label-primary">Job Description</span>
<td th:text="${job.jobDescription}" />
</div>
<div class="col-md-3" id="numberOfPersonToHire">
<span class="label label-success">Number Of Candidates</span>
<td th:text="${job.numberOfPersonToHire}" />
</div>
<div class="col-md-2" id="lastApplicationDate">
<span class="label label-info">Last Date</span>
<td th:text="${job.lastApplicationDate}" />
</div>
<button id="apply" type="submit" class="btn btn-xs"aria-label="Left Align">
<img th:src="#{/images/apply.png}" height="20" width="auto" />
Apply
</button>
</tr>
JS
$("#apply").click(function(e){
e.preventDefault();
e.stopPropagation();
debugger
var jobId = $('#jobID').text();
document.location.href = "/admin";
});
An id (declared using #) is a unique element that can't be used more than once. JavaScript identifies the first element with that specific id and doesn't continue afterwards. You might want to think about using a class:
...
<button class="apply" type="submit" class="btn btn-xs"aria-label="Left Align">
<img th:src="#{/images/apply.png}" height="20" width="auto" />
Apply
</button>
...
And then iterate through every element with the class "apply", assuming you are using JQuery:
$('.apply').each(function() {
...
});
Same thing goes for your #jobId, which can't be used more than once, so you could not use your assignment in the each-loop. You could also include a counter into your for-loop, which you could append to the ids so you would end up with something like <button id="apply-1">... or similiar. However, you would have to use selectors to find each <button> child of each <tr> element you have.

Populate dropdown value inside ng-repeat

I am facing an issue with my Angular code, while populating the dropdown inside a HTML table (Code given below).
Could you please help me with what should be done inside modify() to populate the dropdown ?
HTML Code:
<table class="table" ng-app="empApp" ng-controller="employeeController">
<thead>
<tr class="info">
<th>Emp Name</th>
<th>Status</th>
<th colspan="2"> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="emp in employees">
<td>
<div ng-hide="editingData[emp.id]">{{ emp.name }}</div>
<div ng-show="editingData[emp.id]"><input type="text" ng-model="emp.name" /></div>
</div>
</td>
<td>
<div ng-hide="editingData[emp.id]">{{ emp.status.name }}</div>
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status.id as status.name for status in statuses"></select>
</td>
<td colspan="2">
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[employee.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[employee.id]" ng-click="update(emp)">Update</button>
</div>
</td>
</tr>
</tbody>
</table>
Javascript Code:
var empApp = angular.module("empApp", []);
empApp.controller('employeeController', function($scope, $http) {
$scope.statuses = [{"id":1,"name":"Active"}, {"id":1,"name":"Inactive"}];
$scope.employees = [{"id":1,"name":"Mark","status":{"id":1,"name":"Active"}},{"id":2,"name":"Sara","status":{"id":2,"name":"Inactive"}}];
$scope.editingData = {};
for (var i = 0, length = $scope.employees.length; i < length; i++) {
$scope.editingData[$scope.employees[i].id] = false;
}
$scope.modify = function(employee){
$scope.editingData[employee.id] = true;
//Set Employee Status correctly here to populate the dropdown
};
});
My problem is I am NOT able to populate the dropdown with the existing value, as show in the diagram below:
I made this plunker, check if it's what you need.
1: Both status are with id 1. Change them
From:
$scope.statuses = [{"id":1,"name":"Active"}, {"id":1,"name":"Inactive"}];
To:
$scope.statuses = [{"id":1,"name":"Active"}, {"id":2,"name":"Inactive"}];
2:Change your select ng-model to emp.status.id.
From:
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status.id as status.name for status in statuses"></select>
To:
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status.id"
ng-options="status.id as status.name for status in statuses"></select>
3: Change your buttons ng-hide/ng-show
From:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[employee.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[employee.id]" ng-click="update(emp)">Update</button>
</div>
To:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[emp.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[emp.id]" ng-click="update(emp)">Update</button>
</div>
Update:
As #Rajesh said below you can store the status_id instead of entire status object. Check his fiddle.
Just replace your select tag part with this code
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status as status.name for status in statuses track by status.id"></select>
This is because in ng-model you are passing the full object and you need the full object to be tracked by ID in order to fill the dropdown and relevant dropdown value to be selected
Also in your buttons:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[emp.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[emp.id]" ng-click="update(emp)">Update</button>
</div>
Because you are referencing it by 'emp' and not 'employee' inside ng-repeat as it will be dynamic
You are looping through
<tr ng-repeat="emp in employees">
But in the select list, you are toggling based on:
editingData[employee.id]
Try changing that to
editingData[emp.id]
use emp.id in your button and also in your modify function and loop
use $scope.employees[i].isVisible = false and ng-hide = $scope.employees.isVisible ng-show = $scope.employees.isVisible

jQuery's .children() method returns 'undefined'?

In my jQuery code, I want to be able to get the child element within a <tr> element. I have created a jQuery object and named the variable $trElement. However, for some reason calling $trElement.children(".sequence-edit") returns undefined so any further action done on that statement throws an error (since you are invoking a method on an undefined object). I have checked the HTML code of the parent element and it clearly contains a child element with the indicated class name. So why does it not work?
Here is the code I used to debug the issue.
console.log($trElement.html());
console.log($trElement.children(".sequence-edit"));
The first line console.log($trElement.html()); logs:
<td class="sequence-number">#1</td>
<td class="sequence-data">
<form action="editSequence.do" method="POST">
<textarea cols="50" rows="10" name="sequence" class="sequence-data-textarea"></textarea>
<input type="hidden" name="index" value="0">
</form>
</td>
<td class="button-first">
<!-- Element targeting through ".sequence-class" -->
<button type="button" class="btn btn-info sequence-edit center-block display-true">EDIT</button>
<button class="btn btn-info center-block edit-submit display-false" style="display: none;">SUBMIT</button>
</td>
<td class="button-second">
<button type="button" class="btn btn-danger delete-modal-appearance center-block display-true" data-toggle="modal" data-target="#modal-delete-confirmation">DELETE</button>
<button class="btn btn-warning center-block edit-cancel display-false" style="display: none;">CANCEL</button>
</td>
<td>
<input type="checkbox" class="checkbox-delete-selection center-block">
</td>
The second line console.log($trElement.children(".sequence-edit")); logs: undefined
.children() only travels one step into your selector's (trElement) DOM tree, (and those are your <TD>'s, not your <button>).
instead, use .find(".sequence-edit")
https://api.jquery.com/children/
https://api.jquery.com/find/
var $trElement = $("tr");
console.log( $trElement.find(".sequence-edit").html() ); // "EDIT"
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td class="sequence-number">#1</td>
<td class="sequence-data">
<form action="editSequence.do" method="POST">
<textarea cols="50" rows="10" name="sequence" class="sequence-data-textarea"></textarea>
<input type="hidden" name="index" value="0">
</form>
</td>
<td class="button-first">
<!-- Element targeting through ".sequence-class" -->
<button type="button" class="btn btn-info sequence-edit center-block display-true">EDIT</button>
<button class="btn btn-info center-block edit-submit display-false" style="display: none;">SUBMIT</button>
</td>
<td class="button-second">
<button type="button" class="btn btn-danger delete-modal-appearance center-block display-true" data-toggle="modal" data-target="#modal-delete-confirmation">DELETE</button>
<button class="btn btn-warning center-block edit-cancel display-false" style="display: none;">CANCEL</button>
</td>
<td>
<input type="checkbox" class="checkbox-delete-selection center-block">
</td>
</tr>
</table>

ngIf always evaluates as true

I'm developing an AngularJS application, using v1.3.9. I have a couple ng-if directives which are always evaluating true, and I can't figure out why.
<tr data-ng-repeat="line in chat">
<td class="message-timestamp">{{line.time | date:'short'}}</td>
<div ng-if="line.type === 'gist'">
<td class="message-nick">{{line.nick}}</td>
<td class="message-text">{{line.message}}
<p>
<code ng-show="showGist" walrus-auto-gistify data-gist-id="{{line.options[0]}}"></code>
<br>
<button ng-click="showGist = !showGist" class="btn btn-primary btn-xs">Show/Hide</button>
</p>
</td>
</div>
<div ng-if="false">
<td class="message-nick"><span class="glyphicon glyphicon-arrow-right"></span></td>
<td class="message-text">{{line.message}}</td>
</div>
</tr>
When I run this, both ng-if directives are evaluated as true, so both divs are displayed.
chat is just an array of objects.
I'm pretty sure it has to do with the fact that you have a <div> as a direct child of a <tr>. I've had issues with this type of design in the past where it causes strange behavior.
A solution to this is to put the <div>'s within the <td>, like below:
<tr data-ng-repeat="line in chat">
<td class="message-timestamp">{{line.time | date:'short'}}</td>
<td class="message-nick">
<div ng-if="line.type === 'gist'">{{line.nick}}</div>
<div ng-if="line.type !== 'gist'"><span class="glyphicon glyphicon-arrow-right"></span></div>
</td>
<td class="message-text">
<div ng-if="line.type === 'gist'">
<p>
<code ng-show="showGist" walrus-auto-gistify data-gist-id="{{line.options[0]}}"></code>
<br />
<button ng-click="showGist = !showGist" class="btn btn-primary btn-xs">Show/Hide</button>
</p>
</div>
<div ng-if="line.type !== 'gist'">{line.message}}</div>
</td>
</tr>
EDIT: Attempt at a JSFiddle here

Categories

Resources