Disable click on anchor if form is invalid - angularjs

I've tried several solutions on other answers but so far none has worked as needed, basically I need to disable a button (anchor) if the form is invalid, disabling is not the problem, avoiding the call to the function is.
For that I tried something like this:
<a class="btn icon-btn btn-success" ng-disabled="myForm.$invalid" novalidate ng-submit="myForm.$valid && submit.addOrEditItem()">
<span class="glyphicon btn-glyphicon glyphicon-save img-circle text-success"></span>Save</a>
And on my controller:
$scope.isCreating = true;
$scope.submit = {
addOrEdit: function() {
if($scope.isCreating){
$scope.items.push({type: $scope.newItem.name, description: $scope.newItem.descriptions, isDone:false, editable:false});
}else{
$scope.eItem.type = $scope.newItem.name;
$scope.eItem.description = $scope.newItem.descriptions;
}
$scope.isCreating = true;
$scope.newItem = {};
}
}
Is there any alternatives for this?

Instead of ng-submit, use ng-click:
<a class="btn icon-btn btn-success" ng-disabled="myForm.$invalid" novalidate ng-click="myForm.$valid && submit.addOrEdit()">

Related

AngularJS disable double click

I have a problem with a button which contacts server on click. If you do a double click (or any number of clicks for that matter) you will call the server that number of times.
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'ADMIN.CONTENT.DELIVERIES.BODY.CLOSE' | translate }}</button>
<a class="btn btn-danger" ng-click="vm.markDelivered()" ng-dblclick="return" ng-disabled="flag">{{ 'MANAGER.CONTENT.DELIVERIES.BODY.DELETE_PANEL.CONFIRM' | translate }}</a>
</div>
I have tried with ng-disabled but for some reason it doesn't work, as it is saying that that element is not allowed there. I tried changing a to button, but that seems the same. ng-dblclick="return" does nothing also.
Even I had the same issue,And solved using this approach.
<div class="col-sm-4 form-group pull-right">
<input type="submit" name="Submit" class="btn btn-primary"
value="Submit" data-ng-disabled="myForm.$invalid"
ng-click="myForm.$invalid=true;vm.markDelivered()" />
</div>
So on first click myForm.$invalid=true will be set and button will be disabled. SO you will not have multiple calls to your server side code.
So with Bootstrap buttons you won't be able to use ng-disabled. You would have to do it this way:
<div class="btn btn-default" ng-class="{'disabled': idDisabled}" ng-click="doSomething()">I'm a button!</div>
where you are setting the class disabled on the button. But this does not disable the action itself. So when the button is pressed you would need to check that isDisabled variable and if it is true just return and don't do the intended action.
So for example:
doSomething() {
if (isDisabled) {
return
} else {
// do your server call
// when call is finished set isDisabled = false
}
}
I see a couple of issues here.
First, change the anchor tag to a button.
Second, you seem to be using 'controller as' syntax. So, you are probably setting your flag in the vm. But, in your html, you are looking for the flag in the $scope. Change ng-disabled="flag" to ng-disabled="vm.flag"
Your final button line would look like this:
<button class="btn btn-danger" ng-click="vm.markDelivered()" ng-dblclick="return" ng-disabled="vm.flag">{{ 'MANAGER.CONTENT.DELIVERIES.BODY.DELETE_PANEL.CONFIRM' | translate }}</button>
Adding a working plunker here demonstrating ng-disabled
What about using a simple behaviour directive?
function oneTimeCallDirective($timeout) {
var httpCallMock = (cb = () => 'ok') => $timeout(cb, 5000);
return function oneTimeCallPostLink(iScope, iElement) {
let busy = false;
return iElement
.on('click dblclick', function(event) {
if(busy) {
return event.preventDefault();
}
busy = true;
console.info('Well, Calling.');
return httpCallMock()
.then(() => {
console.log('You Can Start Calling Again');
})
.finally(() => {
busy = false;
})
;
})
;
};
}
angular
.module('test', [])
.directive('oneTimeCall', oneTimeCallDirective)
;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="test">
<button one-time-call>Click Here</button>
</section>

