Angular-UI & Bootstrap: change class based on class - angularjs

I am using Angular-UI and Bootstrap 3.
I have this HTML connected to a scope (assume the scope has a $scope.myBtn = "A", say).
<button type="button" class="btn btn-primary" ng-model="myBtn" btn-radio="A">A</button>
<button type="button" class="btn btn-primary" ng-model="myBtn" btn-radio="B">B</button>
<button type="button" class="btn btn-primary" ng-model="myBtn" btn-radio="C">C</button>
This produces three blue buttons, which is what I want. When one of the buttons is clicked, the $scope.myBtn value gets set to the right value (say, "B") and that button's class gets set to:
<button type="button"
class="btn btn-primary active" ng-model="myBtn" btn-radio="B">B</button>
(notice the addition of active in the class).
When one button is active I want to remove the "btn-primary" class and add the "btn-success" class. I know I could do it this way (and it is what I am actually using now):
<button
type = "button"
class = "btn"
ng-class = "{
'btn-primary': myBtn != 'B',
'btn-success': myBtn == 'B'
}"
ng-model = "myBtn"
btn-radio = "'B'">B</button>
But that seems brutally verbose for every button... Is there some better way to do this?

You can use a directive to conditionally set the class by observing the model value
app.directive('myClass', function () {
return {
link: function (scope, element, attrs) {
attrs.$observe('ngModel', function (item) {
if (scope.myBtn !== 'B') {
attrs.$set('class', "btn-primary");
} else {
attrs.$set('class', "btn-success");
}
});
}
}
});
Working Demo

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".

How to modify value on DOM button?

How to modify text on one specific DOM object? Tried the following code but ended up changing for all the DOM text. It changes all the {{content}} values not only 'b1'.
myfunc () {
$scope.content="nothing";
angular.element(document.getElementById('b1')).scope().content ='something';
}
Here is the html:
<button id="b1" type="button" ng-click="myfunc($event)">
{{content}}
</button>
<button id="b2" type="button" ng-click="myfunc($event)">
{{content}}
</button>
You should not use angular.element or do any DOM manipulation whatsoever anywhere else than in directives. What you want to do here is define $scope.contents as an array / object and then update only the index you want:
$scope.contents = Array(2).fill('nothing');
$scope.updateButton = function(index) {
$scope.contents[index] = 'something';
};
As for your template:
<button type="button" ng-click="updateButton(0)">{{ contents[0] }}</button>
<button type="button" ng-click="updateButton(1)">{{ contents[1] }}</button>

Can't create a popover with custom template

First I tried using angular-ui
<span popover-template="removePopover.html" popover-title="Remove?" class="glyphicon glyphicon-remove cursor-select"></span>
here the template is not included and no errors are provided in console. As I undestood from previous questions this capability is still in development (using v0.13.0).
Then I tried using bootstrap's popover
<span delete-popover row-index={{$index}} data-placement="left" class="glyphicon glyphicon-remove cursor-select"></span>
This is included to popover
<div id="removePopover" style="display: none">
<button id="remove" type="button" ng-click="removeElement()" class="btn btn-danger">Remove</button>
<button type="button" ng-click="cancelElement()" class="btn btn-warning">Cancel</button>
</div>
This is the managing directive
app.directive('deletePopover', function(){
return{
link: function(scope, element, attrs) {
$(element).popover({
html : true,
container : element,
content: function() {
return $('#removePopover').html();
}
});
scope.removeElement = function(){
console.log("remove"); //does not get here
}
scope.cancelElement = function(){
console.log("cancel"); //does not get here
}
}
};
});
In case of bootstrap's popover the scope is messed up. cancelElement() call does not arrive in directive neither the parent controller.
If anyone could help me get atleast on of these working it would be great.

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