How can I pass ng-click to the element my directive replaces? - angularjs

Using the angular directive Max created on this post for easily importing SVGs, I've imported a handful of SVGs on my page. I now want to add a click event to an SVG, except the directive doesn't transfer the click method to the imported SVG. If I inspect the SVG in my browser I see that it is indeed missing the ng-click.
HTML
<svg-image class="svg foo" src="img/foo.svg" ng-click="bar()"></svg-image>
JS
$scope.bar = function() {
console.log("click");
};
If I move ng-click="bar()" to another element on my page it works just fine. I've also tried moving ng-click="bar()" to the svg file itself which didn't work, and I've tried doing what was suggested in this post which didn't work either.
plunker as requested: https://plnkr.co/edit/eqOZJO5Ar8oOmXCjg3Vs

One of possible solutions is to compile your new element and call resulting template function, passing in scope:
.directive('svgImage', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var imgURL = element.attr('src');
// if you want to use ng-include, then
// instead of the above line write the bellow:
// var imgURL = element.attr('ng-include');
var request = $http.get(
imgURL,
{'Content-Type': 'application/xml'}
);
scope.manipulateImgNode = function(data, elem){
var $svg = angular.element(data)[4];
var imgClass = elem.attr('class');
if(typeof(imgClass) !== 'undefined') {
var classes = imgClass.split(' ');
for(var i = 0; i < classes.length; ++i){
$svg.classList.add(classes[i]);
}
}
$svg.removeAttribute('xmlns:a');
angular.element($svg).attr("ng-click", attrs.ngClick);
return $compile($svg)(scope);
};
request.success(function(data){
element.replaceWith(scope.manipulateImgNode(data, element));
});
}
};
}]);
Plunker

