If you are working with Angular, probably you will have case where you want to call some JQUERY library to do something for you. The usual way would be to call JQUERY function on page ready:
$(function(){
$(element).someJqueryFunction();
});
If your element content is dynamically added using Angular, than this approach is not good as I understood, and you need to create an custom directive in order to fulfill this functionality.
In my case, I want to load bxSlider on <ul class="bxslider"> element, but content of the <ul> element should be loaded using angular ng-repeat directive.
Picture names are collected using REST service from the database.
I call my directive called startslider using the following code:
<div startslider></div>
My custom Angular directive is: (pictures is variable in the $scope passed from my controller, which called REST service)
application.directive('startslider', function () {
return {
restrict: 'A',
replace: false,
template: '<ul class="bxslider">' +
'<li ng-repeat="picture in pictures">' +
'<img ng-src="{{siteURL}}/slide/{{picture.gallery_source}}" alt="" />'
'</li>' +
'</ul>',
link: function (scope, elm, attrs) {//from angular documentation, the link: function should be called in order to change/update the dom elements
elm.ready(function () {
elm.bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
}
};
});
As result, I get all the pictures from the database displayed on my screen, but without bxSlider loaded (all pictures displayed one bellow other).
Please note that bxSlider is working, because when I call $(element).bxSlider(); on manually written code, the slider loads.
Why is this happening?
EDIT:
I think your problem is caused by trying to call the slider on HTMLUListElement, which is an object.
To call the slider on the DOM element, you could use $("." + $(elm[0]).attr('class')) that will use the existing bxslider class, or better assign an id to your <div startslider id="slideshow"></div> and call like $("." + $(elm[0]).attr('id'))
elm.ready(function() {
$("." + $(elm[0]).attr('class')).bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
Full code:
http://jsfiddle.net/z27fJ/
Not my solution, but I'd thought I would pass it along.
I like this solution the best (Utilizes directive controllers)
// slightly modified from jsfiddle
// bxSlider directive
controller: function() {},
link: function (scope, element, attrs, controller) {
controller.initialize = function() {
element.bxSlider(BX_SLIDER_OPTIONS);
};
}
// bxSliderItem directive
require: '^bxSlider',
link: function(scope, element, attrs, controller) {
if (scope.$last) {
controller.initialize();
}
}
http://jsfiddle.net/CaioToOn/Q5AcH/8/
Alternate, similar solution, using events (I do not like)
Jquery bxslider not working + Angular js ng-repeat issue
Root issue
You cannot call scope.$apply in the middle of a digest cycle.
You cannot fire bxSlider() until the template gets compiled.
Ultimately, you need to wait for the template to be compiled and available before calling bxSlider()
Calling a function when ng-repeat has finished
this is the directive
.directive('slideit', function () {
return function (scope, elm, attrs) {
var t = scope.$sliderData;
scope.$watch(attrs.slideit, function (t) {
var html = '';
for (var i = 0; i <= t.length-1; i++) {
html += '<li><ul class="BXslider"><li class="BXsliderHead"><img src="'+scope.$imageUrl+'flight/'+t[i].code+'.gif" />'+t[i].name+'</li><li class="BXsliderChild">'+t[i].noneStop+'</li><li class="BXsliderChild">'+t[i].oneStop+'</li><li class="BXsliderChild">'+t[i].twoStop+'</li></ul></li>';
}
angular.element(document).ready(function () {
$("#" + $(elm[0]).attr('id')).html(html).bxSlider({
pager:false,
minSlides: 3,
maxSlides: 7,
slideWidth: 110,
infiniteLoop: false,
hideControlOnEnd: true,
auto: false,
});
});
});
};
});
this is the html in view
<div ui-if="$sliderData">
<ul id="experimental" slideit="$sliderData"></ul>
</div>
and the important part is the dependency of js file
<script src="/yii-application/frontend/web/js/libraries/angular/angular.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/angular/angular-ui.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery/jquery-1.11.3.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/flex/jquery.bxslider.min.js"></script>
<script src="/yii-application/frontend/web/assets/e3dc96ac/js/bootstrap.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery-ui/jquery-ui.min.js"></script>
<script src="/yii-application/frontend/web/js/searchResult.js"></script>
<script src="/yii-application/frontend/web/js/Flight.js"></script>
and don't forget to put ui in module
var app = angular.module('myApp', ['ui']);
this is the bxslider that i use !!!! maybe solve your problem
Related
I'm in need of building a transformation directive that transforms custom directives into html.
Input like: <link text="hello world"></link>
should output to: <a class="someclass" ng-click="linkClicked('hello world')"></a>
linkClicked should be called on the parent controller of the directive.
It would have been very easy if I was the one responsible for the html holding the 'link' directive (using isolated scope), but I'm not. It's an as-is input and I have to figure a way to still do it.
There are countless examples on how to do similar bindings using the default scope of the directive, but I'm writing my controllers using John Papa's recommendations with controllerAs, but don't want to create another instance on the controller in the directive.
This is what I have reached so far:
(function () {
'use strict';
angular
.module('app')
.directive('link', link);
link.$inject = ['$compile'];
function link($compile) {
return {
restrict: 'E',
replace: true,
template: '<a class="someclass"></a>',
terminal: true,
priority: 1000,
link: function (scope, element, attributes) {
element.removeAttr('link'); // Remove the attribute to avoid indefinite loop.
element.attr('ng-click', 'linkClicked(\'' + attributes.text + '\')');
$compile(element)(scope);
},
};
}
})();
$scope.linkClicked = function(text){...} in the parent controller works.
element.attr('ng-click', 'abc.linkClicked(..)') in the directive (where the parent's controllerAs is abc) - also works.
The problem is I don't know which controller will use my directive and can't hard-code the 'abc' name in it.
What do you suggest I should be doing?
It's difficult to understand from your question all the constraints that you are facing, but if the only HTML you get is:
<link text="some text">
and you need to generate a call to some function, then the function must either be:
assumed by the directive, or
conveyed to the directive
#1 is problematic because the user of the directive now needs to understand its internals. Still, it's possible if you assume that a function name is linkClicked (or whatever you want to call it), and the user of your directive would have to take special care to alias the function he really needs (could be done with "controllerAs" as well):
<div ng-controller="FooCtrl as foo" ng-init="linkClicked = foo.actualFunctionOfFoo">
...
<link text="some text">
...
</div>
app.directive("link", function($compile){
return {
transclude: "element", // remove the entire element
link: function(scope, element, attrs, ctrl){
var template = '<a class="someclass" ng-click="linkClicked(\'' +
attrs.text +
'\')">link</a>';
$compile(template)(scope, function(clone){
element.after(clone);
});
}
};
});
Demo
#2 is typically achieved via attributes, which isn't possible in your case. But you could also create a sort of "proxy" directive - let's call it onLinkClick - that could execute whatever expression you need:
<div ng-controller="FooCtrl as foo"
on-link-click="foo.actualFunctionOfFoo($data)">
...
<link text="some text">
...
</div>
The link directive now needs to require: "onLinkClick":
app.directive("link", function($compile){
return {
transclude: "element", // remove the entire element
scope: true,
require: "?^onLinkClick",
link: function(scope, element, attrs, ctrl){
if (!ctrl) return;
var template = '<a class="someclass" ng-click="localClick()">link</a>';
scope.localClick = function(){
ctrl.externalFn(attrs.text);
};
$compile(template)(scope, function(clone){
element.after(clone);
});
}
};
});
app.directive("onLinkClick", function($parse){
return {
restrict: "A",
controller: function($scope, $attrs){
var ctrl = this;
var expr = $parse($attrs.onLinkClick);
ctrl.externalFn = function(data){
expr($scope, {$data: data});
};
},
};
});
Demo
Notice that having a link directive would also execute on <link> inside <head>. So, make attempts to detect it and skip everything. For the demo purposes, I used a directive called blink to avoid this issue.
I am trying to create some Bootstrap modals dynamically using AngularJS, as described in here: https://angular-ui.github.io/bootstrap/
For that purpose, I have created a basic script template in AngularJS inside a view of a directive called modalView.html:
<script type="text/ng-template" **id="{{modalId}}.html"**>
<div class="modal-header">
Header
</div>
<div class="modal-body">
Body
</div>
<div class="modal-footer">
Footer
</div>
</script>
And this is my directive (modalDirective.js):
myDirectives.directive('modal', function () {
return {
restrict: 'A',
scope: {},
templateUrl: 'shared/controls/modal/modalView.html',
link: function (scope, element, attributes) {
scope.modalId = attributes['modalId'];
}
}
});
Then, I have another directive that uses the aforementioned directive alongside a textbox that should open the modal when the latter is clicked.
modalTextboxView.html:
<div modal modal-id="{{modalId}}"></div>
<div textbox is-read-only="true" ng-click="openModal()"></div>
modalTextboxDirective.js:
myDirectives.directive('modalTextbox', function ($modal, $log) {
return {
restrict: 'A',
scope: {},
templateUrl: 'shared/controls/modalTextbox/modalTextboxView.html',
link: function (scope, element, attributes) {
scope.modalId = attributes['fieldId'];
scope.openModal = function(modalSize) {
var modalInstance = $modal.open({
**templateUrl: scope.modalId + '.html',**
size: modalSize,
resolve: {
items: function () {
return scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
}
}
}
});
The html output is as expected. I can see that the script template id of the first view is being set correctly. However, when I click on the textbox I get an error 404. It seems that the modal is trying to be opened but can't find the templateUrl defined, which has been set correctly too. Moreover, if I just type the hardcoded value into the id field of the script template it works flawlessly and it opens the modal. Unfortunately, I need that id to be dynamic as I need to be able to create and generate an undetermined number of modals on my page.
Any ideas?
You can't use the <script type="text/ng-template"> approach to define a dynamically bound template id.
All that Angular is doing (see src) when it encounters this type of <script> tag is that it adds the contents into a $templateCache service with the uninterpolated value of the id attribute; and, in fact, it adds it at compile-time before it could be interpolated.
So, the template is added with a key of "{{modalId}}.html" - literally. And when you request it with templateUrl: "ID123.html", it results in a cache miss and an attempt to download from that non-existent URL.
So, what are you actually trying to get out of this "dynamic" modal URL?
I don't understand the use of your modal directive - all it attempts to do is to dynamically assign an id for the template. Why? If you just need to define N templates and dynamically switch between them, then just define N <script> tags inside your modalTextbox directive:
<script type="text/ng-template" id="modal-type-1">
template 1
</script>
<script type="text/ng-template" id="modal-type-2">
template 2
</script>
<div textbox is-read-only="true" ng-click="openModal()"></div>
and in the invocation of $modal, set templateUrl like so:
$modal.open({
templateUrl: "modal-type-" + scope.modalType,
// ...
});
I need a small help in angularjs,
please have a look on this
code (chrome browser):
http://jsfiddle.net/Aravind00kumar/CrJn3/
<div ng-controller="mainCtrl">
<ul id="names">
<li ng-repeat="item in Items track by $index">{{item.name}} </li>
</ul>
<ak-test items="Items">
</ak-test>
</br>
<div id="result">
</div>
</div>
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
// var e =$compile('<li ng-repeat="item in Items track by $index">{{item.name}} </li>')(scope);
// $("#names").append(e);
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});
I have created a custom directive and trying to access an element from the view which in the ng-repeat above my custom directive
The problem is, in the directive it was saying ng-repeat not rendered yet.
Here is the problem
I have two elements
<svg>
<g>
List of elements
</g>
<g>
Based on the above rendered elements I have to draw a line between elements like a connection. I have to wait till the above elements to get render then only I can read the x,y positions and can draw a line.
</g>
</svg>
Both elements and the connections are scope variables. As per my understanding both are in the same scope and execution flow starts from parent to child and finishes from child to parent. How can I force above ng-repeat rendering part to complete before starting the custom directive?
is there any alternative available in angular to solve this dependency?
It's been a while, so my Angular is getting a bit rusty. But if I understand your problem correctly, it's one that I have run into a few times. It seems that you want to delay processing some elements of your markup until others have fully rendered. You have a few options for doing this:
You can use timeouts to wait for the page to render:
$timeout(function() {
// do some work here after page loads
}, 0);
This generally works ok, but can cause your page to flash unpleasantly.
You can have some of your code render in a later digest cycle using $evalAsync:
There is a good post on that topic here: AngularJS : $evalAsync vs $timeout. Typically, I prefer this option as it does not suffer from the same page flashing issue.
Alternatively, you can look for ways to refactor your directives so that the dependent parts are not so isolated. Whether that option would help depends a lot on the larger context of your application and how reusable you want these parts to be.
Hope that helps!
I would create a directive for the whole list, and maybe a nested directive for each list item. That would give you more control I would think.
Thanks a lot for your quick response #Royce and #Lori
I found this problem causing because of ng-repeat I have solved it in the following way..
Created a custom directive for list elements and rendered all elements in a for loop before the other directive start. This fix solved the problem temporarily but i'll try the $evalAsync and $timeout too :)
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akList",["$compile",function($compile){
return {
restrict: 'A',
replace : false,
link: function (scope, element, attrs) {
var _renderListItems = function(){
$(element).empty();
for(var i=0;i<scope.Items.length; i++)
{
var li ='<li> '+ scope.Items[i].name +' </li>';
element.append(li);
}
};
_renderListItems(scope);
scope.$watch('Items.length', function (o, n) {
_renderListItems(scope);
}, true);
}};}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});
I have a small amount of js in the app.js file that I needed in order to manipulate the DOM in this Angular Grid:
http://plnkr.co/PXRgUA
You can see it in app.js.
$('.userRow ').live('click', function(e) {
$(this).find('span.userDetailRow.blueRow').show().animate({height:200},500);
});
$('.closeDetails').live('click', function(e) {
$(this).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
e.stopPropagation();
});
How can I move this to a directive?
Does it have to be moved to a directive?
It does not seem right here.
Yes, you can (and should) move it to a directive. For the sake of clarity I'll include your old code here:
$('.userRow ').live('click', function(e) {
$(this).find('span.userDetailRow.blueRow').show().animate({height:200},500);
});
$('.closeDetails').live('click', function(e) {
$(this).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
e.stopPropagation();
});
This (binding event listeners with jquery) is what people are chomping at the bit to describe as 'not the angular way.' Instead, you can use ng-click (which is just an inbuilt directive) to call javascript functions:
<tr row ng-click="expandRow()" ng-repeat="user in users" class="item-in-list el userRow" animate="fadeIn">
<span class="userDetailRow blueRow" style="display:none;"><span close ng-click="closeDetails(); $event.stopPropagation()">x</span>
You can see here there are two custom attributes defined on these elements. These link to the directives below. These directives have custom functions defined in their link function which you can then call with ng-click (though note that this is putting these functions on the global scope).
.directive('close', function() {
return {
restrict: 'A',
replace: false,
link: function($scope, element, attrs) {
$scope.closeDetails = function() {
$(element).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
}
}
}
})
.directive('row', function() {
return {
restrict: 'A',
replace: false,
link: function($scope, element, attrs) {
$scope.expandRow = function() {
$(element).find('span.userDetailRow.blueRow').show().animate({height:200},500);
}
}
}
});
jQuery is still being used to here to locate and modify the elements for the sake of simplicity, so you can see where your old logic has gone. However you should ideally change this to use angular's inbuilt animation functionality. (more info on how animation works in the new angular version: http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
Plunker here:
http://plnkr.co/edit/UMvYnx?p=preview
I'm experiencing an issue trying to use tinymce API inside an angular directive in JSFiddle.
Here is the example
The tinymce editor is initialised just fine, there's no errors in browser console. But I get 'undefined' if I try to get an instance of the tinymce Editor.
The question is: why does tinymce.get(id); result in undefined?
HTML:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<my-editor ng-model="text"></my-editor>
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('myEditor', function () {
var uniqueId = 0;
return {
restrict: 'E',
require: 'ngModel',
scope: true,
template: '<textarea></textarea>',
link: function (scope, element, attrs, ngModel) {
var id = 'myEditor_' + uniqueId++;
element.find('textarea').attr('id', id);
tinymce.init({
selector: '#' + id
});
var editor = tinymce.get(id);
alert(editor); **// why is this undefined?**
}
}
});
I've also played with options in Frameworks & Extensions section of JSFiddle. But with no success.
You are dealing with the issue, where the elements have not been appended to the dom when you are doing your alert. (look at the html in firebug)
<my-editor ng-model="text" class="ng-scope ng-pristine ng-valid">
<textarea id="myEditor_0"></textarea>
</my-editor>
Placing the alert inside of a setTimeout will allow you to alert() the object.
setTimeout(function(){
var editor = tinymce.get(id);
alert(editor); // why is this undefined?
},0);
The proper way to go is to set the init_instance_callback option in tinyMCE.init or in tinymceOptions if you are using angular-ui-tinymce :
$scope.tinymceOptions = {
init_instance_callback : function(editor) {
MyCtrl.editor=editor
console.log("Editor: " + editor.id + " is now initialized.");
editor.on('Change', function(editor, e) {
MyCtrl.func()
});
}
Additionally to the answer Mark gave:
You will need to wait for the editor to get initalized and ready to be usable.
For this you may use the onInit tinymce handler. onInit gets fired when the editor is ready.