Setting readonly using ng-readonly dynamically is not working - angularjs

I am fetching some data from the database server and displaying the same in tabular format on my GUI. I want to achieve the following in my GUI design
(a) Any cell in the table should be editable except the first column in the table.
(b) The first row of the table, if added newly to the GUI for accepting new inputs from the user, should be exempted from the above rule i.e., all columns of the first row should be editable.
To achieve this I wrote the following code.
HTML Code
<tbody>
<tr ng-repeat="x in sensordata">
<td>
<input class="form-control" type="text" ng-model="x.name" placeholder="Sensor name" ng-readonly="namereadonly" ng-init="ctrl2.setReadOnly(x.name,$index)"/>
</td>
<td>
<input class="form-control" type="text" ng-model="x.unit" placeholder="Measurement Unit" required/>
</td>
<td>
<input class="form-control" type="text" ng-model="x.type" placeholder="Sensor Type" required/>
</td>
<td>
<input class="form-control" type="text" ng-model="x.periodicity" placeholder="Periodicity" required/>
</td>
<td>
<input class="form-control" type="text" ng-model="x.formula" placeholder="Formula" required/>
</td>
<td>
<input class="form-control" type="email" ng-model="x.vendor" placeholder="Email Id" required/>
</td>
</tr>
</tbody>
Controller code is as follows.
this.setReadOnly = function(name,idx) {
console.log('name ' + name + ' idx ' + idx);
if (name === undefined || name === null || name === '') $scope.namereadonly = false;
if (name !== '' || name !== undefined || name !== null) $scope.namereadonly = true;
};
When I am executing above code the first column of all the rows are getting uneditable as expected. But when I am adding new row to the (by adding new empty JSON object in the sensordata array in another function of the same controller) GUI, the first column of the row is getting uneditable which should not be the case as per the first if statement in the above method. I am unable to figure out why it is happening.

you can try if else statement, which sets the variable properly
this.setReadOnly = function(name,idx) {
console.log('name ' + name + ' idx ' + idx);
if (name === undefined || name === null || name === '') $scope.namereadonly = false;
else (name !== '' || name !== undefined || name !== null) $scope.namereadonly = true;
};

<input class="form-control" type="text" ng-model="x.name" placeholder="Sensor name" ng-model-options="{ updateOn: 'blur' }" ng-readonly="x.name.length"/> this line solved my problem. Without ng-model-options attribute as soon as one types a character in the newly added row the first column is becoming uneditable. With ng-model-options this issue is resolved as now the column would become uneditable upon moving focus from the input field.
No method is to be written in the controller.

you can put the condition directly in ng-readonly as below
<input class="form-control" type="text" ng-model="x.name" placeholder="Sensor name" ng-readonly="{{x.name !== '' && x.name !== undefined && x.name !== null}}" />

a) Any cell in the table should be editable except the first column in
the table. (b) The first row of the table, if added newly to the GUI
for accepting new inputs from the user, should be exempted from
the above rule i.e., all columns of the first row should be editable.
There are plenty solutions but it depends on you/your needs. Here I would like to share you 2 logics :
adding new key for the new object entered by user and use this new key to distinct them with old object ng-readonly="x.new" and new object should have this key: {name:"", new: true}
Store the length of main array, and use this to distinct the new object entered ng-readonly="$index < mainlength" and mainlength is something like: $scope.mainlength = angular.copy($scope.sensordata.length) (use of angular.copy ) see http://plnkr.co/edit/zCcyPTG3EybL4IjsOpz4?p=preview

Related

Angular Form Validation inside ngRepeat

I have a series of table rows with various inputs inside of an ng-repeat and I'm trying to use angular's built in form validation to handle required fields. Everything works well except for when I delete one of the items in the array that is backing the ng-repeat.
Here is how I've set up my validation
<td ng-class="{ 'has-error' : vm.testForm.itemName{{$index}}.$invalid }">
<input type="text"
name="itemName{{$index}}"
class="form-control"
ng-model="item.name"
required />
<div class="help-block"
ng-show="vm.testForm.itemName{{$index}}.$invalid"
ng-messages="vm.testForm['itemName'+$index].$error">
<span ng-message="required">Name is required</span>
</div>
</td>
Plunker showing my issue
https://plnkr.co/edit/Q1qyqlSG69EXR2lTrsHk
In the plunker, if you delete the first table row, you'll notice that while the last row is still invalid, the errors have become attached to the row above. Then as you fill in the last row to make it valid and then empty it again, the errors still show up on the wrong row. Deleting subsequent rows just moves the errors further from the actual invalid row until they don't appear on the table at all.
Is there another way I should be going about validating these inputs?
The problem is that you you don't usally use {{}} (interpolation) in built in angular directives. So change your code to:
<td ng-class="{ 'has-error' : vm.testForm['itemName' + $index].$invalid }">
<input type="text"
name="itemName{{$index}}"
class="form-control"
ng-model="item.name"
required />
<div class="help-block"
ng-show="vm.testForm['itemName' + $index].$invalid"
ng-messages="vm.testForm['itemName'+$index].$error">
<span ng-message="required">Name is required</span>
</div>
</td>
Change:
vm.testForm.itemName{{$index}}.$invalid
To:
vm.testForm['itemName' + $index].$invalid
Made a plunker to verify:
https://plnkr.co/edit/vuQiH4D8qUY9jVoThnCy?p=preview

