How to trigger mailto in AngularJS controller without changing window location - angularjs

I have an angular controller that triggers a "mailto" link when a mail icon is clicked
angular
.module('app')
.component('header', {
template: '<i class="fa fa-envelope" aria-hidden="true"></i>',
controller: HeaderController,
controllerAs: 'headerCtrl'
});
function HeaderController() {
var vm = this;
vm.sendMail = sendMail;
function sendMail(response) {
var email = 'test#mail.com';
var subject = 'email%20subject';
var body = 'email%20body';
vm.mailToUri = 'mailto:' + email + '?subject=' + subject + '&body=' + body;
$window.location = vm.mailToUri;
}
}
Unfortunately, this is running in the same application as a directive that calls a logout url when the browser is closed
angular
.module('app')
.directive('onCloseLogout', onCloseLogout);
function onCloseLogout($http, $window) {
return {
restrict: 'AE',
link: function () {
$window.onbeforeunload = function () {
$http.get('/logout');
};
}
};
}
So, when the email link is clicked, the onCloseLogout directive is triggered, and the user is logged out.
Is there another way to open an email in angular, or to send a get request to the logout URL on browser close?

Turns out I had over engineered the solution. I added the mailToUri to the anchor tag as the ng-href attribute and it all works fine now
angular
.module('app')
.component('header', {
template: '<a ng-href="{{headerCtrl.mailToUri}}"><i class="fa fa-envelope" aria-hidden="true"></i></a>',
controller: HeaderController,
controllerAs: 'headerCtrl'
});
function HeaderController() {
var vm = this;
var email = 'test#mail.com';
var subject = 'email%20subject';
var body = 'email%20body';
vm.mailToUri = 'mailto:' + email + '?subject=' + subject + '&body=' + body;
}

Related

XSS issue in the angularjs application

This is regarding injecting the script tag (given below) in one of the input and it's description field. Values are saving fine and showing in the grid.
Go to this <script>window.alert('abc')</script> and fill out the FAFSA.
Form fields:-
You can see in the below screenshot that the alert script is running from the description field.
Modal implementation (execute on click of the document type link in the listing):-
$rootScope.showNormalModal = function (message) {
var modalHtml = "<div class='modal-header'><button type='button' ng-click='cancel()' class='close' data-dismiss='modal' aria-label='Close'>" +
"<span aria-hidden='true'>×</span></button> " +
"<h4 class='modal-title' id='myModalLabel'><div>" +
"</div>" + message + "</h4></div>";
var modalInstance = $modal.open({
template: modalHtml,
controller: function ($scope, $modalInstance) {
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
});
}
});
If the input parameter has script tag with some operation, it is not showing in the UI as it is, instead the script is running before the modal.
Expected behavior here is when I clicked on the anchor in the listing, it should show its description in the popup. what is happening here is, before showing the description, I am getting a alert window because of the script tag injection.
How will I avoid the alert script getting executed before the popup.
Attached all the screenshots below.
Listing Page :-
Alert Window :-
Description Popup :-
Thanks in advance.
Can you please try below code
$rootScope.showNormalModal = function (message) {
var modalHtml = "<div class='modal-header'><button type='button' ng-click='cancel()' class='close' data-dismiss='modal' aria-label='Close'>" +
"<span aria-hidden='true'>×</span></button> " +
"<h4 class='modal-title' id='myModalLabel'><div>" +
"</div><div ng-bind=\"message\"></div></h4></div>";
var modalInstance = $modal.open({
template: modalHtml,
controller: function ($scope, $modalInstance, message) {
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.message = message;
},
resolve: {
message: function () {
return message;
}
}
});
}

AngularJS: Append to url the page number while paginating

