AngularJS modal window directive - angularjs

I'm trying to make a directive angularJS directive for Twitter Bootstrap Modal.
var demoApp = angular.module('demoApp', []);
demoApp.controller('DialogDemoCtrl', function AutocompleteDemoCtrl($scope) {
$scope.Langs = [
{Id:"1", Name:"ActionScript"},
{Id:"2", Name:"AppleScript"},
{Id:"3", Name:"Asp"},
{Id:"4", Name:"BASIC"},
{Id:"5", Name:"C"},
{Id:"6", Name:"C++"}
];
$scope.confirm = function (id) {
console.log(id);
var item = $scope.Langs.filter(function (item) { return item.Id == id })[0];
var index = $scope.Langs.indexOf(item);
$scope.Langs.splice(index, 1);
};
});
demoApp.directive('modal', function ($compile, $timeout) {
var modalTemplate = angular.element("<div id='{{modalId}}' class='modal' style='display:none' tabindex='-1' role='dialog' aria-labelledby='myModalLabel' aria-hidden='true'><div class='modal-header'><h3 id='myModalLabel'>{{modalHeaderText}}</h3></div><div class='modal-body'><p>{{modalBodyText}}</p></div><div class='modal-footer'><a class='{{cancelButtonClass}}' data-dismiss='modal' aria-hidden='true'>{{cancelButtonText}}</a><a ng-click='handler()' class='{{confirmButtonClas}}'>{{confirmButtonText}}</a></div></div>");
var linkTemplate = "<a href='#{{modalId}}' id= role='button' data-toggle='modal' class='btn small_link_button'>{{linkTitle}}</a>"
var linker = function (scope, element, attrs) {
scope.confirmButtonText = attrs.confirmButtonText;
scope.cancelButtonText = attrs.cancelButtonText;
scope.modalHeaderText = attrs.modalHeaderText;
scope.modalBodyText = attrs.modalBodyText;
scope.confirmButtonClass = attrs.confirmButtonClass;
scope.cancelButtonClass = attrs.cancelButtonClass;
scope.modalId = attrs.modalId;
scope.linkTitle = attrs.linkTitle;
$compile(element.contents())(scope);
var newTemplate = $compile(modalTemplate)(scope);
$(newTemplate).appendTo('body');
$("#" + scope.modalId).modal({
backdrop: false,
show: false
});
}
var controller = function ($scope) {
$scope.handler = function () {
$timeout(function () {
$("#"+ $scope.modalId).modal('hide');
$scope.confirm();
});
}
}
return {
restrict: "E",
rep1ace: true,
link: linker,
controller: controller,
template: linkTemplate
scope: {
confirm: '&'
}
};
});​
Here is JsFiddle example http://jsfiddle.net/okolobaxa/unyh4/15/
But handler() function runs as many times as directives on page. Why? What is the right way?

I've found that just using twitter bootstrap modals the way the twitter bootstrap docs say to is enough to get them working.
I am using a modal to house a user edit form on my admin page. The button I use to launch it has an ng-click attribute that passes the user ID to a function of that scope, which in turn passes that off to a service. The contents of the modal is tied to its own controller that listens for changes from the service and updates values to display on the form.
So.. the ng-click attribute is actually only passing data off, the modal is still triggered with the data-toggle and href tags. As for the content of the modal itself, that's a partial. So, I have multiple buttons on the page that all trigger the single instance of the modal that's in the markup, and depending on the button clicked, the values on the form in that modal are different.
I'll take a look at my code and see if I can pull any of it out to build a plnkr demo.
EDIT:
I've thrown together a quick plunker demo illustrating essentially what I'm using in my app: http://embed.plnkr.co/iqVl0Wb57rmKymza7AlI/preview
Bonus, it's got some tests to ensure two password fields match (or highlights them as errored), and disables the submit button if the passwords don't match, or for new users username and password fields are empty. Of course, save doesn't do anything, since it's just a demo.
Enjoy.

There is a working native implementation in AngularStrap for Bootstrap3 that leverages ngAnimate from AngularJS v1.2+
Demo : http://mgcrea.github.io/angular-strap/##modals
You may also want to checkout:
Source : https://github.com/mgcrea/angular-strap/blob/master/src/modal/modal.js
Plunkr : http://plnkr.co/edit/vFslNmBAoKPVXtdmBXgv?p=preview

Well, unless you want to reinvent this, otherwise I think there is already a solution.
Check out this from AngularUI. It runs without twitter bootstrap.

I know it might be late but i started trying to figure out why the handler got called several times as an exercise and I couldn't stop until done :P
The reason was simply that each div you created for each modal had no unique id, once I fixed that everything started working. Don't ask me as to what the exact reason for this is though, probably has something to do with the $('#' + scope.modalId).modal() call.
Just though I should post my finding if someone else is trying to figure this out :)