Input in ng-repeat not update even ng-model is changed

I have a table with input inside it to allow users edit the quantity of goods, the total values of all goods in the bill is calculated when user input the quantity via ng-change. In ng-change, i will check if the quantity is large than current stock, i will reset it to old value and do not update total values (plus show msg err etc.....). Everthing gone fine, but my only problem is I can not make the input update with the model.
<table st-table="goodsOfBillList" st-safe-src="sellDetailList" class="table table-striped">
<tr ng-repeat="row in goodsOfBillList" st-select-row="row" st-select-mode="single" ng-show="!sellBillCtrl.isLoading">
<td>
<input id="quantity" ng-model="row.quantity" ng-model-options="{ updateOn: 'blur' }"
placeholder="current stock : {{row.currentStock}}"
class="form-control input-md" required="" type="number" min="1" max="{{row.currentStock}}"
ng-change="updateTotal(row, '{{row.quantity}}')">
</td>
</tr>
</table>
When i input an invalid value (50) like the picture below, quantity is reset back to previous value (5), but the input in the model doesn't update
$scope.updateTotal = function (row, oldVal)
{
//because I set max value, if I input an invalid value, row.quantity become undefined
//Checked by using alert()
if(typeof row.quantity === "undefined")
{
row.quantity = oldVal;
alert(JSON.stringify(row));
return;
}
//logic if valid value
};
i am using input in smart-table, and the table inside a boostrap modal

angularjs filter array of objects based on specific attributes values

In the below code to search the record in the table, i added the "searchTutorial" at the filter option (filter:searchTutorila).
But if wanted to search only by the courseName and FacultyName, how the code has to be changed.
> <input id="searchCourseFaculty" type="text" ng-model="searchTutorial">
> <tr ng-repeat ="train in training | filter:searchTutorial"> <td>{{train.SessionID}}</td><td>{{train.SessionDate}}</td><td>{{train.CourseName}}</td><td>{{train.FacultyName}}</td>
> </tr>
If any know the answer, please post it.
<input id="searchCourseFaculty" type="text" ng-model="query">
in controller
$scope.searchTutorial = function(train) {
return (angular.lowercase(train.CourseName).indexOf(angular.lowercase($scope.query) || '') !== -1 ||
angular.lowercase(train.FacultyName).indexOf(angular.lowercase($scope.query) || '') !== -1);
};

Marionette ItemView and radio Buttons

