I'm building an Ionic app where content is returned via $sce.trustAsHtml(data.content); I'd like to programmatically search through the returned HTML and bind an ng-click="openInWebView(url) where the url param is the copied value of whatever value was in the initial anchor tags a-href attribute.
For example if the content of the html is:
<p>lorem ipsum</p>
A Story!
Keep in mind this needs to be done for each <a> element in the content because an article will often contain a number of different links.
View HTML:
<div ng-bind-html="articleHtml" class="article__content" follow-anchor></div>
Directive:
hack.directive('followAnchor', ['$followAnchor', function($compile) {
return {
restrict: 'A',
// terminal: true,
priority: 1001,
link: function($scope, $element, attrs) {
anchor = $element.find('a').clone();
anchor.attr('ng-click', 'openInWebView()');
anchor = $compile(anchor)($scope);
$element.find('a').replaceWith(anchor);
}
};
}]);
Controller:
$scope.openInWebView = function(url) {
var browsingOptions = {
location: 'no',
clearcache: 'yes',
toolbar: 'yes',
mediaPlaybackRequiresUserAction: 'yes',
shouldPauseOnSuspend: 'yes',
closebuttoncaption: 'Close'
};
document.addEventListener('deviceready', function() {
$cordovaInAppBrowser.open(url, '_blank', browsingOptions)
.then(function(event) {
console.log('success!');
}).catch(function(error) {
console.log(error);
});
}, false);
};
I'm sure that my directive is incorrect, but I'm stuck as to how I should proceed or if this is even the correct way of doing something like this.
Related
I am trying to change "Collapse" text to "Expand" on click of h2 tag. at the same time I am applying "active" class to h2 tag. using following directive, which is working fine, but now I am clueless about how to change "collapse" text to Expand on h2 tag click
HTML
<h2 class="panel-title" toggleclass>My Tandem URL</h2>
<a class="collapse-arrow">Collapse</a>
js
.directive("toggleclass", function () {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
element.bind("click", function () {
element.toggleClass('active');
});
}
}
})
Can anyone plz help..
The
If you want to do DOM manipulation in your directive you could change the contents of the element by doing:
var link = element.find('a');
link.text(link.text() === 'Collapse' ? 'Expand' : 'Collapse')`.
Using html instead of text also works.
You have to move the link inside the h2 to have the directive see the link.
Another approach is having the link text changed via the scope, but then you need to have a template and bind the link text to the directive scope. Then you have to wrap both elements in a directive.
This may make the solution a bit too big for this simple use case...
Another suggestion on making up an directive: encapsulates html and behaivor inside: fiddle .
angular.module('myModule', [])
.directive('togglable', function () {
return {
restrict: 'EA',
template: '<div><h2 class="panel-title" ng-class="{ active: isCollapsed }" ng-click="toggle()" ng-transclude></h2><a class="collapse-arrow">{{ isCollapsed ? "Expand" : "Collapse"}}</a></div>',
transclude: true,
replace: true,
scope: false,
controller: function ($scope) {
$scope.isCollapsed = false;
$scope.toggle = function () {
$scope.isCollapsed = !$scope.isCollapsed;
};
}
};
});
Key features:
- no manual DOM manipulaton;
- uses transclude feature;
Such a directive is easy to use:
<togglable>My Tandem URL</togglable>
Depending on how your app works, maybe you can try this:
HTML:
<h2 class="panel-title" ng-class="{'active': expand}" toggleclass>My Tandem URL</h2>
<a class="collapse-arrow" ng-hide="expand">Collapse</a>
<a class="collapse-arrow" ng-show="expand">Expand</a>
JS:
angular.module('myApp', [])
.controller('myCTRL', function($scope) {
$scope.expand = false;
})
.directive("toggleclass", function () {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
element.bind('click', function() {
scope.$apply(function() {
scope.expand = !scope.expand;
});
});
}
}
});
http://jsfiddle.net/uqbc9asf/
I am new to angular and after having researched for 2 days, I still haven't come up with a solution that will work yet.
I have a select item that will have its options updated and am also using bootstrap-select.js. I can get either or to work on their own (angular items updating dynamically as expected in a standard select list or the bootstrap-select item to work with static options). If someone could provide some guidance as to what I am doing wrong, it would be greatly appreciated! Here is my code:
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<selectpicker data-array="users" data-selected="info.selected"></selectpicker>
<button ng-click="add()">Add</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope)
{
$scope.info = {selected: 1};
$scope.users=[];
$scope.users.splice(0);
$scope.users = [{name: "Bob", id: "1"},{name:"Tom", id: "2"}];
$scope.add = function () {
$scope.users.push({name: "John", id: "3"});
};
}]);
app.directive('selectpicker', function($timeout)
{
return {
restrict: 'E',
replace:true,
scope: {
selected: '=',
array: '=',
class: '='
},
template: '<select class="selectpicker" multiple data-selected-text-format="count" ng-model="currentName" ng-options="user.name for user in array">' +
'</select>',
replace:true,
link: function(scope, el, attrs) {
$timeout(function () {
scope.$watch('array', function (newVal) {
console.log(scope.array);
var select = $(el).selectpicker();
select.change(function(evt) {
var val = $(el).selectpicker('val');
$scope.selected = val;
$scope.$apply();
});
}, true);
});
}
};
});
So when I click the Add button, I can see the scope.array value
updates from the console output, but the dropdown itself won't update. I've tried piecing together solutions from similar answers but nothing has yielded results so far.
According to the documentation of Bootstrap-select, you could use refresh() method to update the UI when an underlying select tag has been changed like this:
$(el).selectpicker('refresh');
But lets look further how to improve the directive:
the element variable el is already a jQuery object, no need to wrap it again as $(el).
a $timeout is become unnecessary after the UI refresh is properly handled.
move the change event binding out of $watch, otherwise the handler will be fired multiple times per a change.
$watchCollection is enough if the options will be only add/remove i.e. an individual option will not be changed.
The final result would look like this:
link: function(scope, el, attrs) {
var select = el.selectpicker();
select.change(function (evt) {
scope.selected = el.selectpicker('val');
scope.$apply();
});
scope.$watchCollection('array', function(newVal) {
console.log(scope.array);
el.selectpicker('refresh');
});
}
Example Plunker: http://plnkr.co/edit/Kt0V0UBuHaRYMjKbI5Ov?p=preview
I am trying to develop a FaceBook like notification (like when friend requests are received there is an icon that glows with number of notifications on the top right corner).
For this i wrote a popover directive.
app.directive('popOver', function ($compile) {
var itemsTemplate = "<div ng-repeat='item in items'>{{item}} <br/><hr/></div> ";
var getTemplate = function (contentType) {
var template = '';
switch (contentType) {
case 'items':
template = itemsTemplate;
break;
}
return template;
}
return {
restrict: "A",
transclude: true,
template: "<span ng-transclude></span>",
link: function (scope, element, attrs) {
var popOverContent = "<div></div>";
if (scope.items) {
var html = getTemplate("items");
popOverContent = $compile(html)(scope);
}
var options = {
content: popOverContent,
placement: "bottom",
html: true,
title: scope.title
};
$(element).popover(options);
},
scope: {
items: '=',
title: '#'
}
};
});
The items are populated in the Controller, and there i am using $timeout to fetch new data from database and fill scope.Items
In the UI i have a button which shows number of new notifications and on click of it i want to show a popover with items. The problem is when is click the button i the popover is not loading the new items.
<button pop-over items="items" class="navbar-btn btn-info round-button" title="Notifications:" > {{newAlertCount}} </button>
Directives have their own scope, so I'm supposing that when you change $scope.items in your controller you're talking about a different scope; I think that what you want is to look directly at the original $scope.items object, so I would add this:
scope : {
items : '=items'
},
to your directive.
I am trying to load a 'class' directive using ng-class. but my directive is never loaded when i do that. The directive is a multipurpose directive, and I don't want to create an isolated scope on this. it will be loaded only when required, based on ng-class conditions hence not using attribute or element directive. has anyone tried doing this and succeeded?
this directive is called using <div ng-class="someClass {{tooltip: enabled}}"></div>
here enabled is a scope variable.
app.directive('tooltip', ['$timeout', '$location', '$rootScope', function (timer, $location, $rootScope) {
return {
restrict: 'C',
transclude: true,
link: function (scope, element) {
var printContent = function () {
/* uses the content of .tooltip-content if it is a complex html tooltip,
otherwise
you can use the title attribute for plaintext tooltips
*/
var tooltipContent = $(element).find('.tooltip-content').html();
if (!tooltipContent) {
tooltipContent = $(element).attr('title');
}
$(element).tooltip({
content: tooltipContent,
items: "img, a, span, button, div",
tooltipClass: "tooltip",
position: { my: "left+30 top", at: "right top", collision: "flipfit" },
show: { effect: "fadeIn", duration: "fast" },
hide: { effect: "fadeOut", duration: "fast" },
open: function (event, ui) { $rootScope.tooltipElement = event.target; }
});
};
timer(printContent, 0);
}
};
}]);
Interesting issue. It seems that you don't want to use the ng-class directive since that doesn't recompile the content after adding the class. You'll likely want to create your own dynamic-class directive that actually recompiles when the value is true:
app.directive('dynamicClass', function($compile) {
return {
scope: {
dynamicClassWhen: '=',
dynamicClass: '='
},
link: function(scope, elt, attrs) {
scope.$watch('dynamicClassWhen', function(val) {
if (val) {
console.log(val);
elt.addClass(scope.dynamicClass);
$compile(elt)(scope);
}
});
}
};
});
You may need to modify this for the ability to remove the class and depending on if the $compile is sufficient for you or if you need to further manipulate the html, but this seems to be the right track for you. I made a fiddle with this in action.
Hope this helps!
I'm trying to write a directive that has a template. The template is rendering some DOM elements I want to retrieve. However, when I try to retrieve my DOM elements in the linking function, the DOM elements are not found. If I add a window.setTimeout method before selecting the elements they are found. How can I wait for a template to finish rendering before trying to manipulate the DOM in the linking function?
Here is the directive code for what I'm trying to do:
module.directive('testLocationPicker', function() {
var linkFn = function(scope, element, attrs) {
console.log('in linking function');
window.setTimeout(function() {
var positions = $('.position');
console.log('number positions found: ' + positions.length);
positions.click(function(e) {
console.log('position clicked');
scope.$apply(function() {
scope.selectedPosition = $(e.currentTarget).html();
});
});
}, 500);
};
return {
link: linkFn,
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions">{{position}}</div>',
}
});
I have a JS Fiddle of what I'm trying to do: http://jsfiddle.net/bdicasa/XSFpu/42/
I would recommend doing something like this instead:
var module = angular.module('test', []);
module.controller('TestController', function($scope) {
$scope.positions = [
'Test Position 1',
'Test Position 2',
'Test Position 3'
];
$scope.selectedPosition = '';
$scope.handleClick = function (index) {
$scope.selectedPosition = $scope.positions[index];
}
});
module.directive('testLocationPicker', function() {
return {
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions" ng-click="handleClick($index)">{{position}}</div>',
}
});
Instead of trying to search through the dom and add a click event, just modify your template like this:
template: 'Choose a position: <div class="position" ng-repeat="position in positions" data-ng-click="positionClick($index)">{{position}}</div>',
And then create a positionClick function in the linking function:
var linkFn = function(scope, element, attrs) {
scope.positionClick = function(index){
scope.selectedPosition = index;
}
};
Working jsFiddle: http://jsfiddle.net/XSFpu/77/
The reason your method is not working is because the ng-repeat hasn't fired after the template has loaded. So it's loaded the directive in, and the link function has been hit, but the ng-repeat actually hasn't started repeating yet. This is why I'm suggesting moving some of your code around to accomdate that.