Related

Pass variable to UI-bootstrap modal without using $scope

Since I am a beginner using AngularJS the $scope approach to pass data between different controllers and (in my case) a modal drives me crazy. Due to that reason, I googled around the web and found an interesting blogpost about passing data to a UI-bootstrap modal without using $scope.
I had a deeper look at this blogpost and the delivered plunk which works pretty nice and started to adopt this to my own needs.
What I want to achieve is to open a modal delivering an text input in which the user is able to change the description of a given product. Since this would provide more than a minimal working example I just broke everything down to a relatively small code snippet available in this plunk.
Passing data from the main controller into the modal seems to work as the default product description is displayed in the modal text input as desired. However, passing the data back from the modal to the main controller displaying the data in index.html does not seem to work, since the old description is shown there after it was edited in the modal.
To summarize my two questions are:
What am I doing wrong in oder to achieve a 'two-way-binding' from the main controller into the modal's text input and the whole way back since the same approach works in the mentioned blogpost (well, as the approach shown in the blogpost works there must be something wrong with my code, but I cannot find the mistakes)
How can I implement a proper Accept button in order to accept the changed description only if this button is clicked and discard any changes in any other case (clicking on Cancel button or closing the modal by clicking next to it)?
In your main controller, create two resolver functions: getDescription and setDescription.
In your modal controller, use them.
Your modal HTML
<div class="modal-header">
<h3 class="modal-title">Test Text Input in Modal</h3>
</div>
<div class="modal-body">
Product description:
<input type="text" ng-model="modal.description">
</div>
<div class="modal-footer">
<button ng-click="modal.acceptModal()">Accept</button>
<button ng-click="modal.$close()">Cancel</button>
</div>
Your main controller
function MainCtrl($modal) {
var self = this;
self.description = "Default product description";
self.DescriptionModal = function() {
$modal.open({
templateUrl: 'modal.html',
controller: ['$modalInstance',
'getDescription',
'setDescription',
ModalCtrl
],
controllerAs: 'modal',
resolve: {
getDescription: function() {
return function() { return self.description; };
},
setDescription: function() {
return function(value) { self.description = value; };
}
}
});
};
};
Your modal controller
function ModalCtrl($modalInstance, getDescription, setDescription) {
var self = this;
this.description = getDescription();
this.acceptModal = function() {
setDescription(self.description);
$modalInstance.close();
};
}

angularjs directive rendered by third party component is not working

I have a simple angularjs directive that I use to show a tooltip.
<div tooltip-template="<div><h1>Yeah</h1><span>Awesome</span></div>">Click to show</div>
It works fine but now I'm trying to use it inside a timeline javascript component (visjs.org)
I can add items with html to this timeline like this
item...
item.content = "<div tooltip-template='<div><h1>Yeah</h1><span>Awesome</span></div>'>Click to show</div>";
$scope.timelineData.items.add(item);
The item is well displayed on the page BUT the code of the tooltip-template directive is never reached.
I suspect that because a third party component is rendering the item, the dom element is not read by angular.
I've tried to do a $scope.$apply(), $rootScope.$apply but the result is the same. The directive is never reached.
How can I tell angular to read my dom to parse these directives ?
Here is the directive code :
.directive("tooltipTemplate", function ($compile) {
var contentContainer;
return {
restrict: "A",
link: function (scope, element, attrs) {
var template = attrs.tooltipTemplate;
scope.hidden = true;
var tooltipElement = angular.element("<div ng-hide='hidden'>");
tooltipElement.append(template);
element.parent().append(tooltipElement);
element
.on('click', function () { scope.hidden = !scope.hidden; scope.$digest(); })
$compile(tooltipElement)(scope);
}
};
});
Edit
Added plunker : http://plnkr.co/edit/lNPday452GiZJBhMH4Kl?p=preview
I tried to do the same thing and came with a solution by manually creating scope and compile'ng the html of the directive with the scope using $compile method. Below a snippet
I did the below part inside a directive that created the timeline . Using the scope of that directive ,
var shiftScope = scope.$new(true);
shiftScope.name = 'Shift Name'
var shiftTemplate = $compile('<shift-details shift-name="name"></shift-details>')(shiftScope)[0];
I passed shiftTemplate as the content and it worked fine .
But trying to do this for >50 records created performance issues .

Mapbox map in ui-bootstrap tab not loading tiles