I have data being returned from a server with a status flag. And on each ItemView I have some radio Buttons as follows:
<td><input type='radio' name='<%- id %>-typeRecipe' value='0'></td>
<td><input type='radio' name='<%- id %>-typeRecipe' value='2'></td>
<td><input type='radio' name='<%- id %>-typeRecipe' value='1'></td>
As each ItemView is being created I want to check if the status returned by the server is the same value as the input value ie either 0,1, or 2 and if it is set that element to selected.
Recipe = Marionette.ItemView.extend({
ui: {
comm: 'input[type=radio]'
},
onBeforeRender: function () {
for (var i = 0; i < this.ui.comm.length; i++) {
if(parseInt(this.ui.comm[i].value, 10) === this.model.get('status')) {
this.ui.comm[i].$el.prop('selected')
}
}
}
However, when I view the interface none of the radio buttons have a check in them.
When I debug the code I can see that the line this.ui.comm[i].$el.prop('selected) is being executed so I'm wondering why doesn't it display set the element to selected? BTW I tried using that function on the onRender event also but the same result
I disagree with your approach, instead of having a onBeforeRender function whose whole function is to check the "state" and set it to checked. I would move that "logic" to the template and not have it handled in the view.
Your template would look something like:
<td><input type='radio' name='<%- id %>-typeRecipe' <% if (status == '0') { %>checked='checked'<% } %>value='0'></td>
<td><input type='radio' name='<%- id %>-typeRecipe' <% if (status == '2') { %>checked='checked'<% } %>value='2'></td>
<td><input type='radio' name='<%- id %>-typeRecipe' <% if (status == '1') { %>checked='checked'<% } %>value='1'></td>
and finally your render'er?? lol. would just look something like:
this.$el.html(this.template(this.model.toJSON()));
This again is "my opinion" and you can handle it as you see fit, but i like it because that sort of mundane logic clutters the views and just leaves the person behind you reading more code than he/she has too. And ... i think this approach is more "elegant" because that loop you have going on can be avoided completely.
It is the checked property for radio inputs, not selected. Also you will want to set the second argument to true, to actually set value for that property, or you're just getting it otherwise.
$('input.selectMe').prop('checked', true);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type='radio' name='typeRecipe' value='0'>
<input type='radio' name='typeRecipe' value='2' class="selectMe">
<input type='radio' name='typeRecipe' value='1'>

ng-form not available after manually deleting an item from collection of ng-repeat

Edit Here is the plunk for this question
I have a master detail form for an accounting transaction. The master portion just contains two fields name and Type. The detail portion can have two or more entries and each entry has AccountId, Debit and Credit fields. The form looks like
You can see that there is a delete button against each entry so if we have more than two entries we can delete any entry at random. The form html looks like following
<body data-ng-app="transactions">
<div data-ng-controller="transactionsController">
<form role="form" name="transactionForm" novalidate data-ng-submit="create()">
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<h2 class="form-login-heading">Create Transaction</h2>
<div data-ng-repeat="error in errors" class="alert alert-danger">
{{error[0]}}
</div>
<input type="text" name="name" class="form-control" placeholder="Name" data-ng-model="transaction.Name" required autofocus>
<div>
<span class="error" data-ng-show="transactionForm.name.$error.required && submitted">Please enter Name</span>
</div>
<input type="text" name="type" class="form-control" placeholder="Type" data-ng-model="transaction.Type" required>
<div>
<span class="error" data-ng-show="transactionForm.type.$error.required && submitted">Please enter Type</span>
</div>
<!--<input type="text" readonly name="number" class="form-control" placeholder="Number" data-ng-model="transaction.Number">
<input type="checkbox" data-ng-model="transaction.IsFinalized" /> <label>Finalize</label>-->
<table>
<tr>
<th>Account</th>
<th>Debit</th>
<th>Credit</th>
<th> </th>
</tr>
<tr data-ng-form="entryForm" data-ng-repeat="entry in transaction.Entries track by $index">
<td>
<input required name="accountId" data-ng-model="entry.AccountId" class="form-control" />
<span class="error" data-ng-show="entryForm.accountId.$error.required && submitted">Please select an account</span>
</td>
<td>
<input type="text" name="debit" data-ng-required="!entry.CreditAmount" class="form-control" placeholder="Debit" data-ng-model="entry.DebitAmount">
<span class="error" data-ng-show="entryForm.debit.$error.required && submitted">Debit is required</span>
</td>
<td>
<input type="text" data-ng-focus="checkAddRow($index)" name="credit" data-ng-required="!entry.DebitAmount" class="form-control" placeholder="Credit" data-ng-model="entry.CreditAmount">
<span class="error" data-ng-show="entryForm.credit.$error.required && submitted">Credit is required</span>
</td>
<td><button data-ng-show="transaction.Entries.length>2" class="btn btn-md btn-info " type="button" data-ng-click="deleteRow($index)">delete</button></td>
</tr>
<tr>
<td>Total</td>
<td><input readonly name="totalDebit" type="text" class="form-control" placeholder="Total Debit" data-ng-value="totalDebit()"></td>
<td><input readonly name="totalCredit" compare-to="totalDebit" type="text" class="form-control" placeholder="Total Credit" data-ng-value="totalCredit()"></td>
</tr>
<tr>
<td></td>
<td><b>Difference</b></td>
<td>
<input name="difference" readonly type="text" class="form-control" data-ng-value="difference()">
<!--<span class="error" data-ng-show="submitted && !differencezero">Difference should be 0</span>-->
</td>
</tr>
</table><br />
<button class="btn btn-md btn-info" type="submit">Create</button>
<button class="btn btn-md btn-info" data-ng-show="transaction.Entries.length<15" type="button" data-ng-click="addRow()">Add Row</button>
<div data-ng-hide="message == ''" class="alert alert-danger">
{{message}}
</div>
</div>
<div class="col-md-4">
</div>
<div class="col-md-2">
</div>
</div>
</form>
<style type="text/css">
.error {
color: red;
}
</style>
<pre>{{transactionForm.entryForm|json}}</pre>
</div>
I have the requirement that when focus is on Credit input of last entry the new entry should automatically added to the UI. I do it by using addRow and checkAddRow method on my controller. these methods are as follows
$scope.checkAddRow = function (index) {
if (index == $scope.transaction.Entries.length - 1) {
$scope.addRow();
}
}
$scope.addRow = function () {
entry = {
EntryTime: '',
DebitAmount: '',
CreditAmount: '',
AccountId: ''
};
$scope.transaction.Entries.push(entry);
console.log($scope.transactionForm);
}
$scope.deleteRow = function (index) {
$scope.transaction.Entries.splice(index, 1);
console.log($scope.transactionForm);
}
Again this part is just fine and works well. But I have another requirement that says that if last entry is not used it should not cause the form to invalidate. It should rather be removed from transaction.Entries collection and rest of the data should be saved normally. To achieve this, I have create function defined on $scope that looks like following
$scope.create = function () {
$scope.submitted = true;
if ($scope.transactionForm.entryForm && $scope.transactionForm.entryForm.$invalid && $scope.transactionForm.entryForm.$pristine) {
$timeout(function () {
$scope.deleteRow($scope.transaction.Entries.length - 1);
});
$timeout(function () {
console.log('From time out', $scope.transactionForm.$valid);
console.log($scope.transactionForm.$valid);
if (!$scope.transactionForm.$valid) return;
alert('data saved!');
console.log($scope.transactionForm);
//$scope.transactionForm.name.focus();
}, 200);
}
else {
if ($scope.transactionForm.$valid) {
alert('data saved 2');
}
}
}
You can see that what create function is doing. It is checking if entryForm (ng-form) is present in the main form (transactionForm) then it checks if entryForm is $invalid and $pristine if all these flags are true then, I delete the last entry from $scope.transaction.Entries and save the data (currently an alert to show data is saved) after $timeout. If I don't use timeout then the form is invalid so I have to wait for 200ms before I check the forms $valid flag after removing last row. But to my surprise when I remove last row from create function, there is no entryForm attached to the outer transactionForm. On the other hand If I delete entries using delete buttons present on UI, the entryForm is present inside the main transactionForm. Can anyone explain why is that. I have added <pre>{{transactionForm|json}}</pre> at the end to see when it is and when it is not available on main form. I have created a plunk to show what I mean. just add some data in two input fields of the master portion, enter some data in accountid field of both entries, when you reach the Credit input of second (last) entry, a new entry will be automatically added. Ignore that row and just push create button. The last entry will be removed and data will save but the entryForm will not be there anymore. I am not sure what I am doing wrong.
So, a problem here is that your definition of whether the form is valid or not depends on the state of the last row.
The last row could be of the following variety:
row fetched from the backend, but not new --> should only invalidate if not valid
row is new and $pristine --> should not invalidate
row is new, but $dirty (and still last) --> should only invalidate if not valid
You are trying to remove the last row and then re-evaluate the form for validity.
Approach it the other way - don't let the last row invalidate the form if it's in $pristine state:
Here's a simplified example:
<form name="transactionForm" ng-submit="submit()" novalidate>
<table>
<tr ng-form="entryForm" ng-repeat="transaction in transactions">
<td><input ng-model="transaction.account"
ng-required="transaction !== newLastEntry || entryForm.$dirty"></td>
<td><input ng-model="transaction.amount"
ng-required="transaction !== newLastEntry || entryForm.$dirty"
ng-focus="addEntryIfLast($last)" type="number"></td>
</tr>
</table>
</form>
Note $scope.newLastEntry here. It is set to the new empty (and last) entry. This happens when you add a new empty row:
function addEmptyEntry(){
$scope.newLastEntry = {};
$scope.transactions.push($scope.newLastEntry);
}
And so, ng-required is applied only if row is NOT new last OR otherwise $dirty.
Then, on submit, you can remove the last entry if it's in $pristine state and indeed the new last (as opposed to whatever existed before):
$scope.submit = function(){
var itemsToSubmit = angular.copy($scope.transactions);
if ($scope.transactionForm.$invalid) return;
if ($scope.transactionForm.entryForm &&
$scope.transactionForm.entryForm.$pristine &&
$scope.transactions[$scope.transactions.length - 1] === $scope.newLastEntry) {
itemsToSubmit.splice(itemsToSubmit.length - 1);
}
console.log(JSON.stringify(itemsToSubmit));
};
plunker

Resources