I am working on a application where i am paginating through some records by making calls to the server like random/api/endpoint?page=1/2/3
Now i while i paginate,
i need to append the page i am requesting to the url like http://www.paginate.com/somedata/{1/2/3} and on opening this url it should also fetch that specific page in the view {meaning if i navigate to hhtp://www.paginate.com/somedata/4 then the app/view should reflect data from the api call random/api/endpoint?page=4}.
Presently i am using angular-route 1.4.12 with the same version of AngularJS. Very new to angular (2 days), any help will be greatly appreciated.
EDIT : What i want to do ?
When i click next while paginating, it should append the pageNumber to the url.
route.js
angular
.module('mainRouter', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/somedata/:page', {
templateUrl: 'partials/somedata.html',
controller: 'PaginationCtrl',
controllerAs: 'vm',
reloadOnSearch: false
}).
otherwise( { redirectTo: "/somedata/1" });
}
]);
PaginationCtrl.js
angular
.module('controllers.Pagination', [])
.controller('PaginationCtrl', PaginationCtrl);
PaginationCtrl.$inject = ['$routeParams', 'paginationService'];
function PaginationCtrl ($routeParams, paginationService) {
var vm = this;
vm.paginationData = {
perPage: 10,
currentPage: 1
};
vm.isLoading = false;
vm.paginate = paginate;
paginate(vm.paginationData.currentPage);
calculateTotalPages();
function calculateTotalPages () {
paginationService.findAll(0, 0)
.success(function (res) {
var paginationData = vm.paginationData || {};
paginationData.totalPages = Math.ceil(res.count / paginationData.perPage);
})
.error(function (res) {
console.log('Error trying to get the total number of pages', res);
});
}
function paginate (pageNumber, perPage) {
vm.isLoading = true;
var paginationData = vm.paginationData || {};
if (! perPage) {
perPage = paginationData.perPage;
}
console.log($routeParams);
paginationService.findAll(perPage, pageNumber)
.success(function (res) {
paginationData.items = res.documents;
vm.isLoading = false;
})
.error(function (res) {
console.log('Error fetching more Logs', res);
});
}
}
PaginationService.js
angular
.module('services.Pagination', [])
.service('paginationService', PaginationService);
PaginationService.$inject = ['$http', 'Constants'];
function PaginationService ($http, Constants) {
// console.log($http);
this.findAll = function (perPage, page) {
var url = Constants.baseUrl + '/sms/get/data';
if (page > 0) {
url += '?page=' + page;
}
return $http.get(url);
};
}
directive being used
var app = angular.module('directives.Pagination', []);
app.directive('pagination', [function () {
return {
restrict: 'E',
template: '<div class="ui pagination menu"> \
<a class="icon item" ng-click="vm.previous()"><i class="left arrow icon"></i></a> \
<div class="icon item">{{ vm.paginationData.currentPage }} / {{ vm.paginationData.totalPages }}</div> \
<a class="icon item" ng-click="vm.next()"><i class="right arrow icon"></i></a> \
</div>',
scope: '=',
link: function (scope, element, attrs) {
var vm = scope.vm;
vm.paginationData.currentPage = 1;
vm.next = function () {
vm.paginationData.currentPage++;
if (vm.paginationData.currentPage > vm.paginationData.totalPages) {
vm.paginationData.currentPage = vm.paginationData.totalPages;
}
vm.paginate(vm.paginationData.currentPage);
};
vm.previous = function () {
vm.paginationData.currentPage--;
if (vm.paginationData.currentPage < 1) {
vm.paginationData.currentPage = 1;
}
vm.paginate(vm.paginationData.currentPage);
};
}
};
}]);
You should be able to access your :page parameter via $routeParams, which you've already injected in your controller.
Just call paginate with $routeParams.page instead of your default of 1.
In order to update the url as you go (in such a way that allows the user to copy the url for later use), without updating the route and re-initializing the controller, you can just call $location.search({page: page}). When this is called with reloadOnSearch set to false (as you've already done) it shouldn't re-initalize the controller.
Lastly, in case its not clear, you'll have to update the URL at the same time you make your API call. There isn't a built in angular way to do this, but it should be pretty straightforward.

Create a simple Bootstrap Yes/No confirmation or just notification alert in AngularJS

It's so simple in a non-Angular environment. Just html and a two line of js code to show a modal confirmation dialog on the screen.
Now I am developting an AngularJS project in which I am using ui-bootstrap modal confirmation dialogs all over the place and I am sick of creating new controllers even for simple things like "Are you sure to delete this record?" kind of stuff.
How do you handle these simple situations? I am sure some people wrote some directives to simplify the needs.
I am asking you to share your experiences or the projects you know about that subject.
so create a reusable service for that... read here
code here:
angular.module('yourModuleName').service('modalService', ['$modal',
// NB: For Angular-bootstrap 0.14.0 or later, use $uibModal above instead of $modal
function ($modal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl: '/app/partials/modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function (customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
};
}
return $modal.open(tempModalDefaults).result;
};
}]);
html for display
<div class="modal-header">
<h3>{{modalOptions.headerText}}</h3>
</div>
<div class="modal-body">
<p>{{modalOptions.bodyText}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn"
data-ng-click="modalOptions.close()">{{modalOptions.closeButtonText}}</button>
<button class="btn btn-primary"
data-ng-click="modalOptions.ok();">{{modalOptions.actionButtonText}}</button>
</div>
once this is done... you just have to inject above service whereever you want to create a dialog box, example below
$scope.deleteCustomer = function () {
var custName = $scope.customer.firstName + ' ' + $scope.customer.lastName;
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete Customer',
headerText: 'Delete ' + custName + '?',
bodyText: 'Are you sure you want to delete this customer?'
};
modalService.showModal({}, modalOptions)
.then(function (result) {
//your-custom-logic
});
}
You can see my example. whatever i'v done.
<div ng-app="myApp" ng-controller="firstCtrl">
<button ng-click="delete(1);">Delete </button>
</div>
script
var app = angular.module("myApp", []);
app.controller('firstCtrl', ['$scope','$window', function($scope,$window) {
$scope.delete = function(id) {
deleteUser = $window.confirm('Are you sure you want to delete the Ad?');
if(deleteUser){
//Your action will goes here
alert('Yes i want to delete');
}
};
}])
You can use the Angular Confirm library.
When included, it's became available as a directive:
<button type="button" ng-click="delete()" confirm="Are you sure?">Delete</button>
As well as a service:
angular.module('MyApp')
.controller('MyController', function($scope, $confirm) {
$scope.delete = function() {
$confirm({text: 'Are you sure you want to delete?', title: 'Delete it', ok: 'Yes', cancel: 'No'})
.then(function() {
// send delete request...
});
};
});
For anything that has code that is triggered with a ng-click I just add a confirm attribute
eg
<a confirm="Are you sure?" ng-click="..."></a>
and confirm comes from (not mine, found on the web)
app.controller('ConfirmModalController', function($scope, $modalInstance, data) {
$scope.data = angular.copy(data);
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}).value('$confirmModalDefaults', {
template: '<div class="modal-header"><h3 class="modal-title">Confirm</h3></div><div class="modal-body">{{data.text}}</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: 'ConfirmModalController'
}).factory('$confirm', function($modal, $confirmModalDefaults) {
return function(data, settings) {
settings = angular.extend($confirmModalDefaults, (settings || {}));
data = data || {};
if ('templateUrl' in settings && 'template' in settings) {
delete settings.template;
}
settings.resolve = { data: function() { return data; } };
return $modal.open(settings).result;
};
})
.directive('confirm', function($confirm) {
return {
priority: 1,
restrict: 'A',
scope: {
confirmIf: "=",
ngClick: '&',
confirm: '#'
},
link: function(scope, element, attrs) {
function reBind(func) {
element.unbind("click").bind("click", function() {
func();
});
}
function bindConfirm() {
$confirm({ text: scope.confirm }).then(scope.ngClick);
}
if ('confirmIf' in attrs) {
scope.$watch('confirmIf', function(newVal) {
if (newVal) {
reBind(bindConfirm);
} else {
reBind(function() {
scope.$apply(scope.ngClick);
});
}
});
} else {
reBind(bindConfirm);
}
}
}
})
My google FOO has failed me and I cannot find the source site for this. I will update if I find it.
You can create a simple factory like this
angular.module('app')
.factory('modalService', [
'$modal', function ($modal) {
var self = this;
var modalInstance = null;
self.open = function (scope, path) {
modalInstance = $modal.open({
templateUrl: path,
scope: scope
});
};
self.close = function () {
modalInstance.dismiss('close');
};
return self;
}
]);
In your controller
angular.module('app').controller('yourController',
['$scope','modalService',function($scope,modalService){
$scope.openModal=function(){
modalService.open($scope,'modal template path goes here');
};
$scope.closeModal=function(){
modalService.close();
//do something on modal close
};
}]);
I have passed $scope in service function so that you can access closeModal function and in case you want to access some data from your controller .
In your html
<button ng-click="openModal()">Open Modal</button>

