I want to enable a button based on whether there's the atribute editable in the directive:
Editable:
<table-list items="users" editable></table-list>
Non-editable:
<table-list items="users"></table-list>
I tried this:
.directive('tableList', function ($attr) {
return {
restrict: 'EA',
template: '<a ng-if="editable" class="btn btn-default" href="#">Add to group</a>' +
'<table class="table table-stripped">' +
'<thead>' +
'<tr>' +
'<th>#</th>' +
'<th>Name</th>' +
'<th>Users</th>' +
'<th>Buildings</th>' +
'<th>Created at</th>' +
'</tr>' +
'</thead>' +
'<tbody>' +
'<tr ng-repeat="item in items">' +
'<th scope="row"><input type="checkbox" value="0"></th>' +
'<td>{{item.name}}</td>' +
'<td>_</td>' +
'<td>_</td>' +
'<td>_</td>' +
'</tr>' +
'</tbody>' +
'</table>',
scope: {
items: "=",
editable: "="
},
But the button isn't showing.
What's the proper way of doing this?
If you want to rely on a presence of the attribute only (even if it is set to false), you can check if it is defined with the $attrs in the link function:
scope: {
items: "="
},
link: function($scope, $element, $attrs) {
$scope.editable = $attrs.editable !== undefined;
}
JSFiddle.
However, this will not be reactive (i.e. if you add the attribute in runtime, directive won't show the button). You might use $attrs.$observe() if you need it.
Just add true:
<table-list items="users" editable="true"></table-list>
JSFiddle
When getting an attribute and setting it to the scope like that you're setting the value. And your editable-attribute has no value. If you'd do <table-list items="users" editable="true"></table-list> it would work better.
And in the directive you could probably use # instead of = for the attribute.
This will set the editable property on the scope to the string "true", if you want it to be a boolean you might need some logic in the link function
Related
I am using this custom template inside a directive to generate customized input fields with associated labels:
template: function(elem, attrs) {
var elemTpl = '<div> ' +
'<div class="form-group col-md-{{cols}}" >' +
'<label for="{{id}}" class="control-label input-sm">{{text}}</label> ' +
'<input type="{{type}}" ng-model="value" name="' + attrs.id + '" id="' + attrs.id +
'" class="form-control input-sm szpFocusable" placeholder="{{placeholder}}" ng-required="required" spellcheck="false"/>' +
'</div> ' +
'</div>';
return elemTpl;
}
The problem is that when the label is clicked, the input field is not being focused. When moving the code outside the directive and putting the input tags directly in the html, all works fine. The id and the name attributes are bound correctly to the appropriate scope fields.
Are you sure the ID you are using is unique?
Another option is to nest the <input> tag in the <label>, in that case you don't need the for argument:
var elemTpl = '<div> ' +
'<div class="form-group col-md-{{cols}}" >' +
'<label class="control-label input-sm">{{text}}' +
'<input type="{{type}}" ng-model="value" name="' + attrs.id + '" id="' + attrs.id + '" class="form-control input-sm szpFocusable" placeholder="{{placeholder}}" ng-required="required" spellcheck="false"/>' +
'</label>' +
'</div> ' +
'</div>';
Note:
The real answer is in the 2nd comment below: In a directive the content of element template is placed within the directive tag, in this case resulting in the id being used twice. This can be solved by adding replace : true to the return value of the directive.
This will replace your directive tag with the contents of your template.
app.directive('myDirective', function() {
return {
replace: 'true',
template: templateThatUsesId
};
});
Here there is a more accurate answer:
Attribute of label not working in angular directive?
When rendering directives with templates the container has the same id than the checkbox:
<div class="color-warning checkbox ng-isolate-scope ng-valid" ng-model="ckeck4" id="kitten>
<input id="kitten" type="checkbox">
<label for="kitten">CUSTOM LABEL</label>
</div>
The div that wrappers the rendered directive and the checkbox have the same id, not quite good.
In order to delete it we use a link function:
link: function(scope,el,attrs){
el.removeAttr("id")
}
This is because the link function is executed before angular renders the directive, so, we are removin the id from the div that wrappers the directive, here there is my complete code:
(function() {
'use strict';
angular.module('planetCheckbox', [])
.controller('PlanetCheckboxController', PlanetCheckboxController)
.directive('checkbox', DirectiveCheckbox);
function DirectiveCheckbox() {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '=',
classes: '#class',
customLabel: '#',
check: '=',
id:'#'//,
//radio :'#'
},
link: function(scope,el,attrs){
el.removeAttr("id")
},
controller: 'PlanetCheckboxController',
templateUrl: 'planet-checkbox/template.html'
}
};
PlanetCheckboxController.$inject = ['$scope'];
function PlanetCheckboxController($scope) {
$scope.ngModel = $scope.check;
};
angular.module('planetCheckbox')
.run(['$templateCache', function ($templateCache) {
$templateCache.put('planet-checkbox/template.html', '<div class="checkbox">'+
'<input id="{{id}}" type="checkbox" />' +
'<label for="{{id}}">CUSTOM LABEL</label>' +
'</div>'
);
}]);
})();
The living example is here: http://jsfiddle.net/cherniv/5u4Xp/
Label for is working for id and not for name
<label for="idxyz" class="control-label input-sm">{{text}}</label>
<input id="idxyz" type="{{type}}" ng-model="value" .....
It always works for me!
I have an AngularJS directive, which needs to be appended after an HTML element which called it. The elements structure can have many nested buttons on different levels of DOM structure.
Right now a directive gets appended in the wrong place instead of a container element that contains the button, which called the append function.
It looks like this:
The text should be appended after a button, which was clicked.
Directive:
app.directive('recursiveFields', function ($compile, $http) {
return {
scope: {
field: '=field',
model: '=model'
},
restrict: 'E',
replace: true,
controller: "httpPostController",
template: '<div ng-repeat="nestedField in field.nestedFields"><div ng-show="{{!nestedField.isEntity && !nestedField.isEnum}}">' + '<p ng-show={{nestedField.isRequired}}>{{nestedField.name}}*: </p>' + '<p ng-show={{!nestedField.isRequired}}>{{nestedField.name}}: </p>' + '<input type="text" ng-model="model[nestedField.name]" ng-change="getCreateEntityAsText()"' + 'class="form-control" placeholder="{{parseClassName(nestedField.type)}}">' + '</div>' + '<div ng-show="{{nestedField.isEnum}}">' + '<p ng-show={{nestedField.isRequired}}>{{nestedField.name}}*: </p>' + '<p ng-show={{!nestedField.isRequired}}>{{nestedField.name}}: </p>' + '<select ng-model="model[nestedField.name]" ng-change="getCreateEntityAsText()" class="form-control">' + '<option></option>' + '<option ng-repeat="enumValue in nestedField.enumValues" label={{enumValue.name}}>{{enumValue.ordinal}}</option>' + '</select>' + '</div>' +
'<div ng-show="{{nestedField.restResourceName != null}}">' + '<accordion close-others="oneAtATime">' + '<accordion-group heading={{nestedField.name}} is-open="false">' + /*'<recursive-fields model="createEntityResource" field="field"></recursive-fields>'*/
'<button type="button" ng-click="appendDirective()">I should append a "recursiveFields" directive</button>' + '</accordion-group>' + '</accordion>' + '</div>' + '</div>',
link: function (scope, element, attrs) {
console.log("1");
if (scope.field.restResourceName != null) {
$http.get(CONSTANTS.EXPLAIN_URL + "/" + scope.field.restResourceName)
.success(function (data, status) {
scope.field.nestedFields = [];
data.content.resource.fields.forEach(function (field) {
if (field.isEnum) {
$http.get(CONSTANTS.ENUMS_URL + scope.$root.parseClassName(field.type)).success(function (data, status) {
field.enumValues = [];
for (var index in data.content.values) {
field.enumValues.push(data.content.values[index]);
}
})
}
scope.field.nestedFields.push(field);
})
})
}
scope.appendDirective = function () {
var recursiveFields = $("<p>Insert me</p>");
recursiveFields.insertAfter(element[0]);
$compile(recursiveFields)(scope);
}
}
}
})
Does anyone know how to solve this issue with Angular? Every useful answer is highly appreciated and evaluated.
Thank you.
ngClick has access to the $event that can be passed to your method like this:
<button type="button" ng-click="appendDirective($event)"
That event has a property target.
Check this: https://docs.angularjs.org/api/ng/directive/ngClick
and this: https://docs.angularjs.org/guide/expression#-event-
I'm building a form builder AngularJS app, and I have the following AngularJS directive representing the UI to edit a TextField:
angular.module('myApp.directives').directive('textFormFieldElement', ['$timeout', function($timeout) {
'use strict';
return {
restrict: 'A',
scope: {
element: '='
},
template: '<div class="form-element text-form-field">' +
' <span class="identifier">TF</span>' +
' <strong class="heading" ng-bind="headingText()"></strong>' +
' <div class="editor">' +
' <div class="form-group">' +
' <label>Enter the field name:</label>' +
' <input ng-model="element.fieldName" type="text" class="form-control" />' +
' </div>' +
' <div class="form-group">' +
' <label>Enter a label for the field:</label>' +
' <input ng-model="element.label" type="text" class="form-control" />' +
' </div>' +
' <div class="form-group">' +
' <label>Enter a note for the field:</label>' +
' <input ng-model="element.note" type="text" class="form-control" />' +
' </div>' +
' <div class="checkbox">' +
' <label>' +
' <input ng-model="element.required" type="checkbox" /> Required' +
' </label>' +
' </div>' +
' </div>' +
'</div>',
link: function(scope, element, attributes) {
scope.element.fieldName = scope.element.fieldName || 'TextField';
// Expand the editor when creating new elements, and focus on the first field once rendered.
if (!scope.element._id) {
$timeout(function() {
element.find('.editor').addClass('expanded');
element.find('.editor').find('input:first').select();
}, 10);
}
scope.headingText = function() {
if (scope.element.fieldName.length && scope.element.fieldName.length > 0) {
return scope.element.fieldName;
}
return 'TextField';
};
}
};
}]);
I also have other controls, like a PhoneField, an EmailField, and a RadioButtonListField.
These directives will often have common HTML in their templates as well as common JavaScript behavior. I'd like a way to share this across directives, without polluting the global namespace.
What are some ways this can be achieved?
For template sharing you could extract common template code into 'template directives', i.e. directives that only define template without behavior. This way you can re-use these template directives inside the templates of other directives.
For sharing common JavaScript behavior the two recommended ways are either via Angular services (service/factory) or in the case of directives using directive controller.
For the latter, see the Creating Directives that Communicate section at http://docs.angularjs.org/guide/directive on how to implement this.
I'm using latest version of AngularJS which is 1.2rc3 along with Bootstrap for styling and have directive like this:
angular.module('form.field', [])
.directive('efield', function() {
return {
restrict: 'E',
scope: {
form: '#',
fname: '#',
label: '#'
},
template: "<div data-ng-class=\"{{form}}.{{fname}}.$valid ? 'form-group' : 'form-group has-error'\">" +
"<label class='control-label' for='{{fname}}'>{{label}}</label>" +
"<input type='text' class='form-control' name='{{fname}}' data-ng-required='false' data-ng-model='Form.test'>" +
"</div>"
}
});
html snippet looks like this:
<form name="form" novalidate="novalidate">
<efield form="form" fname="test" label="field"></efield>
</form>
I'm using directive to wrap field html and angular's validation directives to reduce boilerplate code. Problem is even if data-ng-required='false' is set on the input field, parent div is getting 'form-group has error' class instead of just 'form-group'. What am I doing wrong here?
You are running into problems due to the isolated scope you created in your directive. There are several ways to approach it. Here is one that will remove the isolated scope and use template:function(elem,attrs) instead
.directive('efield', function () {
return {
restrict: 'E',
template: function (elem, attrs) {
return "<div data-ng-class=\"" + attrs.form + "." + attrs.fname + ".$valid ? 'form-group' : 'form-group has-error'\">" +
"<label class='control-label' for='" + attrs.fname + "'>" + attrs.label + "</label>" +
"<input type='text' class='form-control' name='" + attrs.fname + "' data-ng-required='false' data-ng-model='Form.test'>" +
"</div>"
}
});
Now scope will be that of the parent scope
Take a look at this directive https://github.com/nelsonomuto/angular-ui-form-validation
I stumbled upon this article on how to build a click to edit feature for a form. The author states:
What about if you wanted input type="date" or even a select? This
is where you could add some extra attribute names to the directive’s
scope, like fieldType, and then change some elements in the template
based on that value. Or for full customisation, you could even turn
off replace: true and add a compile function that wraps the necessary
click to edit markup around any existing content in the page.
While looking through the code I cannot seem to wrap my head around how I could manipulate the template in such a way that I could make it apply to any angular component, let alone how I can make it apply to a drop down list. Code from article below:
app.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled">' +
'{{value}} ' +
'<a ng-click="enableEditor()">Edit</a>' +
'</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue">' +
'Save' +
' or ' +
'<a ng-click="disableEditor()">cancel</a>.' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
My question is, how can we extend the above code to allow for drop down edits? That is being able to change to the values that get selected.
One approach you might consider is using template: function(tElement,tAttrs ).
This would allow you to return appropriate template based on attributes.
app.directive("clickToEdit", function() {
return {
/* pseudo example*/
template: function(tElement,tAttrs ){
switch( tAttrs.type){
case 'text':
return '<input type="text"/>';
break;
}
},....
This is outlined in the $compile docs