Angular ng-click function requiring two clicks to take effect

I have the following directive
app.directive('replybox', function ($timeout, $window, $compile, $sce) {
var linkFn = function (scope, element, attrs) {
var exampleText= element.find('p');
var btn = element.find('button');
var windowSelection="";
scope.okOrCancel="None";
exampleText.bind("mouseup", function () {
scope.sel = window.getSelection().toString();
windowSelection=window.getSelection().getRangeAt(0);
if(scope.sel.length>0) {
scope.showModal = true;
scope.$apply();
}
});
btn.bind("click", function () {
if(scope.okOrCancel=='ok'){
range = windowSelection;
var replaceText = range.toString();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = '<poper>' + replaceText + '<button type="button" class="btn btn-danger btn-xs">×</button></poper>';
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
$compile(frag)(scope);
range.insertNode(frag);
scope.showModal=false;
}
if(scope.okOrCancel=='cancel'){
scope.showModal=false;
}
scope.selection="None";
scope.okOrCancel='None';
});
};
return {
link: linkFn,
restrict: 'A',
scope: {
entities: '=',
selection:'='
},
template: `<ng-transclude></ng-transclude>
<div class="modal fade in" style="display: block;" role="dialog" ng-show="showModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{{sel}}
</div>
<div class="radio">
<div ng-repeat="x in entities">
<div class="radio">
<label>
<input type="radio" name="choice" ng-model="$parent.selection" ng-value = "x">
{{x}}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="okOrCancel='ok'">
Ok
</button>
<button type="button" class="btn btn-primary" ng-click="okOrCancel='cancel'">
Cancel
</button>
</div>
</div>
</div>
</div>`,
transclude: true
};
});
So there is a modal in the template which contains an "Ok" and a "Cancel" button. There is an ng-click on these buttons which sets scope.okOrCancel to the appropriate value. btn binds to a button click and performs different actions depending on the state of scope.okOrCancel. When the "Ok" button is clicked everything works as expected. But the "Cancel" button requires two clicks in order for the modal to dissappear. I would think this would happen immediately within
if(scope.okOrCancel=='cancel'){
scope.showModal=false;
}
Can anyone tell me why the cancel button requires two clicks to close the modal?
Currently you have a mix of jQuery and angularjs for your ok and cancel click. Probably that is the reason to require two clicks to take effect.
If I were you, I would have write click like below:
Template:
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="okClick()"> Ok </button>
<button type="button" class="btn btn-primary" ng-click="cancelClick()"> Cancel </button>
</div>
In JS:
scope.okClick = function() {
range = windowSelection;
var replaceText = range.toString();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = '<poper>' + replaceText + '<button type="button" class="btn btn-danger btn-xs">×</button></poper>';
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
$compile(frag)(scope);
range.insertNode(frag);
scope.showModal=false;
}
scope.cancelClick = function() {
scope.showModal=false;
}
scope.selection="None";
scope.okOrCancel='None';
I hope this helps you!
Cheers
Completely agree with varit05's answer. Most likely it's because you do not trigger digest cycle in the click event handler. But in any way, the point is: it's not very good idea to mix jquery and angular stuff, unless: a) you absolutely sure it's necessary; b) you understand very well what you're doing and why; otherwise it will lead to such an unexpected consequences.
Just another a small addition. Another problem is here:
$compile(frag)(scope);
range.insertNode(frag);
The correct approach would actually be to insert new nodes into real DOM and only then $compile() them. Otherwise, any directives with require: "^something" in the DOM being inserted will fail to compile because they would not be able to find necessary controllers in upper nodes until new nodes actually make it to the "main" DOM tree. Of course, if you absolutely sure you don't have that then you can leave it as is and it will work... But then the problem will just wait out there for its "finest hour".

