Angular ng-click function requiring two clicks to take effect - angularjs

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

Related

Data attribute to call function when modal dialog is closed

I'm using Bootstrap in conjunction with AngularJS to open modal dialogs. To activate a modal without writing JavaScript code, I use the data attributes as described in the documentation. This is a very convenient way, since I do not need to show/hide the dialog manually.
<button type="button" data-toggle="modal" data-target="#myModal">Launch modal</button>
Now I would like to call a method when the modal dialog is closed. With an explicit close button, this is no problem. However, when the user clicks outside of the dialog or presses the Esc key, I cannot trigger any function explicitly.
I know that I can use jQuery or Angular's $uibModal to listen for a dismiss event, but this makes the entire project more complex. I'd rather have it all in one place. I do not want to mix things up, so using jQuery within my AngularJS project is not an option. The solution I'm stuck with right now, is using $uibModal to open() the dialog manually and catching the result to handle user-invoked dismiss.
My question:
How can I call a function when a modal dialog is closed without introducing too much clutter?
What I have in mind looks like this (imaginary data-dismiss-callback):
<button type="button" data-toggle="modal"
data-target="#myModal"
data-dismiss-callback="handleCloseEvent()">Launch modal</button>
As we want to attach a specified behavior (custom callback) to the target modal using the button that opens it, then directive is the best candidate who can help us with achieving this.
We will be listening to show.bs.modal and hide.bs.modal/hidden.bs.modal events: the first one will help us to determine if the modal was opened using the corresponding button and the second one is the place where we want to call the passed callback function.
Here is a working example of modalDismissCallback directive (due to normalization, we can't name it dataDismissCallback):
angular.module('myDemoApp', [])
.controller('myCtrl', [function () {
var ctrl = this;
ctrl.testVar = 2;
ctrl.onModalDismiss = onModalDismiss;
function onModalDismiss(a, e) {
console.log(arguments);
}
return ctrl;
}])
.directive('modalDismissCallback', [function modalDismissCallback() {
return {
restrict: 'A',
scope: {
modalDismissCallback: '&'
},
link: function (scope, element) {
var modal = angular.element(element.data('target'));
modal.on('show.bs.modal', onShow);
modal.on('hide.bs.modal', onHide);
scope.$on('$destroy', function () {
modal.off('show.bs.modal', onShow);
modal.off('hide.bs.modal', onHide);
});
var shouldCall = false;
function onShow(e) {
shouldCall = e.relatedTarget === element[0];
}
function onHide(e) {
if (angular.isFunction(scope.modalDismissCallback) && shouldCall) {
scope.$event = e;
scope.$applyAsync(function () {
scope.modalDismissCallback.apply(this, arguments);
});
}
}
}
}
}]);
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.3/css/bootstrap.min.css">
<body ng-app="myDemoApp">
<div ng-controller="myCtrl as $ctrl">
<button type="button" class="btn btn-default"
data-toggle="modal"
data-target="#myModal"
modal-dismiss-callback="$ctrl.onModalDismiss($ctrl.testVar, $event)">Launch modal
</button>
<button type="button" class="btn btn-default"
data-toggle="modal"
data-target="#myModal">Launch modal wo callback
</button>
<div id="myModal" class="modal fade bd-example-modal-sm" tabindex="-1" role="dialog"
aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body">
<div ng-include="'template.html'"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/ng-template" id="template.html"><h5>Hello from ng-template!</h5></script>
</body>
<script type="text/javascript" src="//code.jquery.com/jquery-3.1.1.slim.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.3/js/bootstrap.min.js"></script>

Angularjs getting the button value

I need to get the button id value using angularjs.I have written the code but i am getting the value as "undefined".So please suggest me the code which i need to use.
My code is:
<button type="button" class="btn btn-primary ole" data-toggle="tooltip" id="buttonvalue" name="rdoResult" value="SUN" ng-click="addvalue()" ng-model="testDate23" data-placement="left" data-original-title="this is a left tooltip">
SUN
</button>
<button type="button" ng-value="2" class="btn btn-primary ole two" data-toggle="tooltip" ng-click="addvalue()"
ng-model="testDate23" value="MON" id="buttonvalue" data-placement="top" data-original-title="this is a top tooltip">
MON
</button>
Script code:
$scope.addvalue = function() {
var datevaluee=$scope.testDate23;
}
You need to pass the $event in ng-click function like below..
ng-click="addvalue($event)"
below will be the function implementation
$scope.addvalue = function(element) {
$scope.testDate23 = element.currentTarget.value; // this will return the value of the button
console.log( $scope.testDate23)
};
you can try this.
<button ng-click="addvalue(testDate23='SUN')">
button
</button>
or
<button ng-click="addvalue(testDate23='MON')">
btn2
</button>
Find fiddle Fiddle example
Hope it will help.
Button is an input for submitting data, not setting data.
You need use directive.
Live example on jsfiddle.
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope) {
$scope.getValue = function() {
$scope.value = $scope.testDate23;
}
});
myApp.directive('buttonValue', function() {
return {
restrict: 'A',
scope: {
buttonValue:"="
},
link: function(scope, element, attr) {
scope.$watch(function(){return attr.ngValue},function(newVal){
scope.buttonValue = newVal;
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<input ng-model="btnValue">
<button ng-value="{{btnValue}}" ng-click="getValue()" button-value="testDate23">
MON
</button>
<br>
<pre>testDate23= {{testDate23}}</pre>
<pre>value= {{value}}</pre>
</body>
If you want to set hardcoded value then use ng-init
<button type="button" ng-init="value=2" class="btn btn-primary ole two" data-toggle="tooltip" ng-click="addvalue()"
ng-model="testDate23" value="MON" id="buttonvalue" data-placement="top" data-original-title="this is a top tooltip">
MON
</button>
You can use value aywhere in your same template.

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);
}

Why does this only work the first time the dialog is displayed?

Using ui-bootstrap with this attribute attached to the ok/save button on the dialog.
The first time my dialog is created, it focuses on the button just as expected.
Every subsequent time it has no effect.
.directive('autoFocus', function($timeout) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$timeout(function(){
_element[0].focus();
}, 0);
}
};
});
The modal template looks like this (This comes from Michael Conroy's angular-dialog-service):
<div class="modal" ng-enter="" id="errorModal" role="dialog" aria-Labelledby="errorModalLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header dialog-header-error">
<button type="button" class="close" ng-click="close()">×
</button>
<h4 class="modal-title text-danger"><span class="glyphicon glyphicon-warning-sign"></span> Error</h4>
</div>
<div class="modal-body text-danger" ng-bind-html="msg"></div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" autoFocus ng-click="close()">Close</button>
</div>
</div>
</div>
</div>
The first time the focus moves to the close button no problem. After that the focus stays where it was.
I'm trying to deal with an enter key keypress that is launching this error dialog repeatedly, and I really need the focus to move away from the from underneath the dialog.
Turns out that autofocus is a really bad choice for a directive. I renamed it takefocus and now it works every time without any change. Why does autofocus not work? Beats me. There are override directives for and other tags that are in angular and work, but overriding autofocus with a directive does not.
It happens because the directive autoFocus is compiled once when the element is added to stage and the link function isn't called again, if you have a variable on parent scope responsible for displaying modal like $scope.opened you can use $watcher on said variable, i.e. if ith change from false to true set focus
.directive('autoFocus', function($timeout, $watch) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$watch('_scope.opened', function (newValue) {
if(newValue){
$timeout(function(){
_element[0].focus();
}, 0);
}
}
}
};
});

