Making AngularUI datepicker an actual directive - angularjs

I'm trying to turn angular-ui's date-picker into an actual directive.
I can display it on the page with the correct model and format, but the toggling doesn't work for me (trying to name it via the attributes).
Here is my code:
My html
<date-picker data-format="dd/MM/yyyy" data-model-name="dateReviewed" data-ng-model="oneWMS.dateReviewed" data-status="statusDateReviewed" data-opened="openDateReviewed"></date-picker>
My directive
function datePicker() {
return {
restrict: 'AE',
require: 'ngModel',
scope: {
format: '#',
mod: '=ngModel',
status: '#',
opened: '#'
},
template: '<div class="input-group">' +
'<input type="text" class="form-control" datepicker-popup="{{format}}" data-ng-model="mod" is-open="status.opened" ng-required="true" close-text="Close" />' +
'<span class="input-group-btn">' +
'<button type="button" class="btn btn-default" ng-click="opened($event)"><i class="glyphicon glyphicon-calendar"></i> </button>' +
'</span>' +
'</div>',
link: function(scope, iElement, iAttrs) {
// all the directive code
console.log(iAttrs.format); // dd/MM/yyyy
console.log(iAttrs.ngModel); // oneWMS.dateReviewed
console.log(iAttrs.status); // statusDateReviewed
console.log(iAttrs.opened); // openDateReviewed
console.log(iAttrs.modelName); // dateReviewed
var modelStatusDate = iAttrs.status;
var modelOpenDate = iAttrs.opened;
var modelName = iAttrs.modelName;
scope.today = function() {
scope.modelName = new Date();
scope.dateApproved = new Date();
scope.today();
scope.clear = function () {
scope.modelName = null;
};
scope.modelOpenDate = function($event) {
scope.modelStatusDate.opened = true;
};
scope.modelStatusDate = {
opened: false
};
scope.getDayClass = function(date, mode) {
if (mode === 'day') {
var dayToCheck = new Date(date).setHours(0,0,0,0);
for (var i=0;i<scope.events.length;i++){
var currentDay = new Date(scope.events[i].date).setHours(0,0,0,0);
if (dayToCheck === currentDay) {
return scope.events[i].status;
}
}
}
return '';
};
} // link
} // return
} // picker
}
Don't know if just opening the picker doesn't work or if the date selecting will fail as well.

I have made a custom date directive using angularui date-picker. Have a look, it might be useful to you. Here is plunkr url . http://plnkr.co/edit/FDigEjyMYm5SVYnQyZGp.

If you're using Angular and Bootstrap, I would strongly encourage you to use Angular UI Bootstrap.
The other Angular UI libraries are not actively maintained.

Related

Angular bootstrap datepicker isnt loading in a separate screen