I'm trying to put a mapbox map inside an angular-ui-bootstrap tab, and it seems that some/most of the tiles are not getting loaded upon initialization, and are not being requested as you pan around on the map. Outside of the ui-bootstrap tabset, the maps work just fine.
No errors are being thrown, but looking at the requests for the tiles, many of them are just not being requested for some reason. I'm not even sure how to debug this one.
Any ideas as to what might be going on?
Here is a plunkr showing the issue
And here is an example angular app that will show the problem
var app = angular.module('app', ['ui.bootstrap'])
app.controller('mapCtrl', ['$scope', '$timeout', function($scope, $timeout) {
$scope.val = 123
}]);
app.directive('myMap', function() {
return {
restrict: 'E',
template: "<div id='map_container'></div>",
link: function ($scope, elem, attrs) {
mapDiv = elem.find('#map_container')
L.mapbox.accessToken = 'pk.eyJ1IjoicmVwdGlsaWN1cyIsImEiOiJlSWZtN1hZIn0.FfT3RxbfRYv4LIjBxXG5fw';
var map = L.mapbox.map(mapDiv[0], 'examples.map-i86nkdio')
.setView([40, -74.50], 9);
}
};
});
The map gets initialized when the mapcontainer is not visible, that's why it fails. You're on the right path with calling invalidateSize but you need to do that when the tab becomes visible. I see you've already setup an event which you could hook into in your directive link function:
$scope.$on('tabSelect:map', function (t) {
$timeout(function () {
map.invalidateSize(true);
});
});
It doesn't work without the timeout. It needs some sort of delay so the tab is complete visible before firing invalidateSize. Here's an updated Plunker: http://plnkr.co/edit/gzwx2pZ1GjBZDE8Utxfl?p=preview

Dynamically change the action of href using angularjs

I'm a newbie to angular, and I'm playing around with it to try and understand how things work. I have an href as part of the template of a directive and an action associated with clicking the link. I would like to know how I can change the action when the user clicks on the link. I tried using a link function in my template, but I couldn't even get it to fire a message to the console.
Here is my link function:
var linkFunction = function(scope) {
scope.$watch(scope.loggedin, function() {
console.log('Here');
});
};
Any pointers? Or is there a better way.
TIA
Link function is part of directive. You can use an ng-click directive in the anchor tag in the template and provide its implementation in the linking function of the directive.
//template
Click Me
//Link function in directive
function(scope) {
scope.doThis = function() {
console.log("doing this);
}
}

Search box in angular js

I want to implement a search box in my angularJs application. As soon as user starts typing some name in the search box , some REST service should be called and it should fetch all the names which matches the name typed in the search text box. Note that there is no button , the result should come automatically as soon as user starts typing. The REST service is already there. I just need to invoke the REST service when the user starts typing and return the result as a list.
For ex:- If I type James then all the user whose name starts with James should come as a list in the search box.
Once the list of name comes , the user can click on one of the name and his information should be loaded in the current page.
How can I implement such type-on search box in angular js? Is there any directive for it? Can anyone please give me some direction.
You should define a directive that listen onkeypress.
app.directive('myOnKeyDownCall', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
});
};
});
HTML
<input type="text" my-on-key-down-call="callRestService()">
CONTROLLER
$scope.callRestService= function() {
$http({method: 'GET', url: '/someUrl'}).
success(function(data, status, headers, config) {
$scope.results.push(data); //retrieve results and add to existing results
})
}
Would be nice to wait until 3 keys has been typed, for that in directive:
var numKeysPress=0;
element.bind("keydown keypress", function (event) {
numKeysPress++;
if(numKeysPress>=3){
scope.$apply(function (){
scope.$eval(attrs.myOnKeyDownCall);
});
event.preventDefault();
}
});
Perhaps, exists typeahead directive from angular-ui that you can use:
angular-ui typeahead
I hope it helps you
Found this to be a simplification of the accepted answer.
// Directive
app.directive('search', function () {
return function ($scope, element) {
element.bind("keyup", function (event) {
var val = element.val();
if(val.length > 2) {
$scope.search(val);
}
});
};
});
// In Controller
$scope.search= function(val) {
// fetch data
}
// HTML
<input type="text" search>
Not sure if you already solved this, but I recommend looking at this tutorial: http://angular.github.io/angular-phonecat/step-12/app/#/phones
Essentially it does what you're interested in, but instead it filters out the results while typing. If you got this working, I'm interested in how you did it. I'm trying this as well.
Why so much drama, directives, and Glyptodon blood?
Since angular already has
ng-keypress
ng-keyup
ng-keydown
Use any of those to invoke REST service just as you would with ng-click.
HTML
<input type="search" ng-model="vm.query" ng-keyup="vm.search()" />
JS
vm.search = search;
function search() {
// Call your cool REST service and attach data to it
vm.data = MyCoolService.myGetFunctionWhatever();
// You can use vm.query ng-model to limit search after 2 input values for example
// if(vm.query.length > 2) do your magic
};
Bootstrap's "Typeahead, Asynchronous results" does exactly what you want, easily. Go to https://angular-ui.github.io/bootstrap/ then scroll down near to the bottom of the page. I used it in my CRUDiest movies database project: https://crudiest-firebase.firebaseapp.com/#/movies

Resources