Compiling Another Directive inside a Directive not working

I'm developing a blog website and currently working on a post creator. The post creator will eventually allow a user to add HTML elements to their post and edit them when needed, before submitting the post.
So far, a user can add a Header or Rich Text to their post. I'm working on rendering these components based on content information retrieved from the backend, as such:
<div ng-repeat="component in components track by $index">
<editable type="component.type" model="component.content"></editable>
</div>
The editable directive looks like the following:
(function() {
'use strict';
angular.module('blog')
.directive('editable', directive);
directive.$inject = ['$compile'];
function directive($compile) {
return {
restrict: 'E',
templateUrl: 'components/editable/editable.html',
scope: {
type: '=',
model: '='
},
link: function(scope, element) {
scope.editing = false;
scope.currentModel = scope.model;
var viewTemplate, editTemplate;
switch(scope.type) {
case 'header':
viewTemplate = '<h2 ng-show="!editing">{{currentModel}}</h2>';
editTemplate = '<input ng-show="editing" type="text" class="form-control" ng-model="model">';
compileTemplate(viewTemplate, editTemplate);
break;
case 'richtext':
viewTemplate = '<div ng-show="!editing">{{currentModel}}</div>';
editTemplate = '<summernote ng-show="editing" ng-model="model" height="300"></summernote>';
compileTemplate(viewTemplate, editTemplate);
break;
default:
break;
}
function compileTemplate(viewTemplate, editTemplate) {
var viewTemplateCompiled, editTemplateCompiled;
viewTemplateCompiled = $compile(angular.element(viewTemplate))(scope);
editTemplateCompiled = $compile(angular.element(editTemplate))(scope);
element.find('view').replaceWith(viewTemplateCompiled);
element.find('edit').replaceWith(editTemplateCompiled);
}
scope.toggleEditMode = function(saveChanges) {
scope.editing = !scope.editing;
if (saveChanges) {
scope.currentModel = scope.model;
}
}
}
}
}
}());
The template looks like the following:
<div class="row">
<div class="col-md-8">
<view></view>
<edit></edit>
</div>
<div class="col-md-4">
<span class="pull-right">
<button ng-show="editing" class="btn btn-success" ng-click="toggleEditMode(true)"><span class="glyphicon glyphicon-ok"></span></button>
<button ng-show="editing" class="btn btn-danger" ng-click="toggleEditMode(false)"><span class="glyphicon glyphicon-remove"></span></button>
<button ng-show="!editing" class="btn btn-warning" ng-click="toggleEditMode(true)"><span class="glyphicon glyphicon-edit"></span></button>
</span>
</div>
</div>
Here's how the website looks like in "view" mode, with the HTML for summernote inspected:
When the "edit" button is clicked (yellow one on the right with the icon), The second line should appear in a summernote editor, but the editor never shows up:
I've noticed that the summernote editor is never inserted after compiling (you'll see that it's not there in the developer tools/element inspector). Perhaps this is the issue? If so, is there a way to fix it?
P.S.: I can create a Plunkr on request.
function compileTemplate(viewTemplate, editTemplate) {
var viewTemplateCompiled = angular.element(viewTemplate),
editTemplateCompiled = angular.element(editTemplate);
element.find('view').replaceWith(viewTemplateCompiled);
element.find('edit').replaceWith(editTemplateCompiled);
$compile(viewTemplateCompiled)(scope);
$compile(editTemplateCompiled)(scope);
}

AngularJS - showing an item only if none of the booleans in a collection are true