I have a date picker in one screen/url (in the same app) where the template works and loads properly but now in a different screen/url (in the same app) the date picker doesnt display properly. It looks like:
Ive compared both codes from both screens and its not working..
function dateFilter() {
var directive = {
controller: dateFilterController,
controllerAs: 'vm',
bindToController: true,
restrict: 'E',
templateUrl: 'app/utils/directives/date-filter/date.filter.tpl.html',
scope: {
'popup': '=',//date format
'options': '=',//uib-datepicker options
'ngModel': '=',//selected date
'ngChange': '=',//filter function
'myId': '=',//id and name of uib-datepicker
'label': '=',//label that preceeds datepicker
'name': '=',//name of the property being filtered
'minDate': '=',//the minumum acceptable date
'maxDate': '=',//the maximum acceptable date,
'updateQuery': '=',
'query': '=',
'popupUrl': '='
}
};
return directive;
}
function dateFilterController() {
var vm = this;
vm.dateChanged = function (date) {
var newFilterValue = {
query: vm.query,
value: date
};
vm.updateQuery(newFilterValue);
};
vm.applyDateFilter = function (x) {
vm.dateChanged(x);
var from = true;
//If the label ends in to, that means we are filtering on the to date.
if (vm.label.substring(vm.label.length - 2, vm.label.length) === 'to') {
from = false;
}
//format the filter data in a way so it can be interpretted by ngChange
var temp = {
key: vm.name,//The name of the property being filtered
value: x,//The value from the filter
from: from//Boolean indicating if it is the to(false) or from(true) date.
};
vm.ngChange(temp);
};
vm.isOpen = false;
vm.ngClick = function () {
vm.isOpen = true;
};
}
This is the date filter tpl
<div class="date_input">
<label for="{{vm.myId}}">{{ vm.label | translate}}</label>
<input tabindex="0"
role="button"
aria-labelledby="fromToDescription"
id="{{vm.myId}}"
name="{{vm.myId}}"
type="text"
class="form-control"
uib-datepicker-popup="{{vm.popup}}"
datepicker-options="vm.options"
datepicker-popup-template-url="{{vm.popupUrl}}"
placeholder="mm/dd/yyyy" ng-pattern="/^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(20)\d{2}$/"
autocomplete="off"
ng-model="vm.ngModel"
is-open="vm.isOpen"
show-button-bar="false"
ng-change="vm.applyDateFilter(vm.ngModel)"
ng-click="vm.ngClick($event)"
minDate="vm.minDate"
maxDate="vm.maxDate"
/>
</div>
html where I used the directive.
<!-- FROM DATE -->
<date-filter
popup="$ctrl.formats"
options="$ctrl.dateOptions"
ng-model="$ctrl.fromDate"
ng-change="$ctrl.filterFunction"
name="customFromDate"
label="$ctrl.fromVerbiage"
min-date="$ctrl.dateOptions.minDate"
max-date="$ctrl.dateOptions.maxDate"
my-id="customFromDate"
update-query="$ctrl.updateQuery"
query="customFromDate"
popup-url="$ctrl.popupTpl"
></date-filter>
<div> <!-- TO DATE -->
<date-filter
popup="$ctrl.formats"
options="$ctrl.dateOptions"
ng-model="$ctrl.toDate"
ng-change="$ctrl.filterFunction"
name="customToDate"
label="$ctrl.toVerbiage"
min-date="$ctrl.dateOptions.minDate"
max-date="$ctrl.dateOptions.maxDate"
my-id="customToDate"
update-query="$ctrl.updateQuery"
query="customToDate"
popup-url="$ctrl.popupTpl"
></date-filter>
</div>

Transforming Bootstrap datepicker directive to AngularJS Material

I have beginner knowledge of angularjs and am having problems transforming a Datepicker directive that uses a Bootstrap template to AngularJS Material. The original Bootstrap template looks like this:
template:
'<div ng-class="{\'input-group\': !snDisabled, \'has-error\': isInvalid}" style="width: 100%;">' +
'<input type="text" name="{{field.name}}" class="form-control" placeholder="{{field.placeholder}}" ng-model="formattedDate" ng-model-options="{updateOn: \'blur\', getterSetter: true}" ng-disabled="snDisabled" />' +
'<span class="input-group-btn" ng-hide="snDisabled">' +
'<input type="hidden" class="datepickerinput" ng-model="formattedDate" ng-readonly="true" />' +
'<button class="btn btn-default" type="button">' +
'<glyph sn-char="calendar" />' +
'</button>' +
'</span>' +
'</div>',
However, I've updated it to AJS Material:
template: '<div ng-class="{\'input-group\': !snDisabled, \'has-error\': isInvalid}" style="width: 100%;">' +
'<md-datepicker md-open-on-focus name="{{field.name}}" md-placeholder="{{field.placeholder}}" ng-model="formattedDate" ng-model-options="{updateOn: \'blur\', getterSetter: true}" ng-disabled="snDisabled"/>' +
'</div>',
When I load the page and check my console, I see an error "TypeError: Cannot read property 'setDate' of undefined"
Going back in the code, I realize that it calls upon an class element on the page input-group-btn, which doesn't appear in AngularJS Material:
link: function(scope, element, attrs, ngModel) {
var includeTime = scope.snIncludeTime;
var format;
format = includeTime ? dateTimeFormat.trim() : dateFormat.trim();
format = format.replace(/y/g, 'Y').replace(/d/g, 'D').replace(/a/g, 'A');
var dp = element.find('.input-group-btn').datetimepicker({
keepInvalid: true,
pickTime: scope.snIncludeTime === true,
format: "X"
}).on('dp.change', onDpChange);
function onDpChange(e) {
scope.formattedDate(e.date.format(format));
if (!scope.$root.$$phase)
scope.$apply();
}
function validate(formattedDate) {
scope.isInvalid = false;
if (formattedDate == null || formattedDate == '') {
dp.data('DateTimePicker').setValue(new Date());
return '';
}
if (isValidDate(formattedDate, format)) {
dp.data('DateTimePicker').setDate(moment(formattedDate, format));
} else if (isValidDate(formattedDate, moment.ISO_8601)) {
var date = moment.utc(formattedDate).clone().local();
dp.data('DateTimePicker').setDate(date);
formattedDate = date.format(format);
} else {
scope.isInvalid = true;
}
return formattedDate;
}
if (ngModel) {
ngModel.$parsers.push(validate);
ngModel.$render = function() {
validate(ngModel.$viewValue);
};
scope.formattedDate = function(formattedValue) {
if (angular.isDefined(formattedValue)) {
ngModel.$setViewValue(formattedValue);
if (scope.snChange) scope.snChange({
newValue: formattedValue
});
}
return ngModel.$viewValue;
};
} else {
scope.formattedDate = function(formattedValue) {
if (angular.isDefined(formattedValue)) {
scope.field.value = validate(formattedValue);
if (scope.snChange) scope.snChange({
newValue: formattedValue
});
}
return scope.field.value;
};
scope.$watch('field.value', function(newValue, oldValue) {
if (newValue != oldValue)
validate(newValue);
});
}
scope.$on('$destroy', function() {
dp.off('dp.change', onDpChange);
});
}
};
}
What do I have to modify in the code above in order for my AngularJS Material template to work without any errors?
you can use this jquery plugin instead of Bootstrap, it's very compatible with Angular
https://jqueryui.com/datepicker/
I recommend it as i always use it on my Angular projects.
Check out the documentation here and how to use:
http://api.jqueryui.com/datepicker/

