I'm trying to implement a Angular UI Bootstrap carousel, but I'm using it for a Quiz. Therefore, I don't need normal Prev() and Next() buttons.
Rather, I need a custom Next() button that makes sure they've selected an answer before continuing on to next "slide" of question/answers.
How do I hook into the carousel directive functions to run my code and then use the carousel.next() function?
Thanks,
Scott
There is no official possibility to achieve this. but this can be hacked, if you want. But i think it is better grab the bootstrap original one, have a look the at angular bootstrap ui sources (carousel) and write your own wrapper.
Here comes the hack:
The first problem we have to solve is, how to access the CarouselController. There is no API that exposes this and the carousel directive creates an isolated scope. To get access to this scope wie need the element that represents the carousel after the directive has been instantiated by angular. To achieve this we may use a directive like this one, that must be put at the same element as our ng-controller:
app.directive('carouselControllerProvider', function($timeout){
return {
link:function(scope, elem, attr){
$timeout(function(){
var carousel = elem.find('div')[1];
var carouselCtrl = angular.element(carousel).isolateScope();
var origNext = carouselCtrl.next;
carouselCtrl.next = function(){
if(elem.scope().interceptNext()){
origNext();
}
};
});
}
};
});
We must wrap our code in a $timeout call to wait until angular has created the isolated scope (this is our first hack - if we don't want this, we had to place our directive under the carousel. but this is not possible, because the content will be replaced). The next step is to find the element for the carousel after the replacement. By using the function isolateScope we have access to the isolated Scope - e.g. to the CarouselController.
The next hack is, we must replace the original next function of the CarouselController with our implementation. But to call the original function later we have to keep this function for later use. Now we can replace the next function. In this case we call the function interceptNext of our own controller. We may access this function through the scope of the element that represents our controller. If the interceptNext returns true we call the original next function of the carousel. For sure you can expose the complete original next function to our controller - but for demonstration purposes this is sufficient. And we define our interceptNext function like this:
$scope.intercept = false;
$scope.interceptNext = function(){
console.log('intercept next');
return !$scope.intercept;
}
We can now control the next function of the carousel by a checkbox, that is bound to $scope.intercept. A PLUNKR demonstrates this.
I knew this is not exactly what you want, but how you can do this is demonstrated.
That hack is neat michael, I started working on something similar for my needs. But then realized I might as well finally dip my toe into contributing to the open source community.
I just submitted a pull request to update the library so the index of the current slide is exposed to the Carousel scope.
https://github.com/angular-ui/bootstrap/pull/2089
This change allows you to have per-slide behavior in the carousel template.
This change allowed me to override the base carousel template so that for instance on the first slide the "prev" button would not show or the "next" button would not show for the final slide.
You can add more complex logic for your own personal needs, but exposing the current index in this manner to the $scope is part of making this part of the framework more flexible.
EDIT
I made more changes for my personal use, but don't want quite yet to contribute this change which is closer to what you are needing.
I modified the carousel directive, adding the "finish" property to scope.
.directive('carousel', [function () {
return {
restrict: 'EA',
transclude: true,
replace: true,
controller: 'CarouselController',
require: 'carousel',
templateUrl: 'template/carousel/carousel.html',
scope: {
interval: '=',
noTransition: '=',
noPause: '=',
finish: '='
}
};
}])
Then, when I declare the carousel, I can pass in a method to that directive attribute which is a method in the scope of the controller containing the carousel.
<carousel interval="-1" finish="onFinish">
...
</carousel>
This allows me to modify my template to have a button that looks like this:
<button ng-hide="slides().length-1 != currentIndex" ng-click="finish()" class="btn next-btn">finish<span class="glyphicon glyphicon-stats"></span></button>
So it only shows conditionally on the correct slide and with ng-click it is calling the carousel's $scope.finish() which is a pointer to a method in the controller I created for this application.
Make sense?
edit: This only works if you don't use sort functionality with ng-repeat. There is a bug which breaks the indexing of the slides for this kind of functionality.
Related
What I think I want to do is completely isolate each step of a wizard into a custom element directive.
I think this would allow me to completely encapsulate the detail of each page of the wizard. For example:
<custom-wizard-tag>
<enter-name-page page="1" name-placeholder="name"/>
<enter-address-page page="2" name-placeholder="name" address-placeholder="address" last-page/>
</custom-wizard-tag>
So far, so good. Each of the elements above has its own directive, and each of these specifies a templateUrl and a controller (templateUrl could be supplied as an attribute, of course).
I want each page of the wizard to 'inherit' some behaviour. The UI components would contain the buttons, which would need to query the outer scope, for example to determine whether it is possible to move forward, backward and so on. We would also need to call member functions on the parent scope in order to actually move the wizard forwards and backwards, and to check whether the current page number matches 'ours'.
I'm new to this so bear with me...
I read the documentation on directive, and thought I could use scope: { onNext: '&onNext' } in order to 'inherit' the onNext function from the previous scope (which is assumed to be one which is 'wizard-like'). However, this is not what angular seems to do. It seems to want map the inner scope's onNext via an attribute called on-next, thus breaking encapsulation, because now the UI elements must reference functions in the parent scope - which is exactly what I wanted to avoid.
Am I barking up the wrong tree, or is there an idiomatic way to do this. A day of web searching has not got me far, but I could be using the wrong search terms.
Thanks for your patience.
scope: { onNext: '&onNext' }
won't do any inherintance, you would have to define onNext in the template (the template scope) the same way you do with the page property: <enter-name-page page="1"
If you have a function onNext defined in you customWizardTag directive either in link function or in its controller, you'll have to put it in the controller, because the controller can be passed to the child directive. Then you'll be able to pass the parent directive's controller in the link functions of somethingPage directives.
.directive('parentDirective, function() {
return {
controller: someControllerThatHasOnNext,
}
})
.directive('childDirective', function() {
return {
require: '^^parentDirective',
link: function(scope, element, attrs, theParentDirectivesController){
theParentDirectivesController.onNext();
}
}
})
If this is what you wanted
I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.
I know that if I want to create a reusable item, such as a date picker, then creating it as a Directive is recommended.
However, let's say that on my homepage, I have a Welcome section that displays the quote of the day with a background image that comes from a Rest service. Should this be a Directive that can encapsulate the markup and controller logic? Or should it be a simple AngularJs Controller that binds to markup in my index.html?
What constitutes whether or not something should be created as a Directive?
Directive is only a wrapper for a controller. It means if you have a controller you can use it. But you also may use the same controller as a controller of a directive for example instead of link function use controller.
This allow as to draw clear line where to use directive and where to use a controller.
We have to use Controller if we want to reproduce logic of piece of HTML markup. When we want to use the same $scope assignments, the same functions inside $scope, ... but HTML markup is always different for every other place where we use this controller.
We have to use directive when we have same logic in a controller of a directive and same HTML markup.
So in your case it is definitely a directive.
This is my own common sense of course, and may not be ideal.
There are three things you will require to implement this functionality:
AngularJS Template a.k.a. Markup to display quote with an image next to it.
AngularJS service to encompass the REST call in order to fetch above details from the server.
AngularJS controller to consume the AngularJS service to feed the data back the template (point 1) to update it accordingly after every rest call.
So the fact is you can achieve this without even writing an AngularJS Directive but what if you need to replicate the same feature in many places. In that sense, you will probably have to copy the same template somewhere else which will again need a separate controller to consume the same service (as using the same controller multiple times in the DOM is not recommended and a bad practice).
With the Directive API, you can put the markup in a directive template and consume the service in a directive controller to render the UI. So the next time if you want multiple instance of the widget, you just have to inject the directive, that's it - rest will work without any issue.
App = angular.module('App', []);
App.directive('welcomeQuote', function(QuoteService) {
return {
restrict: 'E',
template: '<div><img ng-src="{{quote.img}}" /><span ng-bind="quote.title"></span></div>',
controller: function(scope) {
// returns {img: 'angular.png', title: 'AngularJS';
QuoteService.fetch().then(function(data) {
scope.quote = data;
});
}
}
});
App.factory('QuoteService', function($http) {
return function() {
fetch: function() {
return $http.get('http://quote-server.com/new')
}
};
});
Finally you can use the widget as:
<welecome-quote></welcome-quote>
I am trying to create a simple System Notification service in Angular that basically toggles a notification to become visible at the top of my window in a fixed position until the user interacts with it. The problem I am having is trying to position the element after Angular updates the DOM based off of my $scope
app.directive('skSystemUpdate', function(SystemUpdate){
return {
replace:true,
restrict: 'A',
template:'<div class="sk-system-update-wrap">\
<div class="sk-system-update" ng-show="showSystemUpdate">\
<sk-img class="iGreenSystemCheck"></sk-img>\
<span class="sk-system-update-text">{{ message }}</span>\
<a>Undo</a>\
</div>\
</div>',
link: function(scope, elem, attrs){
scope.$watch('showSystemUpdate', function(val){
if(val){
angular.extend(scope, SystemUpdate.getScope());
}
});
}
}
});
I have a service called SystemUpdate which is used to set up a scope variable based off of parameters (i.e. SystemUpdate.create('This is some text for the notification') ) and then flip the $rootScope.showSystemUpdate flag to true.
My directive is $watching for this change and when the flag is set to true, I get the scope variable from the SystemUpdate service and Angular takes care of the rest by applying the updated scope.message variable to the DOM.
My problem is I cannot figure out how to center my notification AFTER the DOM is manipulated.
Update
I still cannot find a solution besides using $timeout which isn't a great solution at all because you can clearly see the div "jump" between positions if the message changes... This has got to be something people have done before! This is something anyone can do with jQuery in a matter of seconds, but Angular is making this a real pain
I still would love to know how someone would go about manipulating the DOM after an ng-show/ng-hide, but in the meantime I was able to produce the effect I am looking for by simply wrapping my element in another div that is position:fixed and then just using text-align:center to center the smaller div within that. This is essentially how Angular UI Bootstrap centers their modals, but I would still like to know a true solution to the original problem if any Angular gurus would oblige.
Update: Fiddle w/ full solution: http://jsfiddle.net/langdonx/VXBHG/
In efforts to compare and contrast KnockoutJS and AngularJS, I ran through the KnockoutJS interactive tutorial, and after each section, I'd rewrite it in AngularJS using what little I already knew + the AngularJS reference.
When I got to step 3 of the Creating custom bindings tutorial, I figured it would be a good time to get spun up on Angular Directives and write a custom tag. Then I failed miserably.
I'm up against two issues that I haven't been able to figure out. I created a new Fiddle to try and wrap my head around what was going on...
1 (fiddle): I figured out my scoping issue, but, is it possible to just passthrough ng-click? The only way I could get it to work is to rename it to jqb-click which is a little annoying.
2 (fiddle): As soon as I applied .button() to my element, things went weird. My guess is because both Angular and jQuery UI are manipulating the HTML. I wouldn't expect this, but Angular seems to be providing its own span for my button (see line 21 of the JavaScript), and of course so is jQuery UI, which I would expect. I hacked up the HTML to get it looking right, but even before that, none of the functionality works. I still have the scope issue, and there's no template binding. What am I missing?
I understand that there's an AngularUI project I should be taking a look at and I can probably pull off what I'm trying to do with just CSS, but at this point it's more about learning how to use Directives rather than thinking this is a good idea.
You can create an isolated scope in a directive by setting the scope parameter, or let it use the parent scope by not setting it.
Since you want the ng-click from parent scope it is likely easiest for this instance to use the parent scope within directive:
One trick is to use $timeout within a directive before maniplulatig the DOM within a templated directive to give the DOM time to repaint before the manipulation, otherwise it seems that the elements don't exist in time.
I used an attribute to pass the text in, rather than worrying about transclusion compiling. In this manner the expression will already have been compiled when the template is added and the link callback provides easy access to the attributes.
<jqbutton ng-click="test(3)" text="{{title}} 3"></jqbutton>
angular.module('Components', [])
.directive('jqbutton', function ($timeout) {
return {
restrict: 'E', // says that this directive is only for html elements
replace: true,
template: '<button></button>',
link: function (scope, element, attrs) {
// turn the button into a jQuery button
$timeout(function () {
/* set text from attribute of custom tag*/
element.text(attrs.text).button();
}, 10);/* very slight delay, even using "0" works*/
}
};
});
Demo: http://jsfiddle.net/gWjXc/8/
Directives are very powerful, but also have a bit of a learning curve. Also in comparison of angular to knockout, angular is more of a meta framework that in the long run has far more flexibilty than knockout
Very helpful reading for understanding scope in directives:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance