I am creating a form with multiple angular-ui datepickers and some input data.
For the datepickers I have created a controller and a parent form controller like the sample given below. The form controller has the model which includes the datepicker dates.
JS:
var app = angular.module('app', ['ui.bootstrap']);
app.controller('dateCntrl', function($scope,$timeout){
$scope.open = function() {
$timeout(function() {
$scope.opened = true;
});
};
});
app.controller('formCntrl', function($scope, $http){
$scope.model = {name:'', startDate:'', endDate:''};
});
HTML:
<form ng-controller="formCntrl">
<input type="text" id="name" placeholder="Name" ng-model="model.name" />
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="model.startDate" id="startDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="model.endDate" id="endDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
</form>
Am I going the right way in having a separate controller for the datepicker. This will act as a common controller for all the date inputs
If yes, is it possible to have a generic way of binding the data in the datepicker controller back to the model dates(model.startDate,model.endDate in this case) in the parent controller.
Is there a alternative way to go about this.
Thanks and regards.
Should have read more about the scope inheritance
The parent scope values can be accessed using $parent
<form ng-controller="formCntrl">
<input type="text" id="name" placeholder="Name" ng-model="model.name" />
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="$parent.model.startDate" id="startDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="$parent.model.endDate" id="endDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
</form>
I took all the code from the verbose example here: http://angular-ui.github.io/bootstrap/#/datepicker & wrapped it into my own directive. This way I can just drop unlimited datepickers into my page, and specify the model for each one to bind to. I do not have to manage passing repetitive settings, or setting up unique variables for tracking the "open" status anymore, I just put 1 line of code:
<div my-date-picker my-date-picker-model="myDate1"></div>
<div my-date-picker my-date-picker-model="myDate2"></div>
<div my-date-picker my-date-picker-model="myDate3"></div>
The user can then toggle each date picker open/closed, and the values will be updated into myDate1, myDate2, & myDate3 appropriately. The open/closed status is now encapsulated within the directive, and out of mind.
To implement the directive, I copied the 'JS' tab's code into it's controller, and I copied the 'Markup' tab's code into it's template. At the end I added 1 bit of code to update the value to the parent scope:
$scope.$watch('dt', function(newVal, oldVal) {
$scope.myDatePickerModel = newVal;
});
At the start of the controller, I changed $scope.today to initialize the value from the parent scope, instead of using the system clock:
$scope.init = function() {
$scope.dt = $scope.hxDatePickerModel;
};
$scope.init();
The directive uses an isolate scope, and 2-way binding on the attribute which defines the parent scope's model:
scope: {
myDatePickerModel: '='
}
Here's the full code of the directive:
app.directive('myDatePicker', function() {
function link(scope, element, attrs) {
}
function controller($scope) {
$scope.init = function() {
$scope.dt = $scope.myDatePickerModel;
};
$scope.init();
$scope.clear = function () {
$scope.dt = null;
};
// Disable weekend selection
$scope.disabled = function(date, mode) {
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
};
$scope.toggleMin = function() {
$scope.minDate = $scope.minDate ? null : new Date();
};
$scope.toggleMin();
$scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened = true;
};
$scope.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
$scope.format = $scope.formats[0];
$scope.$watch('dt', function(newVal, oldVal) {
$scope.myDatePickerModel = newVal;
});
}
return {
restrict: 'A',
templateUrl: 'datepicker.html',
link: link,
controller: controller,
scope: {
myDatePickerModel: '='
}
}
});
And here is the full code of datepicker.html, the template for this directive:
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
other solution , declare the datepicker's open() method in $rootScope and it is available entire application .
Html :
<div ng-app="myApp">
<div ng-controller="DemoController">
<div>
<input type="text" name="salesEndDate" id = "salesEndDate" datepicker-popup="dd-MM-yyyy" ng-model="salesEndDate" datepicker-options="dateOptions"/>
<button id="salesEndDateCal" ng-click="datePickerOpen('salesEndDate')"><i class="icon-calendar"></i></button>
</div>
<div>
<input type="text" name="salesStartDate" id = "salesStartDate" datepicker-popup="dd-MM-yyyy" ng-model="salesStartDate" datepicker-options="dateOptions"/>
<button id="salesEndDateCal" ng-click="datePickerOpen('salesStartDate')"><i class="icon-calendar"></i></button>
</div>
</div>
</div>
Javascript :
var myApp = angular.module('myApp',['ui.bootstrap','ui.bootstrap.datepicker']);
function DemoController($scope,$timeout,$rootScope) {
$rootScope.datePickerOpen = function(id) {
$timeout(function() {
$("#"+id).focus();
});
};
}
jsfiddle link http://jsfiddle.net/angles_k/s7yZm/21/
Related
This is a typical example of the use of ng-messages in AngularJS (1.x):
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required>
<div ng-messages="demoForm.amount.$error">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
see: http://jsfiddle.net/11en8swy/3/
I now want to change this example so the "This field is required" error only shows when the field is touched ($touched) or the user hits the submit button.
I cannot use the ng-submitted class on the form since the validation error prevents the submitting of the form.
How should I do this?
Thanks
You can do this using ng-show:
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched">
<div ng-message="required">This field is required</div>
</div>
And use a custom directive. See a working demo:
var app = angular.module('app', ['ngMessages']);
app.controller('mainCtrl', function($scope) {
});
app.directive('hasFocus', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
element.on('focus', function() {
$timeout(function() {
ctrl.hasFocusFoo = true;
})
});
element.on('blur', function() {
$timeout(function() {
ctrl.hasFocusFoo = false;
})
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-messages.js"></script>
<body ng-app="app" ng-controller="mainCtrl">
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100" required has-focus>
<div ng-messages="demoForm.amount.$error" ng-show="demoForm.amount.$touched || demoForm.amount.hasFocusFoo">
<div ng-message="required">This field is required</div>
</div>
<button type="submit">test submit</button>
</form>
</body>
The directive is basically setting another hasFocusFoo field on the ngModel controller then we can easily use that directive.
Ah, at the PC at last.
https://plnkr.co/edit/EX3UmoAOKmTKlameBXRa?p=preview
<form name="mc.form">
<input type="text" name="empty" ng-model="mc.empty" required />
<label ng-show="mc.form.empty.$dirty && mc.form.empty.$error.required">i'm empty</label>
</form>
MainController.$inject = ['$timeout'];
function MainController($timeout) {
var vm = this;
$timeout(function(){
vm.form.$setPristine();
});
vm.submit = function(){
if(vm.form.$valid){
alert('yay');
}else{
(vm.form.$error.required || []).forEach(function(f){
f.$dirty = true;
});
}
}
}
Here is how I handle this task in my solution. form.$setPristine() - sets the field in a pristine state, so field isn't $dirty and error hidden. But after submit I manually state required fields in a $dirty state, so errors become visible. + if you type something, and delete it after, the error would be visible without submitting a form.
This is my directive code:
'use strict';
demo.directive('myModal', function($parse) {
return {
restrict: 'A',
transclude: true,
scope: '#',
template: '<div ng-transclude><h4>Please enter value</h4></div>'
}
});
Usage is as follows:
<!-- myModal directive -->
<div my-modal>
<input type="text" ng-model="myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>
And my main controller, which wraps the whole application, includes this:
demo.controller('MainCtrl', function($scope) {
$scope.getMyTest = function(){
alert($scope.myTest);
}
});
Any ideas why can't I access myTest?
JsFiddle: http://jsfiddle.net/sZZEt/679/
You should use the dot-notation:
demo.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.getMyTest = function(){
alert($scope.data.myTest);
}
});
and
<div my-modal>
<input type="text" ng-model="data.myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>
JSFIDDLE
Transclusion creates a child scope, that's why you should use the dot-notation for ng-model.
try this. add directive to model element.
<div>
<input my-modal type="text" ng-model="myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>
I am trying to modify a form in a view by means of a button in my directive's template (template is in another file), this is for a basic CRUD where each item has a delete/edit button.
In order to avoid replicating the form I decided to that on edit's click a function would send the item to the controller in questions in order to be updated with the new information.
But I've been having troubles making the connection, so far I tried changing $root, to $rootScope back and forth and using , $broadcast or $emit.
So how can I send the function onChange my item's information based on the template's button click?
Template:
<strong>{{item.type}}</strong> {{item.description}}
<div class="material-switch pull-right">
<button type="button" class="btn btn-warning btn-circle" ng-show="item.editable" ng-click="onChange()">
<span class="glyphicon glyphicon-edit" ></span>
</button>
<button type="button" class="btn btn-danger btn-circle" ng-controller="View1Ctrl" ng-show="item.editable" ng-click="EliminarItem(item)">
<span class="glyphicon glyphicon-minus" ></span>
</button>
<input ng-model="item.isDone"
id="someSwitchOptionDefault{{itemIndex}}"
name="someSwitchOption001{{itemIndex}}"
type="checkbox" />
<label for="someSwitchOptionDefault{{itemIndex}}" class="label-info"></label>
</div>
Directive:
'use strict';
angular.module('myApp.items.directive', [])
.directive('itemSwitch', [ function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
templateUrl: 'templates/itemSwitchTemplate.html',
link : function($scope){
$scope.$broadcast('onChange', item);
}
}
}]);
Controller
.controller('View1Ctrl', ['$scope','itemsService',function($scope,itemsService) {
$scope.items = itemsService.getItems();
$scope.classMap = {GROCERIES:"success",CAR:"danger",UNIVERSITY:"warning",PAYMENTS:"info"};
$scope.newItem = {};
$scope.$on('onChange', function(event, args) {
if ($scope.btnEdit) {
$scope.newItem = args;
} else {
$scope.newItem = {};
}
});
$scope.enableEdit = function (item) {
item.editable = true;
};
$scope.disableEdit = function (item) {
item.editable = false;
};
}]);
View
<div class="col-xs-12">
<div ng-model="currentItem" ng-repeat="item in items" item-switch item="item" item-index="$index" class="notice notice-{{classMap[item.type]}}" ng-mouseover="enableEdit(item)" ng-mouseleave="disableEdit(item)">
</div>
<!-- FORMULARIO -->
<form name = "myForm" class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Task</legend>
</div>
<div class="control-group">
<!-- Name-->
<label class="control-label">Name</label>
<div class="controls">
<input type="text" name="itemName" ng-model="newItem.name" placeholder="Task Name" class="input-xlarge" ng-required="true" >
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Description -->
<label class="control-label">Description</label>
<div class="controls" >
<input type="text" ng-model="newItem.description" placeholder="Task Description" class="input-xlarge">
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<a class="btn icon-btn btn-success" ng-disabled="myForm.$invalid" ng-click="addOrSaveItem()">
<span class="glyphicon btn-glyphicon glyphicon-save img-circle text-success"></span>Save</a>
</div>
</div>
</fieldset>
</form>
</div>
FiddleJS
Look nd Feel
Using "onChange" as an event name is a poor choice as it is likely to conflict with other events with that name. My recommendation is to use the directive's name as part of the event name.
In your directive
angular.module('myApp.items.directive', [])
.directive('itemSwitch', function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
template: '<button ng-click="doIt()">Do It</button>',
link : function(scope){
scope.doIt = function() {
scope.$emit('itemSwitch.doIt', scope.item, scope.itemIndex);
};
}
}
});
In your controller
$scope.doItItems = [];
$scope.$on("itemSwitch.doIt", function(item, itemIndex) {
doItItems.push(item);
});
In this example, on each click of the Do It button, an item is pushed to the doItItems list.
In your itemSwitch directive, you can do
$rootScope.$broadcast('onChange', item);
And then you can pick it up in any scope that is listening (in this case, your controller), with
$scope.$on('onChange', function(event, args) { ... }
This works because $broadcast moves downward from parent to children, while $emit moves upward from child to parents.
So for example, $rootScope.$emit would only be picked up by $rootScope.$on since $rootScope has no parents, while $scope.$broadcast would only be available to that scope's children and not to $rootScope.$on, since $rootScope is not a child of $scope
For few days I was trying to debug why my directive doesn't work. No event has been fired when I press a button. Finally I found which line breaks everything!
Inside template html I have line datepicker-popup ng-model="{{model}}" min-date="{{minDate}}" is-open="{{isOpened}}" if I remove it everything works well. But it is essential part in my custom directive and I want to keep it. I assume problem is that I am using directive inside custom directive?
Could you please help me to identify problem and find a correct solution?
Thanks for any help!
Directive:
(function(){
function directive(){
return {
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened'
},
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html',
controller: 'Ctrl'
};
};
app.directive('myDirective', directive);
})();
controller:
(function(){
function Controller($scope) {
$scope.open = function() {
alert('HELLO');
};
app.controller('Ctrl', Controller);
})();
template html:
<fieldset>
<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="open()" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
As mentioned in the other answer, you don't need the curly braces in your template because it's a two way binding.
The problem with your click function is probably the isolated scope of your custom directive.
If you'd like to have the open method in your main controller you could pass it to the isolated scope.
Please find below a demo of your directive or here at jsfiddle.
angular.module('demoApp', ['ui.bootstrap'])
.controller('mainController', Controller)
.directive('customDir', Directive);
function Directive() {
return {
scope: {
model: '=',
minDate: '=',
isOpened: '='
},
transclude: true,
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html',
//controller: 'Ctrl'
controller: function($scope) {
$scope.open = function() {
console.log('open popup now!!');
$scope.isOpened = true;
};
}
};
}
function Controller($scope) {
$scope.open = function () {
alert('HELLO'); // not called becasue of isolated scope of custom directive
};
$scope.dateModel = {
date: new Date(),
min: new Date()
};
$scope.isOpened = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap-tpls.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<div ng-app="demoApp" ng-controller="mainController">
<script type="text/ng-template" id="templates/datepicker/datepicker.html">
<fieldset>
<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="open()" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
</script>
<custom-dir model="dateModel.date" min-date="dateModel.min" is-opened="isOpened"></custom-dir>
</div>
When you define your directive's scope properties with a = symbol, you don't need to use {{}} in your views.
Remove the {{}} from your view:
<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" />
Here's more info regarding directive's scope properties using =, & and #.
I am building an application with Angular.js and Twitter Bootstrap.
HTML:
<div ng-controller="myController">
<label for="event">Event</label>
<input type="text" ng-model="event"/>
<label for="eventdate">Date</label>
<div class="input-append date" id="datepicker" data-date-format="dd-mm-yyyy">
<input class="span2" size="20" type="text" id="datepicker" ng-model="eventdate" required>
<span class="add-on"><i class="icon-th"></i></span>
<input class="btn-primary" type="submit" id="submit" value="Submit" ng-click="submit()" />
</div>
Controller:
var myApp1 = angular.module('myApp1', []);
myApp1.controller('myController', function($scope) {
$('#datepicker').datepicker();
$scope.submit = function () {
console.log($scope.event);
console.log($scope.eventdate);
};
});
When I click "Submit" button,
console.log($scope.event); prints the data entered in event text box.
But console.log($scope.eventdate); prints "undefined" when I select a date from the date picker
What may be the reason?
Please advice.
Your bootstrap datepicker is a 3rd-component outside of angular. When you change your date from the datepicker, angular is not aware of the changes.
You have to write a custom directive like this:
app.directive('datepicker', function() {
return {
restrict: 'A',
// Always use along with an ng-model
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$render = function() { //This will update the view with your model in case your model is changed by another code.
element.datepicker('update', ngModel.$viewValue || '');
};
element.datepicker().on("changeDate",function(event){
scope.$apply(function() {
ngModel.$setViewValue(event.date);//This will update the model property bound to your ng-model whenever the datepicker's date changes.
});
});
}
};
});
Apply the directive to html:
<div class="input-append date" datepicker ng-model="eventdate" data-date-format="dd-mm-yyyy">
<input class="span2" size="20" type="text" required="" />
<span class="add-on">
<i class="icon-th"></i>
</span>
</div>
DEMO