Try this
var jimApp = angular.module("mainApp", []);
jimApp.controller('mainCtrl', function($scope){
$scope.bar = function() {
console.log("click");
};
});
jimApp.directive('svgImage', function() {
return {
restrict: 'E',
replace: true,
scope: {
onClick: '&'
},
template: '<div ng-click="bar();">Hai</div>',
link: function(scope, element, attrs, fn) {
scope.bar = function(){
scope.onClick()();
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="mainApp" ng-controller="mainCtrl">
asas
<svg-image on-click="bar"></svg-image>
</div>

Related

angularjs directive append templateUrl only on ng-click

I've created a directive that loads a template,
app.directive('youtubeTrailer', function() {
return {
restrict: 'E',
scope: {
show: '=info'
},
link: function(scope, element, attrs) {
scope.hideModal = function() {
scope.show = false;
};
},
templateUrl: '../assets/angular-app/templates/_container-trailer.html',
};
});
This is the ng-click action,
%a{"ng-click" => "toggleModal()"}
Trailer {{$index+1}}
That calls this function,
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
And then the directive gets shown,
%youtube-trailer{:info => "modalShown", :show => "modalShown"}
This works fine, but my problem is that the template is shown in the inline code. I would like to retrieve it only when the toggleModal() function has been clicked.
angular.module('test', [])
.directive('customDirective', ['$http', '$templateCache', '$compile', function($http, $templateCache, $compile) {
function getTemplate() {
return '<div>Hello, {{ name }}!</div>';
};
return {
restrict: 'A',
link : function(scope, element, attrs, fn) {
// instead of that you need to load and cache real template via $http
var template = getTemplate();
scope.name = 'world';
element.replaceWith($compile(template)(scope));
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test">
<div custom-directive></div>
</div>
You just need to use Angular $http service and load your file manually inside this event handler.
After that you need to compile it and insert to your directive element:
element.replaceWith($compile(template)(scope));
You also can use { cache: $templateCache } setting with $http to cache loaded template when loaded once.
Example: (inside directive)
// you need to have injected following dependencies here:
// $http, $compile, $templateCache
link: function(scope, element, attrs, fn) {
//...
scope.onSomeClick = function() {
$http.get(templateUrl, { cache: $templateCache })
.then(function(template) {
element.replaceWith($compile(template)(scope));
});
};
//...
}

Does dynamically adding ng-bind directive not work?

I'm adding the attribute ng-bind='data' to an element through a directive
myApp.directive('myDiv', function() {
return {
restrict: 'E',
link: function($scope, element, attrs) {
element.html('<div ng-bind="data">me</div>');
} }; });
function MyCtrl($scope) {
$('#click').click(function() {
$scope.data = 'change';
}); }
but the ng-bind isn't working as expected.
http://jsfiddle.net/HB7LU/3427/
To answer the main question your issue here is that if you want to include bindings in your template you need to compile the element. The syntax for that is something like:
$compile(angular.element("my html"))(scope)
In your case that actually ends up looking like:
myApp.directive('myDiv', function($compile) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// here adding the ng-bind dynamically
element.html($compile(angular.element('<div ng-bind="data">me</div>'))(scope));
}
};
});
To see it working checkout the updated fiddle here: http://jsfiddle.net/CC8BK/.
One other note is you are using jQuery's "click" event to change scope values. When working with angular you need to start by trying not to use jQuery and instead using the angular directives for whatever you can. In your case ng-click is the directive you should be using. I inserted this in your html so you could see what it would look like.
Hope this puts you on the right track. Best of luck!
As #drew_w said you have to compile element using $compile if you need to apply from link,
or else you can use template in directure like
template: '<div ng-bind="data"></div>'
I mostly prefer template
Also don't use jquery function like
$('#click').click(function() {
$scope.data = 'change';
});
instead you can use
$scope.change = function()
{
$scope.data = 'change';
}
or
ng-click="data = 'change'"
as #drew_w said
Take a look the full code
Working demo
html
<div ng-controller="MyCtrl">Hello, {{name}}!
<button id='click' ng-click="change()">click to 'change'</button>
<my-div>watch, this doesn't change!!!???</my-div>
</div>
script
var myApp = angular.module('myApp', []);
myApp.directive('myDiv', function ($compile) {
return {
restrict: 'E',
template:'<div ng-bind="data"></div>'
};
});
myApp.controller('MyCtrl', function ($scope) {
$scope.data = "me";
$scope.name = 'Superhero';
$scope.change = function () {
$scope.data = 'change';
}
});
here's a variation of the above answer using the Template property and using a click function:
myApp.directive('myDiv', function() {
return {
restrict: 'E',
template:'<div ng-bind="data"></div> me'
};
});
and on the controller:
$scope.click = function() {
$scope.data = 'change';
};
and on the View
<button ng-click="click()">click to 'change'</button>
http://jsfiddle.net/HB7LU/3446/

AngularJS : Issue passing data to a new browser window on Internet Explorer

I am trying to pass some objects to a new browser window. I followed the suggestion from AngularJS: open a new browser window, yet still retain scope and controller, and services
It works on Chrome, but doesn't on IE. My shared objects are always undefined on IE. Any suggestions?
Code for simplified version of what I am trying to do
My parent html
<html ng-app="SampleAngularApp">
<body>
<div ng-controller="popupCtrl">
<my-popup foo="foo" abc="abc">Open Popup from here</my-popup>
</div>
</body>
</html>
My parent JS
var SampleAngularApp = angular.module('SampleAngularApp', []);
var popupCtrl = function ($scope) {
$scope.foo = { baz: 'qux' };
$scope.abc = "12345";
};
SampleAngularApp.directive('myPopup', ['$window', function ($window) {
return {
restrict: 'EA',
scope: {
foo: '=',
abc: '='
},
link: function (scope, elem, attrs) {
elem.css({ 'cursor': 'pointer' });
elem.bind('click', function () {
var popWdw = $window.open("popupWindow.html", "popupWindow", "width=500,height=500,left=100,top=100,location=no");
popWdw.abc = scope.abc;
popWdw.foo = JSON.stringify(scope.foo);
});
}
};
}]);
My popup html
<html ng-app="PopupApp">
<body ng-controller="childCtrl">
</body>
</html>
My popup JS
var PopupApp = angular.module('PopupApp', []);
var childCtrl = function ($scope) {
alert(window.foo);
};
PopupApp.controller(childCtrl);
Per shaunhusain and Sunil D's suggestions, I have changed my code as below and it works
My parent JS
link: function (scope, elem, attrs) {
elem.css({ 'cursor': 'pointer' });
elem.bind('click', function () {
$window.abc = scope.abc;
$window.foo = JSON.stringify(scope.foo);
var popWdw = $window.open("popupWindow.html", "popupWindow", "width=500,height=500,left=100,top=100,location=no");
});
}
My popup JS
var childCtrl = function ($scope) {
alert(window.opener.foo);
};

AngularUI modal to be draggable and resizable

I have an angularUi modal window wrapped in a directive:
html:
<!doctype html>
<html ng-app="plunker">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.8/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.10.0.js"></script>
<script src="main.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div my-modal="{ data: 'test2'}">test2</div>
</body>
</html>
javascript:
angular.module('plunker', ['ui.bootstrap', 'myModal']);
angular.module("myModal", []).directive("myModal", function ($modal) {
"use strict";
return {
template: '<div ng-click="clickMe(rowData)" ng-transclude></div>',
replace: true,
transclude: true,
scope: {
rowData: '&myModal'
},
link: function (scope, element, attrs) {
scope.clickMe = function () {
$modal.open({
template: "<div>Created By:" + scope.rowData().data + "</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>",
controller: function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close({ test: "test"});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
});
}
}
};
});
plunker: http://plnkr.co/edit/yzxtWwZQdq94Tagdiswa?p=preview
I want to make the modal draggable and resizable. I searched through the internet and was able to find the following solution for implementing draggable:
http://plnkr.co/edit/jHS4SJ?p=preview
This is the important part:
app.directive('dragable', function(){
return {
restrict: 'A',
link : function(scope,elem,attr){
$(elem).draggable();
}
}
});
but was not able to make it work with my example. Can someone help me with this? I wonder is it possible to use jqueryui modal wrapped in a directive (instead of bootstrap) ? I am not very good at javascript and will be very greatefull for any working example with both options. Thanks
EDIT:
I added jqueryui reference and managed to make the modal draggable by adding this line:
$(".modal-dialog").draggable();
The problem is that I am not sure when to add this line. In the moment I have added this in the cancel method (just to make it work):
$scope.cancel = function () {
$(".modal-dialog").draggable();
};
So when the modal is opened I need to call cancel and only then the modal is draggable. If I call it earlier the .modal-dialog does not yer exist. Suggestions?
updated plunker:
http://plnkr.co/edit/yzxtWwZQdq94Tagdiswa?p=preview
I am missing something little, can someome provide working example ?
I've created a native directive to make the modal draggable. You only need AngularJs and jQuery. The Directive uses the "modal-dialog" class from Ui-Bootstrap modal and you can only move the modal in the header.
.directive('modalDialog', function(){
return {
restrict: 'AC',
link: function($scope, element) {
var draggableStr = "draggableModal";
var header = $(".modal-header", element);
header.on('mousedown', (mouseDownEvent) => {
var modalDialog = element;
var offset = header.offset();
modalDialog.addClass(draggableStr).parents().on('mousemove', (mouseMoveEvent) => {
$("." + draggableStr, modalDialog.parents()).offset({
top: mouseMoveEvent.pageY - (mouseDownEvent.pageY - offset.top),
left: mouseMoveEvent.pageX - (mouseDownEvent.pageX - offset.left)
});
}).on('mouseup', () => {
modalDialog.removeClass(draggableStr);
});
});
}
}
});
If you don't want to modify built-in templates you can write a directive that targets modalWindow:
.directive('modalWindow', function(){
return {
restrict: 'EA',
link: function(scope, element) {
element.draggable();
}
}
});
Please note that you will have to load both jQuery and jQuery UI before AngularJS scripts.
NOTE: Also keep in mind that newer versions of Angular UI bootstrap have been prefixed with "uib" so "modalWindow" becomes "uibModalWindow" with thanks to #valepu
I combined the two above answers and made my modal dragable.
.directive('modalWindow', function(){
return {
restrict: 'EA',
link: function(scope, element) {
$(".modal-dialog").draggable();
}
}
});
an Angular UI modal with a draggable title bar
NOTE: have to load both jQuery and jQuery UI before AngularJS scripts.
angular.module('xxApp')
.directive('uibModalWindow', function () {
return {
restrict: 'EA',
link: function (scope, element) {
$('.modal-content').draggable({handle: ".modal-header"});
}
}
});
Thank you for your examples. I little bit polished your code and this is my final result. to my solution it works perfectly :-)
HTML:
<div class="draggableModal ui-widget-content">
<div class="modal-header">
...
</div>
</div>
angular.module('posProductsManager').directive('modalDialog', function () {
var definition = {
restrict: 'AC',
link: function ($scope, element) {
var draggableStr = "draggableModal";
var header = $(".modal-header", element);
var modalDialog = element;
var clickPosition = null;
var clickOffset = null;
header[0].addEventListener('mousedown', function (position) {
clickPosition = position;
clickOffset = position;
window.addEventListener('mouseup', mouseUpEvent);
window.addEventListener('mousemove', mouseMoveEvent);
});
function mouseUpEvent() {
clickPosition = null;
window.removeEventListener('mouseup', mouseUpEvent);
window.removeEventListener('mousemove', mouseMoveEvent);
}
function mouseMoveEvent(position) {
var offset = modalDialog.parents().offset();
$("." + draggableStr, modalDialog.parents()).offset({
left: clickPosition.pageX + (position.pageX - clickPosition.pageX) - clickOffset.offsetX,
top: clickPosition.pageY + (position.pageY - clickPosition.pageY) - clickOffset.offsetY,
});
clickPosition = position;
}
}
};
return definition;
});
Try using
$(elem).closest('div.modal-dialog').draggable();
in link function

AngularJs how to call prettyprint?

I'm trying to use prettyprint plugin for my angularjs app.
But cannot make it works. I create a simple directive and call method prettyPrint(), but the code is not formatted.
FIDDLE: http://jsfiddle.net/Tropicalista/yAv4f/2/
App.directive('test', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).prettyPrint()
}
};
});
I modified your code and i'll update here:
http://jsfiddle.net/yAv4f/6/
html:
<div ng-app="Knob" ng-controller="myCtrl">
<pre class="prettyprint linemus"></pre>
<pre class="prettyprint linemus"><!DOCTYPE html><html lang="en"></html></pre>
</div>
javascript:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.dom = '<!DOCTYPE html><html lang="en"></html>'
})
App.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(scope.dom));
}
};
});
Basically, you need to use the file prettify.js to control the execution of the prettify() function, with prettyPrintOne() you can execute it in a specific html text.
And to simplify the use of the directive, like prettify stlyle, i'll suggest restric to 'C' a class and change the the directive name to 'prettyprint'
I've expanded on the previous answers and created a jsfiddle with a working directive that responds in realtime to model changes:
http://jsfiddle.net/smithkl42/cwrgLd0L/27/
HTML:
<div ng-app="prettifyTest" ng-controller="myCtrl">
<div>
<input type="text" ng-model="organization.message" />
</div>
<prettify target="organization"><pre><code class="prettyprint">console.log('{{target.message}}');
</code>
</pre>
</prettify>
</div>
JS:
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.organization = {
message: 'Hello, world!'
};
});
App.directive('prettify', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'E',
scope: {
target: '='
},
link: function (scope, element, attrs) {
var template = element.html();
var templateFn = $compile(template);
var update = function(){
$timeout(function () {
var compiled = templateFn(scope).html();
var prettified = prettyPrintOne(compiled);
element.html(prettified);
}, 0);
}
scope.$watch('target', function () {
update();
}, true);
update();
}
};
}]);
h/t to #DanielSchaffer (see Template always compiles with old scope value in directive).
Angular already has this filter built-in for JSON:
<pre>
{{data | json}}
</pre>
If you want to make your own directive, you can use the JSON object directly:
app.filter('prettyJSON', function () {
function syntaxHighlight(json) {
return JSON ? JSON.stringify(json, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
}
return syntaxHighlight;
});
With markup
<pre>
{{data | prettyJSON}}
</pre>
I would like to make a small addition to the directive by #carlosmantilla
You can achieve the same thing without creating the scope variable. I have added this correction on github
This should work properly I assume.
http://jsfiddle.net/yAv4f/143/
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.text = "function f1(){int a;}";
})
function replaceText(str)
{
var str1 = String(str);
return str1.replace(/\n/g,"<br/>");
}
app.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(replaceText(element.html()),'',true));
}
};
});
I struggled with this issue for quite a while and wanted to chime in here, albeit much later than everyone else (for real though, who's still using AngularJS in late 2017? This guy.) My specific use-case was where I have code (xml) being dynamically loaded on the page which needed to be pretty printed over and over again.
This directive will take in your code as an attribute, remove the prettyprinted class that's added to the element right after you run prettyPrint(). It will watch for changes on the inputted code from the parent's scope and run the code again when changes occur.
Only dependency is that you have Google's code-prettify. I had it self-hosted, hence the PR.prettyPrint() (as instructed in the docs as of sept 2017).
The directive fully encapsulates the needed Google code-prettify functionality for dynamic content.
angular.module('acrSelect.portal.directives')
.directive('prettyPrint', ['$timeout', function($timeout) {
return {
restrict: 'E',
scope: {
'code': '=',
},
template: '<pre ng-class="{prettyprint: code}">{{ code }}</pre>',
link: function (scope, element, attr) {
scope.$watch('code',function(){
$timeout(function() {
//DOM has finished rendering
PR.prettyPrint();
element.find(".prettyprint").removeClass("prettyprinted");
});
});
}
}
}
]);
The html in the parent template might look
<pretty-print code="selectedCode" ng-show="codeIsSelected"></pretty-print>
Hope this helps another poor soul!

Resources