Angularjs required validator for array in model - angularjs

I have an array of 'bikes' that is required to be populated in my form (minimum length of 1). In my controller I have an empty array 'this.bikes = [];'
I have some controls which add and remove bikes from the array.
addBike(bike){
this.bikes.push(bike);
this.currentBike = null;
}
removeBike(bike){
this.bikes = this.bikes.filter((b) => bike != b);
}
How do I apply form validation on the array itself so that my form shows invalid if the array is empty (Something like 'if ($ctrl.bikes.length == 0) $ctrl.form.bikes.$valid = false)?
<label for="bike-make-model">Enter make and model of bike</label>
<div class="input-group p-relative">
<input name="bikeMakeModel" type="text" ng-model="$ctrl.currentBike" class="form-control" id="bike-make-model">
<span class="input-group-btn ">
<button class="btn btn-default" ng-click="$ctrl.addBike($ctrl.currentBike)" ng-disabled="!$ctrl.currentBike">ADD</button>
</span>
</div>
</div>
<div class="list-group" ng-show="$ctrl.bikes.length > 0">
<ul>
<li class="list-group-item" ng-repeat="bike in $ctrl.bikes">
<span>{{bike}} </span>
<i ng-click="$ctrl.removeBike(bike)" class="pointer pull-right far fa-trash-alt"/>
</li>
</ul>
</div>
I want to use it for to disable my submit button using the ng-disabled directive.
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid" ng-click="$ctrl.continue()">Next</button>

Have you tried
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid || $ctrl.bikes.length==0" ng-click="$ctrl.continue()">Next</button>
or you could add a variable in your script to monitor the length of the array. Say
addBike(bike){
this.bikes.push(bike);
this.currentBike = null;
this.bikesArrayLength = this.bikes.length;
}
removeBike(bike){
this.bikes = this.bikes.filter((b) => bike != b);
this.bikesArrayLength = this.bikes.length;
}
and in your button do
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid || $ctrl.bikesArrayLength==0" ng-click="$ctrl.continue()">Next</button>

You can use directive for this purpose. Make a directive and in link function define the logic , if the array is empty make form invalid and it won't submit, your code is short that is why , i can't do it for you.
This is angular way to solve your problem. All i can do now is provide the logic or give you idea how this will go down.
.directive("dirName",function(){
return {
require: "ngModel",
scope: {
confirmArrayLength: "="
},
link: function(scope, element, attributes, modelVal) {
modelVal.$validators.dirName= function(val) {
return "your logic to check if array is empty or not!"
};
// and $watch function will validate and invalidate on the basis of return value of above function
scope.$watch("confirmArrayLength", function() {
modelVal.$validate();
});
}//link ends here
};//return ends here})
If this is the solution of your problem then accept this answer so that this thread can be close, thanks. And if you need any further assistance then feel free to ask.

Related

Custom AngularJS directive with xeditable: doesn't work first time, but works afterward

Building an AngularJS application, I'm using xeditable to allow the user to edit a table, row per row (as described here: xeditable).
The Edit, Remove and Cancel button logic being a bit cumbersome to implement in each table, I created a custom directive: sen-edit-row.
The logic of the buttons in the custom directive works well... except in one case.
When the user clicks [Add], a row is added (just like in xeditable's example), and the new row is immediately editable. Or it should!
The very first time, the row is not editable. The input fields are not created by xeditable. The buttons of my own directive still work.
But the most bizarre thing is: the 2nd time the user clicks [Add], it just works. And then it keeps working!
(this is driving me nuts)
In the HTML, the directive is called this way:
<tr ng-repeat="access in denyAccessTable">
<td class="col-md-4"><div editable-text="access.user" e-form="rowForm">{{ access.user }}</div></td>
<td class="col-md-4"><div editable-text="access.host" e-form="rowForm">{{ access.host }}</div></td>
<td class="col-md-2"><div editable-text="access.mode" e-form="rowForm">{{ access.mode }}</div></td>
<td class="col-md-2"><div sen-edit-row save="saveDenyAccess()" remove="removeDenyAccess($index)" shown="access == newDenyAccess"></div></td>
</tr>
In the JS, the directive is defined as:
angular.module("app", ["xeditable"]).directive("senEditRow", [function() {
return {
restrict: "A",
templateUrl: "sen-edit-row.html",
scope: {
save: "&",
remove: "&",
shown: "="
},
link: function(scope, element, attrs) {
}
}
}])
And the template of the directive:
<div style="white-space: nowrap">
<form class="form-inline" editable-form name="rowForm" onaftersave="save()" ng-show="rowForm.$visible" shown="shown">
<button type="submit" class="btn btn-primary" ng-disabled="rowForm.$waiting">Save</button>
<button type="button" class="btn btn-link" ng-disabled="rowForm.$waiting" ng-click="shown ? remove() : rowForm.$cancel()">Cancel</button>
</form>
<!-- Buttons to edit and remove -->
<div ng-hide="rowForm.$visible">
<span class="glyphicon glyphicon-edit" style="margin-right: 5px;"></span>Edit
<span class="glyphicon glyphicon-trash" style="margin-left: 10px; margin-right: 5px;"></span>Remove
<button class="btn btn-danger" ng-click="remove()" ng-show="showDeleteButton">Remove</button>
<button class="btn btn-link" ng-click="showDeleteButton = false" ng-show="showDeleteButton">Cancel</button>
</div>
</div>
I also created a Plunker that shows evidence of this behavior.
Question: How do I make my directive and xeditable work consistently, including the first time, and activate immediately when a row is added to the table?
Thank you!

Is it possible to change angular bootstrap uib-dropdown templateUrl dynamically?

I want to change the uib-dropdown template dynamically when the user clicks one of its <li>, just like he could "navigate" within that dropdown.
I tried to make it via templateUrl, but nor the ng-templates nor standalone partials can successfully change the dropdown template dynamically, just like this plunkr demonstrates.
My goal is to create a faceted navigation via this dropdown to build query visually, as seen on Fieldbook's Sprint tracker (account required), which is something really like Pure Angular Advanced Searchbox, but I'm having overwhelming issues using this library.
Is this possible to achieve using just AngularJS and angular-bootstrap?
EDIT: You should assign the value of template-url using a controller var which changes as the user select any of the options, then you "repaint" the component, this way the "new" dropdown is "repainted" with the new template.
Yes, it's possible according to the official documentation, though I've never done this before.
You can specify a uib-dropdown-menu settings called template-url.
According to the docs the
default value is none
and
you may specify a template for the dropdown menu
Demo(try the last one):
http://plnkr.co/edit/1yLmarsQFDzcLd0e8Afu?p=preview
How to get it to work?
Based on your plunkr, you should change
<div class="input-group" uib-dropdown auto-close="disabled">
<input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
<ul class="dropdown-menu" role="menu" ng-if="ctrl.dropdownReady" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
</ul>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
to
<div class="input-group" uib-dropdown auto-close="disabled" ng-if="ctrl.dropdownReady">
<input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
<ul class="dropdown-menu" role="menu" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
</ul>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
In which the ng-if="ctrl.dropdownReady" is moved to the div.input-group.
And change
vm.dropdownReady = false;
console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
switch (template) {
case 'word':
partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
break;
case 'main':
partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
break;
}
vm.dropdownReady = true;
to
vm.dropdownReady = false;
console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
switch (template) {
case 'word':
partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
break;
case 'main':
partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
break;
}
$timeout(function(){
vm.dropdownReady = true;
});
Which has a $timeout wrap the vm.dropdownReady = true;. And you should inject the $timeout by hand;
Keep the menu open
According to the documentation, we can choose the initial state of drop menu with is-open attr. And we can listen the toggle event with on-toggle attr. So if we want to keep the menu open after user clicking the input, we should set the attributes of uib-dropdown like this:
<div class="input-group" uib-dropdown auto-close="disabled" ng-if="ctrl.dropdownReady" is-open="ctrl.open" on-toggle="ctrl.toggled(open)">
And in controller:
vm.toggled = function (open) {
// the parameter `open` is maintained by *angular-ui/bootstrap*
vm.open=open;//we don't need to init the `open` attr, since it's undefined at beginning
}
With these things done, once the menu is open, it doesn't close without user clicking the input again.
Why?
Let's check this snippet:
$templateRequest(self.dropdownMenuTemplateUrl)
.then(function(tplContent) {
templateScope = scope.$new();
$compile(tplContent.trim())(templateScope, function(dropdownElement) {
var newEl = dropdownElement;
self.dropdownMenu.replaceWith(newEl);//important
self.dropdownMenu = newEl;
$document.on('keydown', uibDropdownService.keybindFilter);
});
});
The snippet above shows how does angular-ui/bootstrap use the template-url attr to retrive template and take it into effect. It replaces the original ul element with a newly created element. That's why the uib-dropdown-menu and template-url is missing after clicking the ul. Since they don't exist, you can't change the template-url with angular binding anymore.
The reason executing vm.dropdownReady = true; immediately after the vm.dropdownReady = false; doesn't work is that angular have no chance to dectect this change and execute the "ng-if" to actually remove the dom. You must make the toggling of vm.dropdownReady asynchronous to give angular a chance to achieve this.

Setting button/select options from Salesforce quicklist options

I would like to have two menus, where the choice in the first menu sets the options in the second menu. This is to be done on a Salesforce VisualForce page that is using Angular JS and specifically the UI-Grid module for it. Similar controls are using the Angular button classes for menus, but I don't see how to dynamically set the choices using a button.
My current design is using a select control and ng-options.
In the controller.js I have declared variables to be used by the menus and a function to swap out the array values for the second menu. Note that $scope.fieldOptions is set through a query to Salesforce to pull option values from quicklist fields. This array is used in the UI-Grid filter option that accepts in a format like "[ { value: '1', label: 'male' }, { value: '2', label: 'female' }...".
$scope.editRows = 'all';
$scope.editField = 'Program';
$scope.editValue;
$scope.editOptions = {};
$scope.setEditOptions = function(editField) {
switch (editField) {
case "Program" :
$scope.editOptions = $scope.fieldOptions.Program_Enrolled__c;
break;
case "Appl. ASSET" :
$scope.editOptions = $scope.fieldOptions.Applied_for_ASSET__c;
break;
case "4 Year Degree" :
$scope.editOptions = $scope.fieldOptions.Has_Student_Completed_4_Year_Degree__c;
break;
}
}
On the VisualForce page I have the buttons defined in a button group with the select control just after.
<div class="row" style="margin-bottom:10px;">
<div class="col-md-12">
<div class="btn-group">
<div class="btn-group" dropdown="true" dropdown-append-to-body="true">
<button class="btn btn-default dropdown-toggle" dropdown-toggle="true" type="button">
{{editRows | capitalize}} Rows <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li ng-click="editRows = 'all'">All Rows</li>
<li ng-click="editRows = 'visible'">Visible Rows Only</li>
<li ng-click="editRows = 'selected'">Selected Rows Only</li>
</ul>
</div>
<div class="btn-group" dropdown="true" dropdown-append-to-body="true">
<button class="btn btn-default dropdown-toggle" ng-click="setEditOptions(editField)"
dropdown-toggle="true" type="button">
{{editField | capitalize}} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li ng-click="editField = 'Program'">Program Enrolled</li>
<li ng-click="editField = 'Appl. ASSET'">Applied for ASSET</li>
<li ng-click="editField = '4 Year Degree'">4-year degree?</li>
</ul>
</div>
<div>
<select ng-model="editValue" ng-options="option.label for option.value in selectOptions">
</select>
</div>
</div>
<button id="split-button" type="button" class="btn btn-default" ng-click="massUpdate()">Modify</button>
</div>
</div>
Am I on the right track? If there is an easier method, or one that would use a button menu directly, please feel free to offer suggestions. Otherwise, is the issue how I am using ng-options?
Okay, easier than I thought. Two typos. First, I had the array declared with {} instead of []. And I misspelled it in the select statement ng-options section. Fixed that and it's working.
If anyone knows any tricks to get this working with a button so it would match the others though, that would be wonderful. Otherwise I'm thinking I'll go with all selects for consistency. Also, maybe bundle these options into an array with the primary menu choices as the keys so I can just reference without the case statement.
$scope.editRows = 'all';
$scope.editField = 'Program';
$scope.editValue;
$scope.editOptions = {};

AngularJS ng-if doesn't show the DIV

Why my app isn't working properly? It must show one (one at time) of the div on the bottom with the "ng-if" tag..
This is the fiddle:
Fiddle
<div class="fix" ng-if="showAdd()">
<button type="button" class="btn btn-link">
<span class="glyphicon glyphicon-plus"></span>
<span class="fix">Aggiungi un Preferito</span>
</button>
<div class="add">
Aggiungi un Preferito
</div>
</div>
<div class="edit" ng-if="showEdit()">
Modifica
</div>
The problem is with the showEdit() function.
From your fiddle you have:
function showEdit() {
return $scope.startEdit && !$scope.startAdd;
}
Where startEdit and startAdd are defined as:
function startAdd() {
$scope.addBookmark = true;
$scope.editBookmark = false;
}
function startEdit() {
$scope.editBookmark = true;
$scope.addBookmark = false;
}
When your ng-if calls showEdit() it will return $scope.startEdit && !$scope.startAdd;
However, $scope.startEdit and $scope.startAdd are functions, so they will be "truthy" (i.e. evaluate to true in a boolean expression). Therefore, the boolean expression always evaluates to false (and your DIV remains hidden).
See below:
$scope.startEdit && !$scope.startAdd;
true && !true
true && false
false
It looks like you're missing something conceptually with either calling functions or with evaluating boolean expressions.
If you want to call a JavaScript function, you have to follow the name of the function with parenthesis, just like you did with your ng-if="showEdit()" block.
Similarly, if $scope.showEdit() is meant to call startAdd() and startEdit(), you should do something like this:
function showEdit() {
return $scope.startEdit() && !$scope.startAdd();
}
You'd still have a problem, however, as startEdit() and startAdd() don't return anything, and would therefore evaluate to undefined.
If you edit your showEdit() function as described above and have startEdit() and startAdd() return a boolean expression, you should be good to go.
It looks like there's a mistake in your fiddle. The edit div will show up if you change your showAdd and showEdit methods to the following:
function showAdd() {
return $scope.addBookmark && !$scope.editBookmark;
}
function showEdit() {
return $scope.editBookmark && !$scope.addBookmark;
}
The add div never gets added because that would be activated by the startAdd function, which isn't called anywhere.
Also, please post your javascript code here. That way, if something happens to your fiddle, this question might still be useful to others.
EDIT:
To get your add button to work you need to change this:
<div class="fix" ng-if="showAdd()">
<button type="button" class="btn btn-link">
<span class="glyphicon glyphicon-plus"></span>
<span class="fix">Aggiungi un Preferito</span>
</button>
<div class="add">
Aggiungi un Preferito
</div>
</div>
To this:
<button type="button" class="btn btn-link" ng-click="startAdd()">
<span class="glyphicon glyphicon-plus"></span>
<span class="fix">Aggiungi un Preferito</span>
</button>
<div class="fix" ng-if="showAdd()">
<div class="add">
Aggiungi un Preferito
</div>
</div>
If the desire is to always show one or the other, then it is best practice to structure the view as follows:
<div class="fix" ng-if="showingAdd">
<button type="button" class="btn btn-link">
<span class="glyphicon glyphicon-plus"></span>
<span class="fix">Aggiungi un Preferito</span>
</button>
<div class="add">
Aggiungi un Preferito
</div>
</div>
<div class="edit" ng-if="!showingAdd">
Modifica
</div>

Angular Bootstrap UI: Input Fields in Tab-Heading

Again I got a Problem with Angular Bootstrap UI Tabs.
Short description of my problem:
I want the user to create different pages with different titles. After a page is created, I create a new tab with the pagetitle and user can add content. That works fine so far.
Now, in the uib-tab-heading I have an option to edit the page-title
<uib-tab-heading>
<div class="pull-left">
<span data-ng-if="!editable[$index]">{{title}}</span>
<input data-ng-if="editable[$index]" data-ng-model="titles[$index]">
</div>
<div class="pull-right">
<button data-ng-if="!editable[$index]" data-ng-click="edit($index)"><span class="glyphicon glyphicon-wrench"></span></button>
<button data-ng-if="editable[$index]" data-ng-click="save($index)"><span style="color:green;" class="glyphicon glyphicon-ok"></span></button>
</div>
<div style="clear:both;"></div>
</uib-tab-heading>
The button action sets a variable so the input field in the tab appears. That works so far.
But in the input field I only can edit one single letter, then, after input, the input field looses its focus and the tab is changed in a random way.
Is there a common method to disable keyboard interaction with the tabs so I can change the value of the input field without getting the tab changed?
One option is just to force the focus back to the input element whenever it is modified.
See fiddle: https://jsfiddle.net/masa671/2mq3106a/
Markup:
<input data-ng-if="editable[$index]" ng-model="titles[$index]" focus-me>
JavaScript:
.directive('focusMe', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function() {
elem[0].focus();
});
}
};
});
Thanks for your answer!
I modified your fiddle a bit to fit my code.
See fiddle: https://jsfiddle.net/2mq3106a/7/
<div ng-controller="MyCtrl">
<uib-tabset>
<uib-tab class="tabcontent-bordered" data-ng-repeat="pageTitle in titles">
<uib-tab-heading>
<div class="pull-left">
<span data-ng-if="!editable[$index]">{{pageTitle}}</span>
<input id="$index" data-ng-if="editable[$index]" ng-model="titles[$index]" focus-me>
</div>
<div class="pull-right">
<button data-ng-if="!editable[$index]" data-ng-click="edit($index)"><span class="glyphicon glyphicon-wrench"></span></button>
<button data-ng-if="editable[$index]" data-ng-click="save($index)"><span style="color:green;" class="glyphicon glyphicon-ok"></span></button>
</div>
<div style="clear:both;"></div>
</uib-tab-heading>
I am on Tab {{$index}}
</uib-tab>
</uib-tabset>
</div>
I do not lose the focus on the input field, thats works so far, but there is still an other problem.
As you may can see in the fiddle if i change input value of the input field, the other tab gets active state and so the content of the other tab is shown.

Resources