google-place autocomplete directive does not work when its used twice on a single page

I am using a google-place directive to fill the location. The directive I am using is mentioned below:
app.directive('googlePlaces', function(){
return {
restrict:'E',
replace:true,
scope: {location:'='},
template: '<input id="google_places_ac" name="google_places_ac" type="text" class="form-control area_input transition" />',
link: function($scope, elm, attrs){
if(attrs.city =='cities'){
var options = {
types: ['(cities)'],
componentRestrictions: {country: 'IN'}
}
}
else{
var options = {
componentRestrictions: {country: 'IN'}
}
}
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], options);
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
if(attrs.city =='cities'){
$scope.location = $("#google_places_ac")[0].value + '"' +place.geometry.location.lat() + '"' + place.geometry.location.lng();
}
else{
$scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
}
$scope.$apply();
});
}
};
});
<google-places class="form-control location_field" location="location" city='cities' latlngs = "latlngs" ng-model="chosenPlace"></google-places>
But since I need to use it two times on a single page, the second directive does not work, i.e. it does not show autocomplete results.
Could anyone please help me fix this issue. Help would be much appreciated.
Thanks a lot for the reply.. I solved the issue, by replacing id(#google_places_ac) with 'elm[0]', where 'ele' is the element parameter passed in the link function, it refers to current element.

Angular Number Picker Directive Expression is undefined

I've read a few questions having to do with this topic and cannot figure out what I'm missing in my own direcitve.
angular.module('app')
.directive('numberPicker', [NumberPicker]);
function NumberPicker () {
var getTarget, getType;
getTarget = function (e) { return angular.element(e.target); }
getType = function (e) { return getTarget(e).attr('direction-type'); }
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
value: '='
},
template: '<div class="ui action input">' +
'<input value="{{value}}" type="text" />' +
'<button class="ui icon button" type="button" direction-type="up" ng-class="{disabled : canUp === false}">' +
'<i class="angle up icon" direction-type="up"></i>' +
'</button>' +
'<button class="ui icon button" type="button" direction-type="down" ng-class="{disabled : canDown === false}">' +
'<i class="angle down icon" direction-type="down"></i>' +
'</button>' +
'</div>',
controller: function ($scope) {},
link: function (scope, element, attrs, ctrl) {
scope.value = 0;
var options = {
min: 0,
max: 10,
step: 1
};
scope.$watch('value', function (newValue) {
scope.canDown = newValue > options.min;
scope.canUp = newValue < options.max;
if (ctrl.$viewValue != newValue) {
ctrl.$setViewValue(newValue);
}
});
var changeNumber = function (event) {
var type = getType(event);
if ('up' === type) {
if (scope.value >= options.max) {
return;
}
scope.value += options.step;
}
if ('down' === type) {
if (scope.value <= options.min) {
return;
}
scope.value -= options.step;
}
}
var btn = element.find('button');
var input = element.find('input');
btn.on('click', function (e) {
scope.$apply(function () {
changeNumber(e);
});
e.preventDefault();
});
input.on('change', function (e) {
scope.value = input[0].value;
scope.$apply();
})
scope.$on('$destroy', function () {
btn.off('touchstart touchend click')
});
}
}
}
The purpose of this was to create a number picker form element for Semantic UI. It was working perfectly a few days ago. And this error is so vague I can't even process where to start. Did I mention I am an Angular noob?
The error is :
Error: [$compile:nonassign] Expression 'undefined' used with directive 'numberPicker' is non-assignable!
How do you use the directive?
According to the definition you need to have both attributes "value" and "ng-model" set.
For example:
<number-picker value="xyz" ng-model="abc"></number-picker>
The error "Expression 'undefined' used with directive..." is normally thrown if one of the scope values is not set.