Invoking modal window in AngularJS Bootstrap UI using JavaScript

Using the example mentioned here, how can I invoke the modal window using JavaScript instead of clicking a button?
I am new to AngularJS and tried searching the documentation here and here without luck.
Thanks
OK, so first of all the http://angular-ui.github.io/bootstrap/ has a <modal> directive and the $dialog service and both of those can be used to open modal windows.
The difference is that with the <modal> directive content of a modal is embedded in a hosting template (one that triggers modal window opening). The $dialog service is far more flexible and allow you to load modal's content from a separate file as well as trigger modal windows from any place in AngularJS code (this being a controller, a service or another directive).
Not sure what you mean exactly by "using JavaScript code" but assuming that you mean any place in AngularJS code the $dialog service is probably a way to go.
It is very easy to use and in its simplest form you could just write:
$dialog.dialog({}).open('modalContent.html');
To illustrate that it can be really triggered by any JavaScript code here is a version that triggers modal with a timer, 3 seconds after a controller was instantiated:
function DialogDemoCtrl($scope, $timeout, $dialog){
$timeout(function(){
$dialog.dialog({}).open('modalContent.html');
}, 3000);
}
This can be seen in action in this plunk: http://plnkr.co/edit/u9HHaRlHnko492WDtmRU?p=preview
Finally, here is the full reference documentation to the $dialog service described here:
https://github.com/angular-ui/bootstrap/blob/master/src/dialog/README.md
To make angular ui $modal work with bootstrap 3 you need to overwrite the styles
.modal {
display: block;
}
.modal-body:before,
.modal-body:after {
display: table;
content: " ";
}
.modal-header:before,
.modal-header:after {
display: table;
content: " ";
}
(The last ones are necessary if you use custom directives) and encapsulate the html with
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
Open modal windows with passing data to dialog
In case if someone interests to pass data to dialog:
app.controller('ModalCtrl', function($scope, $modal) {
$scope.name = 'theNameHasBeenPassed';
$scope.showModal = function() {
$scope.opts = {
backdrop: true,
backdropClick: true,
dialogFade: false,
keyboard: true,
templateUrl : 'modalContent.html',
controller : ModalInstanceCtrl,
resolve: {} // empty storage
};
$scope.opts.resolve.item = function() {
return angular.copy(
{name: $scope.name}
); // pass name to resolve storage
}
var modalInstance = $modal.open($scope.opts);
modalInstance.result.then(function(){
//on ok button press
},function(){
//on cancel button press
console.log("Modal Closed");
});
};
})
var ModalInstanceCtrl = function($scope, $modalInstance, $modal, item) {
$scope.item = item;
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
Demo Plunker
The AngularJS Bootstrap website hasn't been updated with the latest documentation. About 3 months ago pkozlowski-opensource authored a change to separate out $modal from $dialog commit is below:
https://github.com/angular-ui/bootstrap/commit/d7a48523e437b0a94615350a59be1588dbdd86bd
In that commit he added new documentation for $modal, which can be found below:
https://github.com/angular-ui/bootstrap/blob/d7a48523e437b0a94615350a59be1588dbdd86bd/src/modal/docs/readme.md.
Hope this helps!
Quick and Dirty Way!
It's not a good way, but for me it seems the most simplest.
Add an anchor tag which contains the modal data-target and data-toggle, have an id associated with it. (Can be added mostly anywhere in the html view)
Now,
Inside the angular controller, from where you want to trigger the modal just use
angular.element('#myModalShower').trigger('click');
This will mimic a click to the button based on the angular code and the modal will appear.
Different version similar to the one offered by Maxim Shoustin
I liked the answer but the part that bothered me was the use of <script id="..."> as a container for the modal's template.
I wanted to place the modal's template in a hidden <div> and bind the inner html with a scope variable called modal_html_template
mainly because i think it more correct (and more comfortable to process in WebStorm/PyCharm) to place the template's html inside a <div> instead of <script id="...">
this variable will be used when calling $modal({... 'template': $scope.modal_html_template, ...})
in order to bind the inner html, i created inner-html-bind which is a simple directive
check out the example plunker
<div ng-controller="ModalDemoCtrl">
<div inner-html-bind inner-html="modal_html_template" class="hidden">
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</div>
<button class="btn" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
inner-html-bind directive:
app.directive('innerHtmlBind', function() {
return {
restrict: 'A',
scope: {
inner_html: '=innerHtml'
},
link: function(scope, element, attrs) {
scope.inner_html = element.html();
}
}
});

Resources