I would like to show the button in the code only if none of the aspects in $scope.aspects are true.
<aspect-form ng-repeat="aspect in aspects | filter:{active: true}">
</aspect-form>
<a class="btn btn-primary btn-lg" role="button">Save</a>
How would I do this?
You can do it without binding to a function on the scope like this:
<a class="btn btn-primary btn-lg"
ng-if="(aspects | filter:{active: true}).length === 0">Save</a>
In situations like these I like to use the array.every() and array.some() functions:
<a class="btn btn-primary btn-lg" role="button" ng-if="allAspectsFalse()">Save</a>
$scope.allAspectsFalse = function() {
return $scope.aspects.every(function(aspect) {
return !aspect.active;
});
}
You can use ng-if in the a element, and give it a function that evaluates to true only when all of the aspects in $scope.aspects are false.
<a class="btn btn-primary btn-lg" ng-if="allAspectsAreFalse()">Save</a>
I also found a solution. Mine is using ng-show instead but is practically the same. I will show the whole solution.
<a class="btn btn-primary btn-lg" role="button" ng-show="someAspectShowing()">Save</a>
and in the controller I have defined the function:
$scope.someAspectShowing = function() {
for(index=0; index < $scope.aspects.length; ++index) {
if($scope.aspects[index].active === true) {
return true;
}
}
return false;
};

Button dynamic ng-click stuck after first click

I have a button to change the status (STARTED/STOPPED). The button is created in a cell of ng-grid.
The cell is defined as {field:'status', displayName:'Status', cellTemplate: 'cell/statusCellTemplate.html'}] where the template is
<button class="btn btn-primary" ng-click="changeStatus(row.getProperty('id'),'{{row.getProperty(col.field) | switchStatus}}')">{{row.getProperty(col.field)}}</button>
myapp.filter('switchStatus', function() {
return function(input) {
return (input == 'STOPPED') ? 'STARTED' : 'STOPPED';
};
});
In the Plunker, the initial state is STOPPED so the rendered cell is
<button class="btn btn-primary ng-scope ng-binding" ng-click="changeStatus(row.getProperty('id'),'STARTED')">STOPPED</button>
Then clicking the button switch the status as expected and the rendered cell is
<button class="btn btn-primary ng-scope ng-binding" ng-click="changeStatus(row.getProperty('id'),'STOPPED')">STARTED</button>
But clicking the button does not switch the status which is stuck to STARTED whereas the parameter is set to STOPPED.
The Plunker that reproduces the issue.
Edit: Better example
The first answer made it work by removing the filter but I want to keep it. So I created an example where a button is rendered and clicking the button increments the content of the data. The counter is stuck after the first click.
Init: <button class="btn btn-primary ng-scope" ng-click="increment(1)">Increment</button>
Click: <button class="btn btn-primary ng-scope" ng-click="increment(2)">Increment</button>
Click: <button class="btn btn-primary ng-scope" ng-click="increment(2)">Increment</button> Should be increment(3).
The modified Plunker
There were a couple of things that I changed to get this plunker working.
First, in your cell template the changeStatus method didn't need interpolation around the current status. So this:
changeStatus(row.getProperty('id'),'{{row.getProperty(col.field) | switchStatus}}')"
changed to this:
changeStatus(row.getProperty('id'),row.getProperty(col.field))"
Second, you'll notice that I took off the filter inside the cell template. This would have the effect of calling the $filter again and reversing the toggle.
Finally, I noticed that the toggle logic was reversed. So I changed this:
$scope.changeStatus= function(id, status) {
console.log(status);
$scope.currentStatus = status;
if(status == 'STOPPED') {
$scope.myData = [{status: 'STOPPED'}];
} else {
$scope.myData = [{status: 'STARTED'}];
}
$scope.nextStatus = $filter('switchStatus')(status);
}
To this:
$scope.changeStatus= function(id, status) {
console.log(status);
$scope.currentStatus = status;
if(status == 'STOPPED') {
$scope.myData = [{status: 'STARTED'}];
} else {
$scope.myData = [{status: 'STOPPED'}];
}
$scope.nextStatus = $filter('switchStatus')(status);
}
All seems to be working now. Hope this helps.

Resources