Angular.js: choosing a pre-compiled template depending on a condition

[disclaimer: I've just a couple of weeks of angular behind me]
In the angular app I'm trying to write, I need to display some information and let the user edit it provided they activated a switch. The corresponding HTML is:
<span ng-hide="editing" class="uneditable-input" ng:bind='value'>
</span>
<input ng-show="editing" type="text" name="desc" ng:model='value' value={{value}}>
where editing is a boolean (set by a switch) and value the model.
I figured this is the kind of situation directives are designed for and I've been trying to implement one. The idea is to precompile the <span> and the <input> elements, then choose which one to display depending on the value of the editing boolean. Here's what I have so far:
angular.module('mod', [])
.controller('BaseController',
function ($scope) {
$scope.value = 0;
$scope.editing = true;
})
.directive('toggleEdit',
function($compile) {
var compiler = function(scope, element, attrs) {
scope.$watch("editflag", function(){
var content = '';
if (scope.editflag) {
var options='type="' + (attrs.type || "text")+'"';
if (attrs.min) options += ' min='+attrs.min;
options += ' ng:model="' + attrs.ngModel
+'" value={{' + attrs.ngModel +'}}';
content = '<input '+ options +'></input>';
} else {
content = '<span class="uneditable-input" ng:bind="'+attrs.ngModel+'"></span>';
};
console.log("compile.editing:" + scope.editflag);
console.log("compile.attrs:" + angular.toJson(attrs));
console.log("compile.content:" + content);
})
};
return {
require:'ngModel',
restrict: 'E',
replace: true,
transclude: true,
scope: {
editflag:'='
},
link: compiler
}
});
(the whole html+js is available here).
Right now, the directive doesn't do anything but output some message on the console. How do I replace a <toggle-edit ...> element of my html with the content I define in the directive? If I understood the doc correctly, I should compile the content before linking it: that'd be the preLink method of the directive's compile, right ? But how do I implement it in practice ?
Bonus question: I'd like to be able to use this <toggle-edit> element with some options, such as:
<toggle-edit type="text" ...></toggle-edit>
<toggle-edit type="number" min=0 max=1 step=0.01></toggle-edit>
I could add tests on the presence of the various options (like I did for min in the example above), but I wondered whether there was a smarter way, like putting all the attrs but the ngModel and the editflag at once when defining the template ?
Thanks for any insight.
Here is a tutorial by John Lindquist that shows how to do what you want. http://www.youtube.com/watch?v=nKJDHnXaKTY
Here is his code:
angular.module('myApp', [])
.directive('jlMarkdown', function () {
var converter = new Showdown.converter();
var editTemplate = '<textarea ng-show="isEditMode" ng-dblclick="switchToPreview()" rows="10" cols="10" ng-model="markdown"></textarea>';
var previewTemplate = '<div ng-hide="isEditMode" ng-dblclick="switchToEdit()">Preview</div>';
return{
restrict:'E',
scope:{},
compile:function (tElement, tAttrs, transclude) {
var markdown = tElement.text();
tElement.html(editTemplate);
var previewElement = angular.element(previewTemplate);
tElement.append(previewElement);
return function (scope, element, attrs) {
scope.isEditMode = true;
scope.markdown = markdown;
scope.switchToPreview = function () {
var makeHtml = converter.makeHtml(scope.markdown);
previewElement.html(makeHtml);
scope.isEditMode = false;
}
scope.switchToEdit = function () {
scope.isEditMode = true;
}
}
}
}
});
You can see it working here: http://jsfiddle.net/moderndegree/cRXr6/

Resources