I'm having an hard time trying to ask idea to keep my code style on certain conditions.
The use case is really simple : When declaring inline templates in angular, i'd like to keep the string concatenation indented "hmtl-like", aka :
return {
restrict : "E",
require : "?ngModel",
transclude : true,
replace : true,
template : '' +
'<div class="tl-checkbox">' +
'<div class="tl-checkbox-container">' +
'<div class="tl-checkbox-icon"></div>' +
'</div>' +
'<div class="tl-checkbox-label" ng-transclude></div>' +
'</div>'
}
But when applying code styles, this become flattened :
return {
restrict : "E",
require : "?ngModel",
transclude : true,
replace : true,
template : '' +
'<div class="tl-checkbox">' +
'<div class="tl-checkbox-container">' +
'<div class="tl-checkbox-icon"></div>' +
'</div>' +
'<div class="tl-checkbox-label" ng-transclude></div>' +
'</div>'
}
Which is just simply unreadable, unmaintainable and ugly.
The "workaround" I actually use is to insert empty spaces at the start of the lines :
return {
restrict : "E",
require : "?ngModel",
transclude : true,
replace : true,
template : '' +
'<div class="tl-checkbox">' +
' <div class="tl-checkbox-container">' +
' <div class="tl-checkbox-icon"></div>' +
' </div>' +
' <div class="tl-checkbox-label" ng-transclude></div>' +
'</div>'
}
But this insert empty text node in the template, and this is basically less pretty than without spacing.
My question is : is there a way to make idea understand that I want my custom indentation preserved for string concatenation while keeping the code style / fix indent for the rest of the file ?
Edit : before this is answered : no, using templateUrl instead of inline templating is not an acceptable answer, they behave slightly differently in complex layouts (sync linking for template vs async linking for templateUrl )
Related
I have created an angularjs directive called image-multiselect which can be used as follows.
<image-multiselect items="landTransportTypes" image="illustrationURL" itemname="name" append="<div class='detail'>...some html here...</div>"></image-multiselect>
Notice the append attribute which is assigned an html as string. This html string i expect to use for modifying the template attribute in the DDO as follows
function(){
var imageMultiselect = function(){
return {
scope : {
items: "=",
image: "#",
itemname: "#",
append: "#"
},
template : "<div style='background:#f2f2f2;padding:20px;margin:20px;border-radius:5px;min-height:100px;'>" +
"<div ng-repeat='item in items' class='card text-center'>" +
"<img class='card-img' src='{{item[image]}}' alt='Avatar'>" +
"<div class='detail'>" +
"<b>{{item[itemname]}}</b>" +
/*append used to customize template as per requirement*/
"{{append}}"+
"</div>" +
"</div>" +
"</div>"
}
}
angular.module("myApp").directive("imageMultiselect",imageMultiselect);
}());
Problem : The html string passed in the append is not rendered as html rather entire markup is displayed as it is on the page ?
Angular doesnt render HTML, because of the potential dangeros bahaviour of unknown HTML. If you want your HTML to be rendered use the ngSanitize
Then use
<div ng-bind-html="variableWithHTMLString"> </div>
When using the $sanitize service. Data in ng-bind-html will by default be rendered.
I've made a plunker to your exsample: https://plnkr.co/edit/Vi46BsuAsnuk3N3R4Yz9?p=preview
The changes are that the append varibale is binded with ng-bind-html, sanitaize is downloaded in sciript tag and injected in module.
#Anders thanks for your response, it gave me the right direction. I used #Ander's approach but instead of using ng-bind-html i used ng-html-compile directive by Francis Bouvier ( https://github.com/francisbouvier/ng_html_compile )
Use Directive
<image-multiselect items="landTransportTypes"
image="illustrationURL"
itemname="name"
append="<input type='text' name='count' placeholder='Count' ng-model="transport.count" class='form-control input-sm'/></div>">
</image-multiselect>
Directive Definition
(function(){
var imageMultiselect = function(){
return {
scope : {
items: "=",
image: "#",
itemname: "#",
append: "#"
},
template : "<div style='background:#f2f2f2;padding:20px;margin:20px;border-radius:5px;min-height:100px;'>" +
"<div ng-repeat='item in items' class='card text-center'>" +
"<img class='card-img' src='{{item[image]}}' alt='Avatar'>" +
"<div class='detail'>" +
"<b>{{item[itemname]}}</b>" +
/* This is where i use ng-html-compile directive, which works wonders for me, refer : https://github.com/francisbouvier/ng_html_compile */
"<span ng-html-compile='append'></span>" +
"</div>" +
"</div>" +
"</div>"
}
}
angular.module("myApp").directive("imageMultiselect",imageMultiselect);
}());
I have several multi-selects on a page, each with a bit of logic that fills that multi-select from the server, and I want to wrap each one up into a Directive.
Before trying to wrap these into Directives, I built each as such:
index.html
<select name="groups" ng-model="inputs.groups" ng-change="groupsChanged()" ng-options="g for g in allgroups" multiple></select>
controllers.js
In the first pass, I do my $http calls from here. Yes, I know, not best practices, but I wanted to prove that this works to myself first.
$scope.loadSelect = function(_url) {
$http({
url: _url,
method: 'POST',
data: $scope.inputs,
model: 'all' + _url
}).success(function(data, status, headers, config) {
$scope[config.model] = data;
});
};
// Fill groups
$scope.loadSelect('groups');
// When groups change, reload other select fields that depend on groups
$scope.groupsChanged = function() {
$scope.loadSelect('categories');
$scope.loadSelect('actions');
}
Now I want to migrate this to a Directive. I see two major challenges:
1.) How do I encapsulate the entire set of options (e.g. what is now the "allgroups" model) into the Directive?
2.) Based on initial experiments, I tried to physically build the <select/> into the template, but realized that I have to manipulate the DOM to physically replace name, ng-model, and ng-options. That lead me to the compile attribute, but a.) that feels wrong and b.) setting <select ng-options="x for x in allgroups" /> doesn't actually repeat after it's been inserted into the DOM. Using compile doesn't feel right; what's the right way to approach this?
Here is my first attempt at the Directive looks like this. It doesn't really work, and I think I'm going about it incorrectly:
index.html
<dimension ng-model="inputs.users" alloptions-model="allusers">Users</dimension>
directives.js
directive('dimension', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
alloptionsModel: '='
},
template:
'<div>' +
'<label ng-transclude></label>' +
'<fieldset>' +
'<div class="form-group">' +
'<select ng-model="{{ngModel}}" ng-options="x for x in {{alloptionsModel}}" multiple class="form-control"></select>' +
'</div>' +
'</fieldset>' +
'</div>',
replace: true,
transclude: true
};
});
Clearly I haven't even gotten to the server load part yet, but I plan to roll that into a controller in the Directive, with the actual $http call in a service.
I feel like I'm moving down the wrong track. If you have suggestions on how to realign, please help!
The main problem with your directive is that you can't use mustache binding in ngModel and ngOptions directive because they are evaluated directly. You can directly bind to the scoped property (ngModel and alloptionsModel):
directive('dimension', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
alloptionsModel: '='
},
template:
'<div>' +
'<label ng-transclude></label>' +
'<fieldset>' +
'<div class="form-group">' +
'<select ng-model="ngModel" ng-options="x for x in alloptionsModel" multiple class="form-control"></select>' +
'</div>' +
'</fieldset>' +
'</div>',
replace: true,
transclude: true
};
});
See this plunkr for a working example.
Edit
As for the compile route, there is nothing wrong with it. It is useful when you need to dynamically create a template which will clearly be your case when you will get to the select's item template.
compile: function(tElement, tAttrs) {
var select = tElement.find('select'),
value = tAttrs.value ? 'x.' + tAttrs.value : 'x',
label = tAttrs.label ? 'x.' + tAttrs.label : 'x',
ngOptions = value + ' as ' + label + ' for x in alloptionsModel';
select.attr('ng-options', ngOptions);
}
// In the HTML file
<dimension ng-model="inputs.users"
alloptions-model="allusers"
label="name">
Users
</dimension>
I've updated the plunkr with the compile function.
I wanna rewrite some html into a directive but I'm in the dark when it comes to directives that use a dynamic template. Basically what I need to do is being able to pass parameters to the template so that I can re-use it with different values.
Now I've just passed parameters to the directive function but I know this is wrong. When you create directives that uses restrict: 'A' you can pass parameters like so:
<div error="somevalue"></div>
Which would then be available in the attrs. But how does it work when it uses restrict: 'E'?
How can I achieve what I want here?
forumApp.directive('error', [function(row, isRowAvailable, isRowUnchanged) {
return {
restrict: 'E',
template: '<div ng-if="row.model === "' + row + '">
<span class="error">*</span>
<div class="error-cell-inner" ng-if="row.model === 'row' && user[' + row + '].length > 0">
<span ng-if="' + isRowUnchanged + '"></span>
<span ng-if="' + isRowAvailable + ' && !' + isRowUnchanged + '" class="available">The name is available</span>
<span ng-if="!' + isRowAvailable + ' && !' + isRowUnchanged + '" class="error weight-override">The name is already taken</span>
</div>
</div>'
}
}]);
This helps in understanding how custom directives work in a easy way.
http://www.ng-newsletter.com/posts/directives.html
Try something on these lines.
Here I am using AE which means I am allowing this directive to be used as Attribute or element. But you can restrict it to just E too.
<rb-add-button text="Add New Parcel" ng-disabled="!storeNumber" ng-click="addParcel()"></rb-add-button>
appRedB.directive('rbAddButton', function () {
return {
restrict: 'AE',
replace: true,
scope: {
text: '#'
},
template: '<button class="btn btn-primary btn-sm"><i class="fa fa-plus-circle"></i> {{ text || "Add"}}</button>'
};
});
I have several multi-selects on a page, each with a bit of logic that fills that multi-select from the server, and I want to wrap each one up into a Directive.
Before trying to wrap these into Directives, I built each as such:
index.html
<select name="groups" ng-model="inputs.groups" ng-change="groupsChanged()" ng-options="g for g in allgroups" multiple></select>
controllers.js
In the first pass, I do my $http calls from here. Yes, I know, not best practices, but I wanted to prove that this works to myself first.
$scope.loadSelect = function(_url) {
$http({
url: _url,
method: 'POST',
data: $scope.inputs,
model: 'all' + _url
}).success(function(data, status, headers, config) {
$scope[config.model] = data;
});
};
// Fill groups
$scope.loadSelect('groups');
// When groups change, reload other select fields that depend on groups
$scope.groupsChanged = function() {
$scope.loadSelect('categories');
$scope.loadSelect('actions');
}
Now I want to migrate this to a Directive. I see two major challenges:
1.) How do I encapsulate the entire set of options (e.g. what is now the "allgroups" model) into the Directive?
2.) Based on initial experiments, I tried to physically build the <select/> into the template, but realized that I have to manipulate the DOM to physically replace name, ng-model, and ng-options. That lead me to the compile attribute, but a.) that feels wrong and b.) setting <select ng-options="x for x in allgroups" /> doesn't actually repeat after it's been inserted into the DOM. Using compile doesn't feel right; what's the right way to approach this?
Here is my first attempt at the Directive looks like this. It doesn't really work, and I think I'm going about it incorrectly:
index.html
<dimension ng-model="inputs.users" alloptions-model="allusers">Users</dimension>
directives.js
directive('dimension', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
alloptionsModel: '='
},
template:
'<div>' +
'<label ng-transclude></label>' +
'<fieldset>' +
'<div class="form-group">' +
'<select ng-model="{{ngModel}}" ng-options="x for x in {{alloptionsModel}}" multiple class="form-control"></select>' +
'</div>' +
'</fieldset>' +
'</div>',
replace: true,
transclude: true
};
});
Clearly I haven't even gotten to the server load part yet, but I plan to roll that into a controller in the Directive, with the actual $http call in a service.
I feel like I'm moving down the wrong track. If you have suggestions on how to realign, please help!
The main problem with your directive is that you can't use mustache binding in ngModel and ngOptions directive because they are evaluated directly. You can directly bind to the scoped property (ngModel and alloptionsModel):
directive('dimension', function() {
return {
restrict: 'E',
scope: {
ngModel: '=',
alloptionsModel: '='
},
template:
'<div>' +
'<label ng-transclude></label>' +
'<fieldset>' +
'<div class="form-group">' +
'<select ng-model="ngModel" ng-options="x for x in alloptionsModel" multiple class="form-control"></select>' +
'</div>' +
'</fieldset>' +
'</div>',
replace: true,
transclude: true
};
});
See this plunkr for a working example.
Edit
As for the compile route, there is nothing wrong with it. It is useful when you need to dynamically create a template which will clearly be your case when you will get to the select's item template.
compile: function(tElement, tAttrs) {
var select = tElement.find('select'),
value = tAttrs.value ? 'x.' + tAttrs.value : 'x',
label = tAttrs.label ? 'x.' + tAttrs.label : 'x',
ngOptions = value + ' as ' + label + ' for x in alloptionsModel';
select.attr('ng-options', ngOptions);
}
// In the HTML file
<dimension ng-model="inputs.users"
alloptions-model="allusers"
label="name">
Users
</dimension>
I've updated the plunkr with the compile function.
I made a following directive:
.directive('getData', function(){
return {
restrict: 'C',
replace: true,
transclude: true,
scope: { myData:'#myData' },
template: '<div ng-switch="myData">' +
'<div ng-switch-when="4">Real Data</div>' +
'<div ng-switch-when="5">False Data</div>' +
'<div ng-switch-default>No Data</div>' +
'</div>'
}
Then on a grid that uses ng-grid, I have a column that has 2 rows with values 4 and 5. I have following in cellTemplate for this column:
cellTemplate: ' < div class="getData" myData="{{row.getProperty(col.field)}}" />'
The problem is both the rows display "No Data". I would like to show "Real Data" on the cell that has value 4 and "False Data" on the cell that has 5.
What I am not doing right? Any help would be greatly appreciated.
In your cellTemplate change myData to my-data. When using angular directives camel case is converted to dash case automatically by the angular library.
For example if you have a directive element defined as "mySuperAwesomeDirective". It would be declared in html using the following.
<my-super-awesome-directive></my-super-awesome-directive>
Here is a working version of your plunker example.
http://plnkr.co/edit/ENj00Re3bfaVGPlomhFG?p=preview