So I have a custom directive which works fine as it is. This directive is being used at multiple places. This is an element directive.
This element directive has certain attributes. I have added a custom attribute for only 1 instance of this directive i.e. only at 1 particular usage of this directive I have added an extra attribute for this element.
Here is the directive being used in the HTML:
<attribute-types target-model="patient" attribute="::attribute"
field-validation="::fieldValidation"
is-auto-complete="isAutoComplete"
get-auto-complete-list="getAutoCompleteList"
get-data-results="getDataResults" is-read-only="isReadOnly"
handle-update="handleUpdate" validate-autocomplete="true">
</attribute-types>
The validate-autocomplete is the extra attribute I have used at 1 place use of this directive.
Here is the template for the directive:
<div class="left" data-ng-switch-when="org.openmrs.Concept" ng-if="attribute.name == 'PATIENT_OCCUPATION'" style="position: absolute">
<input type="text"
class="ui-autocomplete-input"
id="{{::attribute.name}}"
name="{{::attribute.name}}"
ng-model="targetModel[attribute.name].value"
ng-keyup="suggest(targetModel[attribute.name])"
ng-required="{{::attribute.required}}">
<ul class="ui-front ui-autocomplete ui-menu ui-widget ui-widget-content ui-corner-all" ng-if="showTag" ng-hide="hideList"
style="position:absolute; top:30px; width:192px">
<li class="ui-menu-item" role="presentation" ng-repeat="info in filterOcuppation"
ng-click="hideSuggestions(info)">
<a class="ui-corner-all" tabindex="-1">{{info.description}}</a>
</li>
</ul>
</div>
And this is the directive definition:
angular.module('bahmni.common.attributeTypes', [])
.directive('attributeTypes', [function () {
var link = function (scope, element, attrs, ngModelCtrl) {
var formElement = element[0];
if (attrs.validateAutocomplete) {
ngModelCtrl.$setValidity('selection', true);
}
};
return {
link: link,
scope: {
targetModel: '=',
attribute: '=',
fieldValidation: '=',
isAutoComplete: '&',
handleLocationChange: '&',
handleSectorChange: '&',
getAutoCompleteList: '&',
getDataResults: '&',
handleUpdate: '&',
isReadOnly: '&',
isForm: '=?'
},
templateUrl: '../common/attributeTypes/views/attributeInformation.html',
restrict: 'E',
controller: function ($scope) {
var dateUtil = Bahmni.Common.Util.DateUtil;
$scope.getAutoCompleteList = $scope.getAutoCompleteList();
$scope.getDataResults = $scope.getDataResults();
$scope.today = dateUtil.getDateWithoutTime(dateUtil.now());
// to avoid watchers in one way binding
$scope.isAutoComplete = $scope.isAutoComplete() || function () { return false; };
$scope.isReadOnly = $scope.isReadOnly() || function () { return false; };
$scope.handleUpdate = $scope.handleUpdate() || function () { return false; };
$scope.handleLocationChange = $scope.handleLocationChange() || function () { return false; };
$scope.handleSectorChange = $scope.handleSectorChange() || function () { return false; };
$scope.suggestions = $scope.attribute.answers;
$scope.showTag = false;
$scope.itisinvalid = true;
$scope.appendConceptNameToModel = function (attribute) {
var attributeValueConceptType = $scope.targetModel[attribute.name];
var concept = _.find(attribute.answers, function (answer) {
return answer.conceptId === attributeValueConceptType.conceptUuid;
});
attributeValueConceptType.value = concept && concept.fullySpecifiedName;
};
$scope.suggest = function (string) {
$scope.hideList = false;
$scope.showTag = true;
var output = [];
angular.forEach($scope.suggestions, function (suggestion) {
if (suggestion.description.toLowerCase().indexOf(string.value.toLowerCase()) >= 0) {
output.push(suggestion);
}
});
$scope.filterOcuppation = output;
};
$scope.hideSuggestions = function (object) {
$scope.targetModel[$scope.attribute.name] = object;
$scope.targetModel[$scope.attribute.name].value = object.description;
$scope.targetModel[$scope.attribute.name].conceptUuid = object.conceptId;
$scope.hideList = true;
};
}
};
}]);
When running this I get TypeError: ngModelCtrl.$setValidity is not a function
What I'm basically doing it validating whatever is entered into the input text is valid or not. For that I would also need the ng-model, how would I access that in my link function?
If I have written some wrong, feel free to correct me. I'm still in the process of learning AngularJS
You should use directive like that:
directive('attributeTypes', [function() {
return {
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
...
ngModel.$setValidity(...
Related
I am making a custom directive on top of md-select. I am having issues with default behavior of ngModel and ngChange. I can't seem to make them both work together.
Currently I have this http://next.plnkr.co/edit/X34DUWtkyYhbwJP4?open=lib%2Fscript.js
The ngModel is being updated, but the ngChange doesnt seem to work.
I also tried a method shown in http://embed.plnkr.co/HZAHSyi9L8UQdE24zYYI/
but having issues when setting value with a timeout (assuming value comes from api).
app.controller("appCtrl", function($scope){
$scope.items = [1,2,3,4,5,6];
$scope.foo=2; // this works
$scope.bar = function(foo) {
$scope.aux = foo;
}
setTimeout(function(){
$scope.foo=5;
}, 0); // this doesnt work
});
I want to make these two attributes to work as default md-select does.
When working with ng-model and custom directives, you can specify ngModel as a require, and then automatically get access to other directives like ngChange and ngRequired. I've updated your plunkr: http://next.plnkr.co/edit/VzYpZ2elmzV6XkbM?open=lib
HTML
<md-custom-select
ng-model="vm.SelectItems"
ng-change="vm.onselectchange()"
list="vm.ItemList">
</md-custom-selector>
JavaScript
var app = angular.module("MaterialApp", ["ngMaterial"]);
app.directive("mdCustomSelect", ["$compile", mdCustomSelect]);
function mdCustomSelect($compile) {
return {
restrict: "E",
require: {
ngModelCtrl: '^ngModel'
},
scope: {
ngModel: "<",
list: "=",
options: "<",
},
replace: true,
link: function(scope, element, attrs, controllers) {
scope.ngModelCtrl = controllers.ngModelCtrl;
var searchTemplate = '<md-select-header aria-label="Select Header" class="demo-select-header"><input aria-label="InputSearchBox" ng-keydown="$event.stopPropagation()" ng-model="searchTerm" type="search" placeholder="Search items" class="md-text"></md-select-header>';
var selectAllTemplate = '<div style="padding: 0px 0px 15px 5px; background-color: #efefef;"><md-checkbox class="md-warn" title="Select All" ng-model="checkAllChecked" ng-change="toggleSelectAll()">Check/Uncheck All </md-checkbox></div>';
var multiSelectGroupTemplate = '<md-option ng-value="item.ItemID" ng-repeat="item in ItemList | filter: searchTerm">{{item.ItemName}}</md-option>';
var completeTemplate = "";
completeTemplate += '<md-select multiple ng-model="ngModel" ng-change="valChanged()" data-md-container-class="selectdemoSelectHeader">';
completeTemplate += searchTemplate; //2 begin and end
completeTemplate += selectAllTemplate; //3 begin and end
completeTemplate += multiSelectGroupTemplate; //4 begin and end
completeTemplate += " </md-select>"; //1 end
element.html(completeTemplate);
$compile(element.contents())(scope);
},
controller: ["$scope", function($scope) {
var defaultValueProperty = ($scope.options == undefined || $scope.options.Value === undefined) ? "value" : $scope.options.Value;
var defaultTextProperty = ($scope.options == undefined || $scope.options.Text === undefined) ? "name" : $scope.options.Text;
$scope.isMultipleSelected = angular.isUndefined($scope.multiple) ? true : $scope.multiple;
$scope.checkAllChecked = false;
$scope.ItemList = [];
var rawItemList;
$scope.$watch("list", function(newValue) {
$scope.ItemList = newValue.map(item => {
return { ItemID: item[defaultValueProperty], ItemName: item[defaultTextProperty] };
});
}, true);
$scope.valChanged = function(){
$scope.ngModelCtrl.$setViewValue($scope.ngModel);
}
$scope.toggleSelectAll = function() {
if ($scope.checkAllChecked == false) {
$scope.ngModelCtrl.$setViewValue([]);
} else {
$scope.ngModelCtrl.$setViewValue($scope.ItemList.map(item => item.ItemID));
}
};
}]
};
}
I'm currently trying to pass a validation directive to a custom element directive. But I'm struggling to make it work since it should receive model as an input while I am using bind to controller.
I have to premise that I cannot upgrade to a more recent version of Angular, so 1.5 is the limitation, together with the fact I cannot edit validation directive.
I thought transclude would have helped but with directive attribute it looks not so promising.
What the following code should do is to validate vm.model on input element.
Here's the HTML:
<body ng-controller="MainCtrl">
<div class="myClass">
<my-custom-directive data-placeholder="No text"
data-id="myModel.id"
data-model="myModel.text"
not-editable-directive-attribute >
</my-custom-directive>
</div>
</body>
And here the app.js:
var myTemplate = '<div class="myContainer">' +
'<input class="myInput"' +
' ng-mousedown="$event.stopPropagation();"' +
' ng-show="vm.isFocused"' +
' ng-model="vm.model"' +
' ng-change="vm.onChange()"' +
' type="text">' +
'<span ng-show="!vm.isFocused">{{vm.model}}</span>' +
'<span ng-show="!vm.isFocused && !vm.model && vm.placeholder">{{vm.placeholder}}</span>' +
'</div>';
app.controller('MainCtrl', function($scope) {
$scope.myModel = {
id: 'test',
text: 'this is text'
};
});
app.directive('myCustomDirective', ['$timeout', function($timeout) {
return {
restrict: 'E',
replace: true,
template: myTemplate,
controllerAs: 'vm',
bindToController: {
id: '#',
model: '=',
onChange: '&',
placeholder: '#'
},
scope: {},
controller: angular.noop,
link: function(scope, element) {
var input = element.find('input')[0];
var spans = Array.from(element.find('span'));
var vm = scope.vm;
vm.isFocused = false;
vm.focus = function() {
vm.isFocused = true;
scope.$applyAsync(function() {
$timeout(function() {
input.focus();
input.select();
});
});
};
spans.forEach(span => span.addEventListener('click', vm.focus));
}
};
}]);
app.directive('notEditableDirectiveAttribute', [function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.myCustomDirectiveAttribute = function(modelValue, viewValue) {
if (viewValue) {
return viewValue.indexOf('e') < 0;
}
return false;
};
}
};
}]);
I've created a plunker to make it clearer:
http://plnkr.co/edit/auminr?p=preview
So clicking on span element i should be able to edit text and directive should validate it (in this specific case check if it contains letter "e").
Is it even possible or am I struggling against windmills?
One approach to adding directives to templates based on component attributes is to use the function form of the template property:
<my-custom-directive data-placeholder="No text"
model="vm.data"
custom="not-editable-directive-attribute" >
</my-custom-directive>
app.directive("myCustomDirective", function() {
return {
template: createTemplate,
scope: {},
//...
});
function createTemplate(tElem, tAttrs) {
var placeholder = tAttrs.placeholder;
var model = tAttrs.model;
var custom = tAttrs.custom;
return `
<input placeholder=${placeholder}
ng-model=${model}
${custom} />
`;
}
})
The createTemplate function copies attributes and used them in a template literal.
For more information, see
AngularJS Comprehensive Directive API - template
MDN JavaScript Reference - Template Literals
I would like to make a custom datepicker directive with a custom template.
But I have no idea how to start building it...
How to include date data for my directive?
I appreciate your guide or give me some advice to work on this more precisely.
It might help you! follow the below steps
HTML code sample:
<label>Birth Date</label>
<input type="text" ng-model="birthDate" date-options="dateOptions" custom-datepicker/>
<hr/>
<pre>birthDate = {{birthDate}}</pre>
<script type="text/ng-template" id="custom-datepicker.html">
<div class="enhanced-datepicker">
<div class="proxied-field-wrap">
<input type="text" ui-date-format="yy-mm-dd" ng-model="ngModel" ui-date="dateOptions"/>
</div>
<label>
<button class="btn" type="button"><i class="icon-calendar"></i></button>
<span class="datepicker-date">{{ngModel | date:'d MMM yyyy'}}</span>
</label>
</div>
</script>
JS code sample:
angular
.module('App',['ui.date'])
.directive('customDatepicker',function($compile){
return {
replace:true,
templateUrl:'custom-datepicker.html',
scope: {
ngModel: '=',
dateOptions: '='
},
link: function($scope, $element, $attrs, $controller){
var $button = $element.find('button');
var $input = $element.find('input');
$button.on('click',function(){
if($input.is(':focus')){
$input.trigger('blur');
} else {
$input.trigger('focus');
}
});
}
};
})
.controller('myController',function($scope){
$scope.birthDate = '2013-07-23';
$scope.dateOptions = {
minDate: -20,
maxDate: "+1M +10D"
};
});
/*global angular */
/*
jQuery UI Datepicker plugin wrapper
#note If ≤ IE8 make sure you have a polyfill for Date.toISOString()
#param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto uiDateConfig
*/
angular.module('ui.date', [])
.constant('uiDateConfig', {})
.directive('uiDate', ['uiDateConfig', '$timeout', function (uiDateConfig, $timeout) {
'use strict';
var options;
options = {};
angular.extend(options, uiDateConfig);
return {
require:'?ngModel',
link:function (scope, element, attrs, controller) {
var getOptions = function () {
return angular.extend({}, uiDateConfig, scope.$eval(attrs.uiDate));
};
var initDateWidget = function () {
var showing = false;
var opts = getOptions();
// If we have a controller (i.e. ngModelController) then wire it up
if (controller) {
// Set the view value in a $apply block when users selects
// (calling directive user's function too if provided)
var _onSelect = opts.onSelect || angular.noop;
opts.onSelect = function (value, picker) {
scope.$apply(function() {
showing = true;
controller.$setViewValue(element.datepicker("getDate"));
_onSelect(value, picker);
element.blur();
});
};
opts.beforeShow = function() {
showing = true;
};
opts.onClose = function(value, picker) {
showing = false;
};
element.on('blur', function() {
if ( !showing ) {
scope.$apply(function() {
element.datepicker("setDate", element.datepicker("getDate"));
controller.$setViewValue(element.datepicker("getDate"));
});
}
});
// Update the date picker when the model changes
controller.$render = function () {
var date = controller.$viewValue;
if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
}
element.datepicker("setDate", date);
};
}
// If we don't destroy the old one it doesn't update properly when the config changes
element.datepicker('destroy');
// Create the new datepicker widget
element.datepicker(opts);
if ( controller ) {
// Force a render to override whatever is in the input text box
controller.$render();
}
};
// Watch for changes to the directives options
scope.$watch(getOptions, initDateWidget, true);
}
};
}
])
.constant('uiDateFormatConfig', '')
.directive('uiDateFormat', ['uiDateFormatConfig', function(uiDateFormatConfig) {
var directive = {
require:'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var dateFormat = attrs.uiDateFormat || uiDateFormatConfig;
if ( dateFormat ) {
// Use the datepicker with the attribute value as the dateFormat string to convert to and from a string
modelCtrl.$formatters.push(function(value) {
if (angular.isString(value) ) {
return jQuery.datepicker.parseDate(dateFormat, value);
}
return null;
});
modelCtrl.$parsers.push(function(value){
if (value) {
return jQuery.datepicker.formatDate(dateFormat, value);
}
return null;
});
} else {
// Default to ISO formatting
modelCtrl.$formatters.push(function(value) {
if (angular.isString(value) ) {
return new Date(value);
}
return null;
});
modelCtrl.$parsers.push(function(value){
if (value) {
return value.toISOString();
}
return null;
});
}
}
};
return directive;
}]);
Here is a working fiddle http://jsfiddle.net/FVfSL/.
I'm using angular & trying to prevent the user from entering alphabet into text field and at the same item push the model onto array. But the logic doesn't seem to work perfectly and sometimes allows alphabets. How can I prevent special characters like $,% from being entered ?
HTML
<ul>
<li ng-repeat="item in arr track by $index">
<input type="text" number ng-change="itemChange()" ng-model="item" />
<button ng-click="add()">Add Item</button>
</li>
</ul>
JS
app.directive('number', function(){
return {
require: 'ngModel',
link: function(scope, elem, attr, ctrl){
elem.bind('keyup', function(e) {
console.log(e)
var text = this.value;
this.value = text.replace(/[a-zA-Z]/g,'');
});
}
};
})
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.arr = [];
//Initialize with one element
$scope.arr[0] = '';
//Push when there is a change in the input
$scope.itemChange = function() {
$scope.arr[this.$index] = this.item;
}
//Add an empty item at the end
$scope.add = function() {
$scope.arr[$scope.arr.length] = '';
}
});
Demo : http://plnkr.co/edit/t8OE5uJ578zgkiUTjHgt?p=preview
Try this:
app.directive('alphabetonly', function(){
return {
require: 'ngModel',
link: function(scope, elem, attr, ctrl){
ctrl.$parsers.push(function(inputValue) {
var transformedInput = inputValue.replace(/[^\w\s]/gi,'');
if (transformedInput != inputValue) {
ctrl.$setViewValue(transformedInput);
ctrl.$render();
}
return transformedInput;
});
}
};
})
See plunk.
I want to use a directive to customize my code.
I have created a button to switch isCollapsedUpload flag defined in the controller as: #scope.isCollapsedUpload=false.
When the user presses the button, the isCollapsedUpload turns to true or vice versa and the icon changes.
From the controller:
$scope.switcher = function (booleanExpr, trueValue, falseValue) {
return booleanExpr ? trueValue : falseValue;
}
$scope.isCollapsedUpload = false;
<button class="btn" ng-click="isCollapsedUpload = !isCollapsedUpload">
<span>Upload file</span>
<i class="{{ switcher( isCollapsedUpload, 'icon-chevron-right', 'icon-chevron-down' )}}"></i>
</button>
I wrote this directive:
feederliteModule.directive('collapseExtend', function() {
return {
restrict: 'E',
scope: { isCollapsed:'#collapseTarget' },
compile: function(element, attrs)
{
var htmlText =
'<button class="btn" ng-click="isCollapsed = !isCollapsed">'+
' <span>'+attrs.label+'</span>'+
' <i class="{{ switcher(isCollapsed, \'icon-chevron-right\', \'icon-chevron-down\' )}}"></i>'+
'</button>';
element.replaceWith(htmlText);
}
}
});
And now I can use it like:
<collapse-extend
collapse-target="isCollapsedUpload"
label="Upload file"
></collapse-extend>
It doesn't work. No icon changes. No errors,
isCollapsedUpload flag doesn't change. It changes only into directive
Did I miss something?
The reason the class doesn't change correctly is because you are not linking the template properly. This is easy to fix if you use the built in functionality:
var feederliteModule = angular.module('feederliteModule', []);
feederliteModule.directive('collapseExtend', [function() {
return {
restrict: 'E',
scope: {
isCollapsed:'=collapseTarget',
label: '#'
},
template: '<button class="btn" ng-click="isCollapsed = !isCollapsed">'+
'<span>{{ label }}</span>'+
'<i ng-class="{ \'icon-chevron-right\': isCollapsed, \'icon-chevron-down\': !isCollapsed }"></i>'+
'</button>'
}
}]);
feederliteModule.controller('test', ['$scope', function($scope) {
$scope.isCollapsedUpload = false;
}]);
To the best of my understanding, by replacing the parent element, you were removing the isolate scope this object was tied to without creating a new one on the button itself.
EDIT: See a complete working fiddle with multiple buttons
I suggest using a service instead of a controller to maintain your model data. This allows you better separation of concerns as your app gets more complex:
var feederliteModule = angular.module('feederliteModule', []);
feederliteModule.service('btnService', function(){
this.isCollapsedUpload = false;
this.isCollapsedSomething = false;
});
feederliteModule.controller('btnController', function($scope, btnService){
$scope.isCollapsedUpload = btnService.isCollapsedUpload;
$scope.isCollapsedSomething = btnService.isCollapsedSomething;
});
feederliteModule.directive('collapseExtend', function() {
return {
restrict: 'E',
scope: {
isCollapsed:'=collapseTarget',
label:'#'
},
replace: true,
link: function (scope, element, attrs){
scope.switcher = function (booleanExpr, trueValue, falseValue) {
return booleanExpr ? trueValue : falseValue;
};
scope.toggleCollapse = function() {
scope.isCollapsed = !scope.isCollapsed;
}
},
template: '<button class="btn" ng-click="toggleCollapse()">'+
'<span>{{label}}</span>'+
'<i ng-class="switcher(isCollapsed, \'icon-chevron-right\', \'icon-chevron-down\')"></i>'+
'</button>'
}
});
Also, notice that you must use '=' instead of '#' in order for isCollapsed to work as you expect. The answer above needs this as well.