When I click on datapicker (angular moment picker) after load it in inline fancybox, it output empty window.
But if I click on datapicker input directly - all works fine.
I use fancybox as directive:
.directive('fancybox', function ($compile, $http) {
return {
scope: true,
restrict: 'EA',
link: function(scope, element, attrs) {
scope.openFancybox = function (href) {
scope.curHosterUID = angular.fromJson(attrs.hoster);
//console.log(scope.chEvent);
//scope.curHosterUID = href.replace('#viewEvent', '');
console.log(scope.curHosterUID);
var template = angular.element(href);
var compiledTemplate = $compile(template);
compiledTemplate(scope);
$.fancybox.open();
$.fancybox.open({ href: href, type: 'inline' });
};
}
};
})
<div class="input-group" moment-picker="ctrl.timepicker" format="HH:mm:ss">
<span class="input-group-addon">
<i class="fa fa-clock-o"></i>
</span>
<input id="event_start" class="form-control"
placeholder="Select a time"
ng-model="ctrl.timepicker"
ng-model-options="{ updateOn: 'blur' }">
</div>
How I can force datapicker in fancybox?
Related
Currently I'm testing kendo ui controls with angular js. I',m trying to create my own directive which is using kendo auto complete control. It looks like this:
app.directive('testCompile', [ '$log','$compile', function ($log, $compile) {
var directiveDefinitionObject = {
restrict: 'A',
transclude: true,
controller: function () {
},
controllerAs: 'testctrl',
bindToController: true,
compile: function compile(tElement:ng.IAugmentedJQuery, tAttrs: ng.IAttributes,tTransclude) {
return {
pre: function preLink(scope, iElement:ng.IAugmentedJQuery, iAttrs:ng.IAttributes, controller, iTransclude) {
iTransclude(function (clone) {
var html = '<input id="test-id" test-compile"test" type="text" kendo-auto-complete class="k-input" k-data-source=""/>';
var compiled = $compile(html)(scope);
iElement.replaceWith(compiled);
});
},
post: function postLink(scope, iElement:ng.IAugmentedJQuery, iAttrs:ng.IAttributes, controller, iTransclude) {
}
}
}
};
return directiveDefinitionObject;
}]);
It should generate part of html like this:
<span tabindex="-1" role="presentation" class="k-widget k-autocomplete k-header k-input form-control k-state-default">
<input id="test-id" type="text" kendo-auto-complete="" class="k-input form-control" k-data-source="" data-role="autocomplete" autocomplete="off" role="textbox" aria-haspopup="true" aria-disabled="false" aria-readonly="false" aria-owns="city2_listbox" aria-autocomplete="list" style="width: 100%;">
<span class="k-icon k-loading" style="display:none">
</span>
</span>
But unfortunately it generates only this:
<input id="test-id" type="text" kendo-auto-complete="" class="k-input form-control" k-data-source="" data-role="autocomplete" autocomplete="off" role="textbox" aria-haspopup="true" aria-disabled="false" aria-readonly="false" aria-owns="city2_listbox" aria-autocomplete="list" style="width: 100%;">
Spans are not generated.
What am I doing wrong?
I want on-click event from directive invoke some function from my controller. But for some reason it doesn't work. I want my datepicker to expand when I event is fired. Could you please help me to investigate what is wrong my in my current build?
app.js
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened',
openFunction: '&'
},
templateUrl: 'templates/datepicker/datepicker.html',
link: function(scope, elm, attrs) {
}
};
});
app.controller('FlightDatePickerController', function ($scope) {
$scope.openFunction = function($event, isDepart) {
$event.preventDefault();
$event.stopPropagation();
$scope.departureOpened = true;
};
};
datepicker.html
<fieldset>
<pre>{{model}}</pre>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup ng-model="{{model}}" min-date="{{minDate}}" is-open="{{isOpened}}" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click='openFunction({event:event}, {isDepart:isDepart})' class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
index.html
<div ng-controller="FlightDatePickerController">
<div class="col-md-2">
<my-datepicker model="departureDate" minDate="minDateDeparture" isOpened="departureOpened" open-function="openFunction($event, isDepart)"></my-datepicker>
</div>
</div>
You can add a controller attribute to your directive, in order to bind some function to your template.
In your case, you can do :
Directive
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened'
},
templateUrl: 'templates/datepicker/datepicker.html',
controller: 'FlightDatePickerController'
};
});
Datepicker.html
<div ng-controller="FlightDatePickerController">
<div class="col-md-2">
<my-datepicker model="departureDate" minDate="minDateDeparture" isOpened="departureOpened"></my-datepicker>
</div>
</div>
Your overall implementation is correct, but you made couple of mistakes.
ng-click should be like adding parameter in JSON like structure.
ng-click='openFunction({event:$event, isDepart:isDepart})'
& then your directive element should have
open-function="openFunction($event, isDepart)"
I have a directive that wraps a form element with some inputs. One of the options is passing in a formName. Usually, with a form with the example name of myForm, to show an error you would do something like myForm.firstName.$error.required.
But, how do I get access to the errors when the form name is dynamically being passed in to the directive?
example usage
<my-custom-form formName='myForm' formSubmit='parentCtrl.foo()'></my-custom-form>
directive
angular.module('example')
.directive('myCustomForm', [
function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'myCustomForm.directive.html',
scope: {
fornName: '#',
formSubmit: '&'
},
require: ['myCustomForm', 'form'],
link: function(scope, element, attrs, ctrls) {
var directiveCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.data = {};
scope.hasError = function(field) {
// how do i show the errors here?
};
scope.onSubmit = function() {
scope.formSubmit();
};
}
};
}]);
template
<form name="{{ formName }}" ng-submit="onSubmit()" novalidate>
<div class="form-group" ng-class="{'is-invalid': hasError('fullName') }">
<input type="text" name="fullName" ng-model="data.full_name" required />
<div ng-show="hasError('fullName')">
<p>How do I show this error?</p>
</div>
</div>
<div class="form-group" ng-class="{'is-invalid': hasError('email') }">
<input type="text" name="email" ng-model="data.email" ng-minlength="4" required />
<div ng-show="hasError('email')">
<p>How do I show this error?</p>
</div>
</div>
<button type="submit">Submit</button>
</form>
I think the only problem with your code is that the directive requires itself, I don't think that will work. Just removing the myCustomForm from the require works fine.
To check if the field has errors, you just need to check if the $error object in the form controller is empty.
require: ['form'],
link: function(scope, element, attrs, ctrls) {
var formCtrl = ctrls[0];
scope.data = {};
scope.hasError = function(field) {
// Field has errors if $error is not an empty object
return !angular.equals({}, formCtrl[field].$error);
};
Plunker
For example I have the form where I am showing form input errors.
I need to show red badge (with 'hover to show errors') near input label if there are some errors. If user will hover this red badge - he will see list of errors using AngularJS UI Bootstrap tooltip.
I don't want to put list of errors into tooltip-html-unsafe attribute, because it is not convenient to edit and maintain.
This code is more declarative:
<validation-tooltip ng-show="appForm.number.$invalid && appForm.number.$dirty">
<ul>
<li ng-show="appForm.number.$error.required">this field is required</li>
<li ng-show="appForm.number.$error.number">should be number</li>
<li ng-show="appForm.number.$error.min">minimum - 5</li>
<li ng-show="appForm.number.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
than this code:
<span tooltip-html-unsafe="{{<ul><li>This field is required;</li><li>...</li></ul>}}">hover to show errors</span>
How can I write such validation-tooltip directive using AngularJS UI Bootstrap tooltip?
Or maybe can you suggest another approach to maintain validation error messages?
Demo Fiddle
Validation Tooltip Directive
The validationTooltip is the main directive. It has the following responsibilities:
Define the tool tip template through its transcluded contents
Keep track of validation expressions so that they can be evaluated with each digest cycle.
Expose a controller API for allowing valiationMessage directives to register themselves
Provide a 'target' attribute on the directive to specify which form field the badge (and the associated tooltip) will be bound to
Additional Notes
The tooltip template uses the transclusion function from the link function to bind the template to the directive's scope. There are two properties that are within scope that the template can bind to:
$form: Bound to the form model defined in parent scope. i.e. $scope.myForm
$field: Bound to the form.name model in parent scope. i.e. $scope.myForm.myInput
This allows the template to bind to validation properties such as $valid, $invalid, $pristine, $dirty, and $error without referring to the form name or the input field's name directly. For example, all of the following expressions are valid binding expressions:
$form properties:
`$form.$valid`
`$form.$invalid`
`$form.$dirty`
`$form.$pristine`
`$form.$error.required` etc...
$field properties:
`$field.$valid`
`$field.$invalid`
`$field.$dirty`
`$field.$pristine`
`$field.$error.required` etc...
Directive Implementation
app.directive('validationTooltip', function ($timeout) {
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {},
template: '<span class="label label-danger span1" ng-show="errorCount > 0">hover to show err</span>',
controller: function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
});
},
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var badge = element.find('.label');
var tooltip = angular.element('<div class="validationMessageTemplate tooltip-danger" />');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
badge.tooltip({
placement: 'right',
html: true,
title: clone
});
});
});
}
}
});
Validation Message Directive
The validationMessage directive keeps track of the validation messages to display in the tooltip. It uses ng-if to define the expression to evaluate. If there is no ng-if found on the element, then the expression simply evaluates to true (always shown).
app.directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true );
}
}
});
Usage in HTML
Add a form with a name attribute
Add one or more form fields - each with a name attribute and an ng-model directive.
Declare a <validation-tooltip> element with a target attribute referring to the name of one of the form fields.
Apply the validation-message directive to each message with an optional ng-if binding expression.
<div ng-class="{'form-group': true, 'has-error':form.number.$invalid}">
<div class="row">
<div class="col-md-4">
<label for="number">Number</label>
<validation-tooltip target="number">
<ul class="list-unstyled">
<li validation-message ng-if="$field.$error.required">this field is required </li>
<li validation-message ng-if="$field.$error.number">should be number</li>
<li validation-message ng-if="$field.$error.min">minimum - 5</li>
<li validation-message ng-if="$field.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
</div>
</div>
<div class="row">
<div class="col-md-4">
<input type="number" min="5" max="20" ng-model="number" name="number" class="form-control" required />
</div>
</div>
</div>
#pixelbits answer is great. I used this instead:
<div class="form-group" ng-class="{ 'has-error': form.name.$dirty && form.name.$invalid }">
<label for="name" class="col-sm-4 control-label">What's your name?</label>
<div class="col-sm-6">
<input class="form-control has-feedback" id="name" name="name"
required
ng-minlength="4"
ng-model="formData.name"
tooltip="{{form.name.$valid ? '' : 'How clients see your name. Min 4 chars.'}}" tooltip-trigger="focus"
tooltip-placement="below">
<span class="glyphicon glyphicon-ok-sign text-success form-control-feedback" aria-hidden="true"
ng-show="form.name.$valid"></span>
</div>
</div>
The technique is ui-bootstrap's tooltip and set the tooltip text to '' when valid.
http://jsbin.com/ditekuvipa/2/edit
Great answer from #pixelbits. I used his directives and modified them slightly to allow the tooltip to display over the actual input as some users requested.
angular.module('app')
.directive('validationTooltip', ['$timeout', function ($timeout) {
function toggleTooltip(scope) {
if (!scope.tooltipInstance) {
return;
}
$timeout(function() {
if (scope.errorCount > 0 && (scope.showWhen == undefined || scope.showWhen())) {
scope.tooltipInstance.enable();
scope.tooltipInstance.show();
} else {
scope.tooltipInstance.disable();
scope.tooltipInstance.hide();
}
});
}
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {
showWhen: '&',
placement: '#',
},
template: '<div></div>',
controller: ['$scope', function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
toggleTooltip($scope);
});
}],
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var tooltip = angular.element('<div class="validationMessageTemplate" style="display: none;"/>');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
var targetElm = $('[name=' + attr.target + ']');
targetElm.tooltip({
placement: scope.placement != null ? scope.placement : 'bottom',
html: true,
title: clone,
});
scope.tooltipInstance = targetElm.data('bs.tooltip');
toggleTooltip(scope);
if (scope.showWhen) {
scope.$watch(scope.showWhen, function () {
toggleTooltip(scope);
});
}
});
});
}
}
}]);
The major change is that the directive uses jQuery to find the target element (which should be an input) via the name attribute, and initializes the tooltip on that element rather than the element of the directive. I also added a showWhen property to the scope as you may not always want your tooltip to show when the input is invalid (see example below).
The validationMessage directive is unchanged
angular.module('app').directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true);
}
}
});
Usage in Html is also similar, with just the addition of showWhen if you want:
<div class="form-group" ng-class="{ 'has-error' : aForm.note.$invalid && (aForm.note.$dirty) }">
<label class="col-md-3 control-label">Note</label>
<div class="col-md-15">
<textarea
name="note"
class="form-control"
data-ng-model="foo.Note"
ng-required="bar.NoteRequired"></textarea>
<validation-tooltip target="note" show-when="aForm.note.$invalid && (aForm.note.$dirty)">
<ul class="validation-list">
<li validation-message ng-if="$field.$error.required">Note required</li>
</ul>
</validation-tooltip>
</div>
</div>
you can actually just use the tooltip-enable property:
<div class="showtooltip" tooltip-placement="left" tooltip-enable="$isValid" tooltip="tooltip message"></div>
My goal was to leverage both ng-messages and ui-bootstrap popover for validation feedback. I prefer the popover vs. the tooltip as it displays the help-block styles more clearly.
Here's the code:
<!-- Routing Number -->
<div class="form-group-sm" ng-class="{ 'has-error' : form.routingNumber.$invalid && !form.routingNumber.$pristine }">
<label class="control-label col-sm-4" for="routing-number">Routing #</label>
<div class="col-sm-8">
<input class="form-control input-sm text-box"
id="routing-number"
name="routingNumber"
ng-model="entity.ROUTINGNUM"
popover-class="help-block"
popover-is-open="form.routingNumber.$invalid"
popover-trigger="none"
required
uib-popover-template="'routing-number-validators'"
string-to-number
type="number" />
</div>
<!-- Validators -->
<script type="text/ng-template" id="routing-number-validators">
<div ng-messages="form.routingNumber.$error">
<div ng-messages-include="/app/modules/_core/views/validationMessages.html"></div>
</div>
</script>
</div>
Here is the validationMessages.html
<span ng-message="required">Required</span>
<span ng-message="max">Too high</span>
<span ng-message="min">Too low</span>
<span ng-message="minlength">Too short</span>
<span ng-message="maxlength">Too long</span>
<span ng-message="email">Invalid email</span>
Note: I had to upgrade to jQuery 2.1.4 to get the uib-popover-template directive to work.
Dependencies:
string-to-number
UI Bootstrap
ng-messages
I try to make the directive with dynamic template
app.directive('boolInput', function () {
'use strict';
var
restrict = 'E',
replace = true,
template = '<ng-include src="template"></ng-include>',
scope = {
value: "=",
template: "#"
},
link = function (scope, element, attributes) {
// some stuff
};
return {
link: link,
restrict: restrict,
replace: replace,
template: template,
scope: scope,
transclude: true
};
});
So I use
template = '<ng-include src="template"></ng-include>'
and
scope = {
//..
template: "#"
}
to pass template url via attribute. All work great instead of one thing. There is how I use directive:
<bool-input data-value="item.value" data-ng-repeat="item in source" data- template="templates/boolInput.html">
{{item.Text}}
</bool-input>
{{item.Text}} - should be transcluded into template
That template:
<div class="checkbox">
<label class="checkbox-custom" ng-transclude>
<input type="checkbox">
<i class="icon-unchecked checked"></i>
</label>
</div>
but this does not happen, as a result I see:
<ng-include src="template" data-value="item.value" data-ng-repeat="item in data" data-template="templates/boolInput.html" class="ng-scope"><div class="checkbox ng-scope">
<label class="checkbox-custom" ng-transclude="">
<input type="checkbox">
<i class="icon-unchecked checked"></i>
<!-- There should be the text -->
</label>
</div></ng-include>
This was 2 years ago... For now we can pass a attribute to templateUrl function
templateUrl: function (elememt, attrs) {
return attrs.template || '<some default template path>';
}