There seems to be an issue with my ng-model binding to filterDetails.value. It will display the value "Test", but when I update the value to "Test1234" and click the button to execute filterColumn(), the alert displays "Test" instead of the updated value. If I then update the value again to "Test12345" and click the filter button it displays "Test1234". It is always one update behind. I am using angular version 1.2.24. This particular pop over is being used inside a header cell template in a ng-grid. I added a popover to something else on the page and it works fine so it does appear to be related to using it in the ng-grid.
app.directive('popOver', function ($compile) {
var filterTemplate = "<div><input type=\"text\" ng-model=\"filterDetails.value\"></input><button style=\"margin-left:5px;\" ng-click=\"filterColumn()\">Filter</button></div>";
var getTemplate = function () {
var template = filterTemplate;
return template;
}
return {
restrict: "A",
link: function (scope, element, attrs) {
scope.filterDetails =
{
value: "Test"
};
scope.filterColumn = function () {
alert(scope.filterDetails.value);
};
var popOverContent;
var html = getTemplate();
popOverContent = $compile(html)(scope);
var options = {
content: popOverContent,
placement: "bottom",
html: true,
title: scope.title,
container: "body"
};
$(element).popover(options);
},
scope: false
};
});
What version of angular are you using? You may need to upgrade your version of angular. I put together a quick jsfiddle with your directive in it, and it seems to be working ok for me using angular 1.3.15 AND 1.2.28
//angular 1.3.15
http://jsfiddle.net/x5o9s27a/
//angular 1.2.28
http://jsfiddle.net/ge7bqh4n/
Related
I'm trying to create an editor which does "syntax highlighting",
it is rather simple:
yellow -> <span style="color:yellow">yellow</span>
I'm also using <code contenteditable> html5 tag to replace <textarea>, and have color output.
I started from angularjs documentation, and created the following simple directive. It does work, except it do not update the contenteditable area with the generated html.
If I use a element.html(htmlTrusted) instead of ngModel.$setViewValue(htmlTrusted), everything works, except the cursor jumps to the beginning at each keypress.
directive:
app.directive("contenteditable", function($sce) {
return {
restrict: "A", // only activate on element attribute
require: "?ngModel", // get ng-model, if not provided in html, then null
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {return;} // do nothing if no ng-model
element.on('blur keyup change', function() {
console.log('app.directive->contenteditable->link->element.on()');
//runs at each event inside <div contenteditable>
scope.$evalAsync(read);
});
function read() {
console.log('app.directive->contenteditable->link->read()');
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs.stripBr && html == '<br>' ) {
html = '';
}
html = html.replace(/</, '<');
html = html.replace(/>/, '>');
html = html.replace(/<span\ style=\"color:\w+\">(.*?)<\/span>/g, "$1");
html = html.replace('yellow', '<span style="color:yellow">yellow</span>');
html = html.replace('green', '<span style="color:green">green</span>');
html = html.replace('purple', '<span style="color:purple">purple</span>');
html = html.replace('blue', '<span style="color:yellow">blue</span>');
console.log('read()-> html:', html);
var htmlTrusted = $sce.trustAsHtml(html);
ngModel.$setViewValue(htmlTrusted);
}
read(); // INITIALIZATION, run read() when initializing
}
};
});
html:
<body ng-app="MyApp">
<code contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>This <span style="color:purple">text is purple.</span> Change me!</code>
<hr>
<pre>{{userContent}}</pre>
</body>
plunkr: demo (type yellow, green or blue into the change me input area)
I tried scope.$apply(), ngModel.$render() but has no effect. I must miss something really obvious...
The links I already read through:
others' plunker demo 1
others' plunker demo 2
angularjs documentation's example
$sce.trustAsHtml stackoverflow question
setViewValue stackoverflow question
setViewValue not updating stackoverflow question
Any help is much appreciated. Please see the plunker demo above.
After almost a year, I finally settled to Codemirror, and I was never happier.
I'm doing side-by-side markdown source editing with live update (with syntax highlighting, so even a bit more advanced than stackoverflow's editing page.)
I created a simple codeEditor angular directive, which requires codeMirror, and uses it.
For completeness, here is the component sourcecode:
$ cat components/codeEditor/code-editor.html
<div class="code-editor"></div>
$ cat codeEditor.js
'use strict';
angular.module('myApp')
.directive('codeEditor', function($timeout, TextUtils){
return {
restrict: 'E',
replace: true,
require: '?ngModel',
transclude: true,
scope: {
syntax: '#',
theme: '#'
},
templateUrl: 'components/codeEditor/code-editor.html',
link: function(scope, element, attrs, ngModelCtrl, transclude){
// Initialize Codemirror
var option = {
mode: scope.syntax || 'xml',
theme: scope.theme || 'default',
lineNumbers: true
};
if (option.mode === 'xml') {
option.htmlMode = true;
}
scope.$on('toedit', function () { //event
//This is required to correctly refresh the codemirror view.
// otherwise the view stuck with 'Both <code...empty.' initial text.
$timeout(function() {
editor.refresh();
});
});
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('codeEditor.js needs CodeMirror to work... (o rly?)');
}
var editor = window.CodeMirror(element[0], option);
// Handle setting the editor when the model changes if ngModel exists
if(ngModelCtrl) {
// Timeout is required here to give ngModel a chance to setup. This prevents
// a value of undefined getting passed as the view is rendered for the first
// time, which causes CodeMirror to throw an error.
$timeout(function(){
ngModelCtrl.$render = function() {
if (!!ngModelCtrl.$viewValue) {
// overwrite <code-editor>SOMETHING</code-editor>
// if the $scope.content.code (ngModelCtrl.$viewValue) is not empty.
editor.setValue(ngModelCtrl.$viewValue); //THIRD happening
}
};
ngModelCtrl.$render();
});
}
transclude(scope, function(clonedEl){
var initialText = clonedEl.text();
if (!!initialText) {
initialText = TextUtils.normalizeWhitespace(initialText);
} else {
initialText = 'Both <code-editor> tag and $scope.content.code is empty.';
}
editor.setValue(initialText); // FIRST happening
// Handle setting the model if ngModel exists
if(ngModelCtrl){
// Wrap these initial setting calls in a $timeout to give angular a chance
// to setup the view and set any initial model values that may be set in the view
$timeout(function(){
// Populate the initial ng-model if it exists and is empty.
// Prioritize the value in ngModel.
if(initialText && !ngModelCtrl.$viewValue){
ngModelCtrl.$setViewValue(initialText); //SECOND happening
}
// Whenever the editor emits any change events, update the value
// of the model.
editor.on('change', function(){
ngModelCtrl.$setViewValue(editor.getValue());
});
});
}
});
// Clean up the CodeMirror change event whenever the directive is destroyed
scope.$on('$destroy', function(){
editor.off('change');
});
}
};
});
There is also inside the components/codeEditor/vendor directory the full codemirror sourcecode.
I can highly recommend codeMirror. It is a rocksolid component, works in
every browser combination (firefox, firefox for android, chromium).
I'm trying to create a simple html5 video playlist app. I've got an overlay div on top of the html5 video that should appear/disappear when stopping and starting the video.
I've got ng-show and a variable to trigger it, but it's not changing when I look using ng-inspector.
My events might not be quite correct, either - but I can't seem to find much information on putting events on different elements within the same directive. Is this a clue that I should break this up into multiple directives?
(function() {
'use strict';
angular
.module('app')
.controller('campaignController', campaignController)
.directive('myVideo', myvideo);
function campaignController($log,Campaign) {
var vm = this;
vm.overlay = true;
Campaign.getCampaign().success(function(data) {
vm.campaign = data[0];
vm.item = vm.campaign.videos[0];
});
vm.select = function(item) {
vm.item = item;
};
vm.isActive = function(item) {
return vm.item === item;
};
};
function myvideo() {
return {
restrict: 'E',
template: ['<div class="video-overlay" ng-show="vm.overlay">',
'<p>{{ vm.campaign.name}}</p>',
'<img class="start" src="play.png">',
'</div>',
'<video class="video1" controls ng-src="{{ vm.item.video_mp4_url | trusted }}" type="video/mp4"></source>',
'</video>' ].join(''),
link: function(scope, element, attrs) {
scope.video = angular.element(document.getElementsByClassName("video1")[0]);
scope.startbutton = angular.element(document.getElementsByClassName("start")[0]);
scope.startbutton.on('click', function() {
scope.vm.overlay = false;
scope.video[0].play();
});
scope.video.on('click', function() {
scope.video[0].pause();
scope.vm.overlay = true;
});
}
};
}
})();
From my personal experience angular expression evaluation does not work as javascript. so try ng-show="vm.overlay==true".
Furthermore you bind click using native javascript.
Either don't do that and use ng-click or call scope.$apply() in the click event t callbackas last intruction (even though i'm not sure if it's really important).
I created a directive for table sorting as
app.directive('msTableHeader', function ($compile) {
return {
restrict: 'EA',
scope: {
key: '#',
title: '#',
sortBy: '=',
reverse: '='
},
link: function (scope, element) {
var html = "<th class='{{key}}' nowrap>{{title}} <a ng-click=\"sort('{{key}}')\"><i class='icon-sort'></i></a></th>";
var elm = $compile(html)(scope);
element.replaceWith(elm);
},
controller: function ($scope) {
$scope.sort = function (sortBy) {
if ($scope.sortBy == sortBy)
$scope.reverse = !$scope.reverse;
else
$scope.sortBy = sortBy;
// icon setup
$('th i').each(function () {
// icon reset
$(this).removeClass().addClass('icon-sort');
});
var icon = $scope.reverse ? 'icon-chevron-up' : 'icon-chevron-down';
$('th.' + sortBy + ' i').removeClass().addClass(icon);
};
}
}
});
This is how I use it:
<th ms-table-header key="name" title="Name" sort-by="sortBy" reverse="reverse"/>
It was working fine earlier. However, today I suddenly find it's not working anymore. The problem is, as I found through debugger, that {{key}} inside of the sort function for ng-click is not evaluated, so that {{key}} literal got passed to sort function. I believe this happened after I upgraded angularjs from 1.1.5 to 1.2.
EDIT:
I created a Plunker at here. Initially, I use icons from font-awesome css, but those icons don't show up. I changed them to gryphicons from bootstrap.css. I included two angular.js versions, with one commented out. If you enable version 1.1.5, the table sorting works, but if you enable 1.2.14, it doesn't work.
Finally it was like I said, you need to pass key, not '{{key}}', you can see it working here:
http://plnkr.co/edit/Af1PHb9LVqZbJCdnH6oz?p=preview
I have a form that is wired into angular, using it for validation. I am able to display error messages using ng-show directives like so:
<span ng-show="t3.f.needsAttention(f.fieldName)" ng-cloak>
<span ng-show="f.fieldName.$error.required && !f.fieldName.$viewValue">
This field is required.
</span>
</span>
.. where f is the form, and t3 comes from a custom directive on the form which detects whether a submission was attempted, and contains functions for checking the validity of fields.
What I am trying to accomplish is to display validation message(s) inside a popover instead. Either bootstrap's native popover, or the popover from UI Bootstrap, I have both loaded. I may also consider AngularStrap if it is easier to do it using that lib.
What I'm struggling with right now is the nature of popovers in general -- they autodisplay based on user events like click, mouseenter, blur, etc. What I want to do is show & hide the popover(s) based on the same functions in the ng-show attributes above. So that when the expression returns false hide it, and when it returns true, show it.
I know bootstrap has the .popover('show') for this, but I'm not supposed to tell angular anything about the dom, so I'm not sure how I would get access to $(element).popover() if doing this in a custom form controller function. Am I missing something?
Update
The solution mentioned in the duplicate vote still only shows the popover on mouseenter. I want to force it to display, as if doing $('#popover_id').popover('show').
You can also build your own extended triggers. This will apply to both Tooltip and Popover.
First extend the Tooltip triggers as follows:
// define additional triggers on Tooltip and Popover
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'show': 'hide'
});
}]);
Then define the trigger on the HTML tag like this:
<div id="RegisterHelp" popover-trigger="show" popover-placement="left" popover="{{ 'Login or register here'}}">
And now you can call hide and show from JavaScript, this is a show in 3 seconds.
$("#RegisterHelp").trigger('show');
//Close the info again
$timeout(function () {
$("#RegisterHelp").trigger('hide');
}, 3000);
As it turns out, it's not very difficult to decorate either the ui-bootstrap tooltip or the popover with a custom directive. This is written in typescript, but the javascript parts of it should be obvious. This single piece of code works to decorate either a tooltip or a popover:
'use strict';
module App.Directives.TooltipToggle {
export interface DirectiveSettings {
directiveName: string;
directive: any[];
directiveConfig?: any[];
}
export function directiveSettings(tooltipOrPopover = 'tooltip'): DirectiveSettings {
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', ($tooltipProvider: ng.ui.bootstrap.ITooltipProvider): void => {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = (): any[] => {
return ['$timeout', ($timeout: ng.ITimeoutService): ng.IDirective => {
var d: ng.IDirective = {
name: directiveName,
restrict: 'A',
link: (scope: ng.IScope, element: JQuery, attr: ng.IAttributes) => {
if (angular.isUndefined(attr[directiveName + 'Toggle'])) return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise: ng.IPromise<void>;
$(window).on('resize', (): void => {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout((): void => {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], (value: boolean): void => {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout((): void => {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout((): void => {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings: DirectiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
}
With this single piece of code, you can set up programmatic hide and show of either a tooltip or popover like so:
var tooltipToggle = App.Directives.TooltipToggle.directiveSettings();
var popoverToggle = App.Directives.TooltipToggle.directiveSettings('popover');
var myModule = angular.module('my-mod', ['ui.bootstrap.popover', 'ui.bootstrap.tpls'])
.directive(tooltipToggle.directiveName, tooltipToggle.directive)
.config(tooltipToggle.directiveConfig)
.directive(popoverToggle.directiveName, popoverToggle.directive)
.config(popoverToggle.directiveConfig);
Usage:
<span tooltip="This field is required."
tooltip-toggle="formName.fieldName.$error.required"
tooltip-animation="false" tooltip-placement="right"></span>
or
<span popover="This field is required."
popover-toggle="formName.fieldName.$error.required"
popover-animation="false" popover-placement="right"></span>
So we are reusing everything else that comes with the ui-bootstrap tooltip or popover, and only implementing the -toggle attribute. The decorative directive watches that attribute, and fires custom events to show or hide, which are then handled by the ui-bootstrap tooltip provider.
Update:
Since this answer seems to be helping others, here is the code written as javascript (the above typescript more or less compiles to this javascript):
'use strict';
function directiveSettings(tooltipOrPopover) {
if (typeof tooltipOrPopover === "undefined") {
tooltipOrPopover = 'tooltip';
}
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', function ($tooltipProvider) {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = function() {
return ['$timeout', function($timeout) {
var d = {
name: directiveName,
restrict: 'A',
link: function(scope, element, attr) {
if (angular.isUndefined(attr[directiveName + 'Toggle']))
return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise;
$(window).on('resize', function() {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout(function() {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], function(value) {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout(function() {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout(function() {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
For ui.bootstrap 0.13.4 and newer:
A new parameter (popover-is-open) was introduced to control popovers in the official ui.bootstrap repo. This is how you use it in the latest version:
<a uib-popover="Hello world!" popover-is-open="isOpen" ng-click="isOpen = !isOpen">
Click me to show the popover!
</a>
For ui.bootstrap 0.13.3 and older:
I just published a small directive that adds more control over popovers on GitHub: https://github.com/Elijen/angular-popover-toggle
You can use a scope variable to show/hide the popover using popover-toggle="variable" directive like this:
<span popover="Hello world!" popover-toggle="isOpen">
Popover here
</span>
Here is a demo Plunkr: http://plnkr.co/edit/QeQqqEJAu1dCuDtSvomD?p=preview
My approach:
Track the state of the popover in the model
Change this state per element using the appropriate directives.
The idea being to leave the DOM manipulation to the directives.
I have put together a fiddle that I hope gives a better explain, but you'll find much more sophisticated solutions in UI Bootstrap which you mentioned.
jsfiddle
Markup:
<div ng-repeat="element in elements" class="element">
<!-- Only want to show a popup if the element has an error and is being hovered -->
<div class="popover" ng-show="element.hovered && element.error" ng-style>Popover</div>
<div class="popoverable" ng-mouseEnter="popoverShow(element)" ng-mouseLeave="popoverHide(element)">
{{ element.name }}
</div>
</div>
JS:
function DemoCtrl($scope)
{
$scope.elements = [
{name: 'Element1 (Error)', error: true, hovered: false},
{name: 'Element2 (no error)', error: false, hovered: false},
{name: 'Element3 (Error)', error: true, hovered: false},
{name: 'Element4 (no error)', error: false, hovered: false},
{name: 'Element5 (Error)', error: true, hovered: false},
];
$scope.popoverShow = function(element)
{
element.hovered = true;
}
$scope.popoverHide = function(element)
{
element.hovered = false
}
}
For others coming here, as of the 0.13.4 release, we have added the ability to programmatically open and close popovers via the *-is-open attribute on both tooltips and popovers in the Angular UI Bootstrap library. Thus, there is no longer any reason to have to roll your own code/solution.
From Michael Stramel's answer, but with a full angularJS solution:
// define additional triggers on Tooltip and Popover
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'show': 'hide'
});
}])
Now add this directive:
app.directive('ntTriggerIf', ['$timeout',
function ($timeout) {
/*
Intended use:
<div nt-trigger-if={ 'triggerName':{{someCodition === SomeValue}},'anotherTriggerName':{{someOtherCodition === someOtherValue}} } ></div>
*/
return {
restrict: 'A',
link: function (scope, element, attrs) {
attrs.$observe('ntTriggerIf', function (val) {
try {
var ob_options = JSON.parse(attrs.ntTriggerIf.split("'").join('"') || "");
}
catch (e) {
return
}
$timeout(function () {
for (var st_name in ob_options) {
var condition = ob_options[st_name];
if (condition) {
element.trigger(st_name);
}
}
})
})
}
}
}])
Then in your markup:
<span tooltip-trigger="show" tooltip="Login or register here" nt-trigger-if="{'show':{{ (errorConidtion) }}, 'hide':{{ !(errorConidtion) }} }"></span>
I am using angular-ui's ui-select2. I want to add custom html formatting to the selections. Select2 allows this by specifying the formatSelection in its config.
I have html with angular tags as below that I want to use for formatting the selection-
var format_code = $compile('<div ng-click="showHide=!showHide" class="help-inline"><div style="cursor: pointer;" ng-show="!!showHide" ng-model="workflow.select" class="label">ANY</div><div style="cursor: pointer;" ng-hide="!!showHide" ng-model="workflow.select" class="label">ALL</div></div>')( $scope );
var format_html = "<span>" + data.n + ' : ' + data.v +' ng-bind-html-unsafe=format_code'+ "</span>"
$scope.select_config = {
formatSelection: format_html
}
If I compile the html as in above and assign it, I just see an [object,object] rendered in the browser. If I dont compile it, I see the html rendered properly, but the angular bindings dont happen, ie the clicks dont work.
Any ideas what is wrong?
I had the same problem, select2 loading in a jquery dialog and not using the options object I would give it.
What I ended up doing is isolating the element in a directive as following:
define(['./module'], function (module) {
return module.directive('dialogDirective', [function () {
return {
restrict: 'A',
controller: function ($scope) {
console.log('controller gets executed first');
$scope.select2Options = {
allowClear: true,
formatResult: function () { return 'blah' },
formatSelection: function () { return 'my selection' },
};
},
link: function (scope, element, attrs) {
console.log('link');
scope.someStuff = Session.someStuff();
element.bind('dialogopen', function (event) {
scope.select2content = MyResource.query();
});
},
}
}]);
and the markup
<div dialog-directive>
{{select2Options}}
<select ui-select2="select2Options" style="width: 350px;">
<option></option>
<option ng-repeat="item in select2content">{{item.name}}</option>
</select>
{{select2content | json}}
</div>
What is important here:
'controller' function gets executed before html is rendered. That means when the select2 directive gets executed, it will already have the select2Options object initialized.
'link' function populates the select2content variable asynchronously using the MyResource $resource.
Go on and try it, you should see all elements in the dropdown as "blah" and selected element as "my selection".
hope this helps, that was my first post to SO ever.