ngTransclude fails on PhantomJS

So I'm developing a AngularJS website, and I have been charged with the task of making it friendly to facebook sharing and SEO. I've chosen PhantomJS as a possible solution, to evaluate the Javascript and spit out executed html code, which for now is just about filling the facebook meta tags with information.
However after getting phantomJS started, and evaluating the page, I get this error in my chrome:
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <ul ng-transclude="">
http://errors.angularjs.org/1.2.22/ngTransclude/orphan?p0=%3Cul%20ng-transclude%3D%22%22%3E
After looking over the code, I only use transclude once in the site, and that is for generating my menu, which consist of a ul and transcluded li items.
app.directive('mvMenu', ['$rootScope', '$location', function ($rootScope, $location) {
return {
restrict: 'E',
transclude: true,
controller: function ($scope, $element, $attrs) {
...
},
template: '<nav id="nav" role="navigation">' +
'<div class="block">' +
'<ul ng-transclude>' +
'</ul>' +
'</div>' +
'</nav>'
}
}]);
My li items are also a directive, and its code is this:
app.directive('mvMenuItem', ['$location', function ($location) {
return {
require: '^mvMenu',
restrict: 'E',
scope: {
text: '#text',
navLink: '#link',
icon: '#faicon'
},
link: function (scope, element, attr, menuCtrl) {
...
},
template: '<li ng-class="{\'is-active\': isActive(navLink)}">' +
'<a ng-click="navAndToggleMenu(navLink)"><span class="fa {{icon}}"></span>{{text | translate}}</a>' +
'</li><!--'
}
}]);
I have never seen this error when the site is used on any browser on any device. It only comes up when evaluating the page with phantomJS.
This is the phantomJS code I am using to generate the html:
var page = require('webpage').create();
var system = require('system');
var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();
page.onResourceReceived = function (response) {
if (requestIds.indexOf(response.id) !== -1) {
lastReceived = new Date().getTime();
responseCount++;
requestIds[requestIds.indexOf(response.id)] = null;
}
};
page.onResourceRequested = function (request) {
if (requestIds.indexOf(request.id) === -1) {
requestIds.push(request.id);
requestCount++;
}
};
function checkLoaded() {
return page.evaluate(function () {
return document.all;
}) != null;
}
// Open the page
page.open(system.args[1], function () { });
var checkComplete = function () {
// We don't allow it to take longer than 5 seconds but
// don't return until all requests are finished
if ((new Date().getTime() - lastReceived > 300 && requestCount ===
responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
clearInterval(checkCompleteInterval);
var result = page.content;
//result = result.substring(0, 10000);
console.log(result);
//console.log(results);
phantom.exit();
}
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);
This code is taken from How to make a SPA SEO crawlable?, with the only difference beeing the "return document.all" in the evaluate method.
Thanks in advance!
After you run your PhantomJS script, the html that you get out is already rendered. It doesn't make sense to let angular try to render it again when you open it in a normal browser after it already rendered.
Take for example the following markup:
<div ng-repeat="stuff in stuffList">{{stuff}}</div>
This gets for example rendered to
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff1</div>
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff2</div>
What would happen if you open the already rendered markup in a browser? The ng-repeat would be executed again which would result in the following markup:
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff1</div>
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff1</div>
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff2</div>
<div class="ng-repeat" ng-repeat="stuff in stuffList">Stuff2</div>
That's not what you want.
You should remove all page scripts before saving the markup. The JavaScript is not needed anymore.
page.evaluate(function(){
[].forEach.call(document.querySelectorAll("script"), function(s){
s.parentNode.removeChild(s)
});
});
fs.write("content.html", page.content);
phantom.exit();

Angularjs Hash linking redirects to a new URL

I want to link to an element on the same page. I followed the link below but for some reason it redirecting me to the same URL with #id attached to the URL, I have html5Mode enabled.
How to handle anchor hash linking in AngularJS
directive code
var commonComponentsApp = commonComponentsApp || angular.module('cpUtils', []);
commonComponentsApp.directive("highlight", function ($location, $compile, $anchorScroll) {
'use strict';
return {
restrict: "A",
controller: function ($scope) {
$scope.scrollTo = function (id) {
console.log(id);
$location.hash(id);
$anchorScroll();
}
},
link: function (scope, element, attrs) {
var hightedText = attrs.highlight;
hightedText = hightedText.replace("<em", "<em ng-click=scrollTo('" + attrs.id + "')");
var item = '<p>' + hightedText + '</p>';
var e = angular.element(item);
e = $compile(e)(scope);
element.html(e);
}
};
});
Any help will be much appreciated. Thanks
UPDATE
Its inside a ng-repeaet
<li id="document-{{$index}}" ng-repeat="document in query.response.result" ng- mouseenter="select(document, $index)" bubble>
<div class="description-wrapper">
......
<hr />
<p highlight="{{query.response.highlighted[document.id].text[0]}}" id="{{document.id}}"></p>
.....
</div>
</li>
as you hover over a list item, it shows the complete record on the side which has the link it should scroll to.
Does your attrs.id value contain the hash? If so, it shouldn't. You only need the id value, without the hash.
Also, your ng-click=scrollTo(id) is missing quotes:
<em ng-click="scrollTo('idName')">

Resources