Dynamically change html element in Angular directive - javascript

Let's say have have a simple template for a directive like this:
<section class="card {{width}} recipe-list-card">
<div class="card-top">
<h3>{{headerText}}</h3>
</div>
<div class="card-bottom">
<div ng-transclude></div>
</div>
</section>
In some cases I'd like to use an h2 and in others and h3. Is there a good way to change the element with a directive?
Here's what I have in my directive:
module.exports = function(app) {
app.directive('cardDirective', function() {
return {
restrict: 'AC',
replace: true,
transclude: true,
templateUrl: '/templates/card_template.html',
scope: {
header: '=',
headerText: '#',
width: '#' //two columns, three columns, etc
}
}
})
}
I'd like to assign the header variable to h2, h3 etc. So far I've only been able to get escaped html (the actual tag rendered out like <h2> in the browser).

You can create a directive for your heading tag, like this:
angular.module('myApp')
.directive('myHeading', myHeading);
function myHeading() {
return {
transclude: true,
template: function(tElement, tAttrs) {
var level = Number(tAttrs.level);
if (level < 1 || level > 6) level = 1; // default
return '<h' + level + '><ng-transclude></ng-transclude></h' + level + '>';
}
};
}
Then you could use it in your template like this:
<my-heading level="2">{{headerText}}</my-heading>

You can do it by following the code as follows
Change HTML as follows:
<section class="card {{width}} recipe-list-card">
<div class="card-top">
<h3 ng-show="h3">{{headerText}}</h3>
<h2 ng-show="h2">{{headerText}}</h2>
</div>
<div ng-click="updateh2h3()">Check h2h3 changes</div>
<div class="card-bottom">
<div ng-transclude></div>
</div>
</section>
And modify controller as follows:
module.exports = function(app) {
app.directive('cardDirective', function() {
return {
restrict: 'AC',
replace: true,
transclude: true,
templateUrl: '/templates/card_template.html',
scope: {
header: '=',
headerText: '#',
width: '#' //two columns, three columns, etc
},
controller: function($scope) {
$scope.h2 = false;
$scope.h3 = true;
$scope.updateh2h3 = function(){
if($scope.h2){
$scope.h2 = false;
$scope.h3 = true;
} else {
$scope.h2 = true;
$scope.h3 = false;
}
}
}
}
})
}

You can simply add an attribute to the directive and use ng-switch to setup the header you need for your card. take a look this demo i've done.
<div class="card">
<div class="card--Title" ng-switch on="headline">
<h1 ng-switch-when="h1">{{::headerText}}</h1>
<h2 ng-switch-when="h2">{{::headerText}}</h2>
<span ng-switch-default>{{::headerText}}</span>
</div>
</div>
http://embed.plnkr.co/simYTj/

I think you are looking at this issue the wrong way. Instead of switching tags around, you can just define two classes for h2 and h3, and then you can use ng-class to switch between. Dynamically compiling tags and manipulating dom for this is very expensive operation.

Related

Why can`t I access scope via controller?

I am working on passion project. And I cant access scope to pass data to back-end frame work.
Here is my index file
<div id="main-menu" ng-controller="appCtrl">
//some other code
<div id="includedDocumentsFilter" style="float:right; display:none; padding-right: 10px;">
<my-documents validate-options="validateDialogOptions()" call-dialog="showDialog()"> </my-documents>
</div>
//some other code
</div>
My custom directive
'use strict';
dbApp
.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
}
return {
scope: true,
template: documentTemplate
}
}]
)
My controller
$scope.changeDocument = function (applyFilter) {
if (applyFilter === true) {
//Here is where I cant access $scope
}
}
Firstly, I see a extra closing curly braces in your directive. Secondly in your html code there is display:none in div with id "includedDocumentsFilter". Just wondering if you are hiding the div, how will you be able to see the template defined in your directive. I have added a working jsfiddle link below using your above mentioned code
dbApp.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
return {
scope: true,
template: documentTemplate
}
}]
)
JsFiddle link: https://jsfiddle.net/anilsarkar/gk2dfh1p/21/
Note: I have replaced kendo-button with span in jsfiddle

Pass json value as ng-model value in angular 1.2

I'm trying to bind 2 separate elements so that one can trigger the other. The first step in this, is adding an identifying variable to my component template.
Here's the bullet.html template:
<div class="button bullet" ng-model="component.bullet.show_on_click" ng-click="showElementByUniqueName( component.bullet.show_on_click )"><p>{{component.bullet.text}}</p></div>
I'd like to know what is the correct syntax to set ng-model as the VALUE in component.bullet.show_on_click. At the moment, in the final html, ng-model turns out just as shown in the template. I have tried single speech marks and single AND double curly braces; All throw errors.
Any help, much appreciated!
EDIT
On request, here is some more detail:
The eng-bullet attribute fires up the engBullet directive:
app.directive('engBullet', function() {
return {
restrict: 'A',
replace: true,
templateUrl: 'components/bullet.html',
link: function(scope, element, attrs) {
// following function referenced in bullet.html
scope.showElementByUniqueName = function (showOnClick) {
// remove 'replaces' element
$('#'+$('#'+showOnClick).attr('data-replaces')).addClass('hidden');
// hide all popups (in case another popup is currently visible)
$('.popup').addClass("hidden");
// show selected popup
$('#'+showOnClick).removeClass("hidden");
}
}
};
});
The eng-popup attribute fires up the engPopup directive:
app.directive('engPopup', function() {
return {
restrict: 'A',
replace: true,
templateUrl: 'components/popup.html',
link: function(scope, element, attrs) {
scope.show = true;
scope.complete = false;
// watch for this popup being made visible and check if
scope.$watch(function() { return element.is(':visible') }, function() {
scope.$parent.componentCompleted(attrs.id);
});
}
};
});
..which loads in the components/popup.html template:
<div class="hidden popup {{component.popup.type}}" id="{{component.popup.name}}" data-replaces="{{component.popup.replaces}}" eng-completable ng-show="component.popup.name">
<div ng-if="component.popup.type=='overlay'">
<div class="float-right button close-button">X</div>
</div>
<p class="heading">{{component.popup.heading}}</p>
<div class="popup-content">
<div ng-if="0" ng-repeat-start="(innerIndex, component) in component.popup.popup_components"></div>
<div ng-if="0" ng-repeat-start="(type, object) in component"></div>
<div ng-attr-id="{{'p' + pageId + '-s' + skey + '-c' + ckey + '-component-' + index + '-innerComponent-' + innerIndex}}" ng-switch="type">
<div ng-switch-when="image" eng-image></div>
<div ng-switch-when="paragraph" eng-paragraph></div>
</div>
<div ng-if="0" ng-repeat-end></div>
<div ng-if="0" ng-repeat-end></div>
</div>
</div>
I'm not sure this is really relevant to the question though, which is how do I get the VALUE in component.bullet.show_on_click to present in the final html as the value of ng-model in the bullet html template, eg:
ng-model="unique_name_here"
?
Thanks.

Directive with same template but with dynamic content

HTML :
<div custDirective id="managerNames"></div>
<div custDirective id="empNames"></div>
Template.html
<div ng-repeat="name in names">
<ol><li>{{name}}</li></ol>
</div>
Directive link function :
if(attr.id === "name"){
scope.names = ["A","B","C","D"];
}else{
scope.names = ["E","F","G","H"];
}
I want to get the dynamic list based on the id attribute.i.e, If id is managerNames then my ng-repeat should repeat a,b,c,d else it should repeat e,f,g,h.
How to achieve this? I am using angular.js 1.2 version.
you can send an attribute to your link where it will check on the value you send to it and work accordingly to it
Here is an example :
//Directive
angular.module('yourModule').directive('directiveName',
function($parse) {
return {
restrict: 'E',
templateUrl: "your/template.html",
scope: {
check: '#',
},
link: function(scope, element, attrs) {
if(scope.check == "whatever"){
}
}
})
//HTML
<directive-name check="whatever"></directive-name>

AngularJs controller add all html elements from template

I want to add all my elements from my template to my $scope.
I want them to be accessible like c# or java elements in the code.
for example
if i have this HTML template
<div ch-options-div id="chOptions" ng-controller="chOptionsCtrl">
<span>Options</span>
<div id="chOptionsWrapper">
<div id="div1">
</div>
<div id="div2"></div>
<div id="div3"></div>
</div>
</div>
And here is a possible controller:
var chOptions = angular.module('chOptions',[]);
chOptions.controller('chOptionsCtrl', function ($scope,$document,$element)
{
//select all elements by id and add them to scope
$scope.chOptionsWrapper = document.getElementById('chOptionsWrapper');
//or with jquery
$scope.div1 = $('#div1')
}
Is there a best case to do this or is there a good way to add all my HTML elements to the scope ? I want clean "object oriented" javascript code.
You can use a directive to achieve this.
.directive('box', function () {
return {
scope: {
rgb: '='
},
link: function (scope, elem, attrs) {
scope.$watch('rgb', function () {
angular.element(elem).css('background-color', 'rgb(' + scope.rgb + ')');
});
}
}
}
Here is an example of you to use the directive: http://jsfiddle.net/skriblez/dqfwvyso/4/

Is it possible to compile angular template to final html string?

Is it possible to compile this html template string:
"<p>List of products from {{supplier.name}}</p>
<p ng-repeat="ref in refs">{{ref}}</p>"
directly to an html string like:
"<p>List of products from Some Supplier</p>
<p>a0120</p>
<p>a0241</p>
<p>z1242</p>
<p>z3412</p>"
or at least the less clean version:
"<p class="ng-scope ng-binding">List of product from Duval</p>
<!-- ngRepeat: ref in refs track by $index -->
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">a0120</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">a0241</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">z1242</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">z3412</p>"
I tried using $compile(templateStr)($scope) but the dom elements returned are not fully processed.
However I managed no compile it to a page element using the following directive and and inspecting that element I can see it has the final html I'm looking for:
app.directive('compile', function($compile) {
return{
restrict: 'A',
scope: {
compile: '=compile',
data: '=ngData'
},
link: function(scope, element, attrs) {
scope.$watch('data',
function(value) {
for (var k in scope.data)
scope[k] = scope.data[k];
}
)
scope.$watch('compile',
function(value) {
element.html(value);
var a = $compile(element.contents())(scope);
}
)
}
}
})
Is there any way I can get that final html directly from the template?
Thanks
PS:
What I'm trying to achieve here is to edit a template directly in CKEditor (in text mode, not source)
and only eventually goint to source mode to add some "ng-repeat" attributes.
Using template engines like Handlebars require placeholders outside html elements and are automaticaly erased by CKEditor since it only deals with html.
POSSIBLE SOLUTION (hacky):
One possible way is to use the compile directive on an hidden element and read the element's content after view is loaded on the controller:
$scope.$on('$viewContentLoaded', $scope.onLoaded);
$timeout(function() {
var el =$("#text div")[0]
cleanAngularStuff(el)
$scope.currMailTemplate.processed = el.innerHTML
});
The cleanAngularStuff function is just to clean extra angular directives and classes.
I'll post it here if someone wants to use it or improve it.
Any better way to do this without adding an element to the page?
What you need to do is access the compiled element after a $digest cycle.
So within a $digest cycle you can do:
templateString = '<some-template-code/>';
...
var compiled = $compile(templateString)(scope);
// scope.$digest // only call this if not within a $digest cycle
// you can do a $timeout to let the previous digest cycle complete
$timeout(function(){
var theHtml = compiled[0].outerHTML;
console.log('the html with the variables', theHtml);
});
If you aren't already within a digest cycle then you need to manually call scope.$digest(). You can see the embedded sample below.
(function(){
"use strict";
var app = angular.module('soApp', []);
app.directive('showCompiledTemplate',[
'$compile','$timeout',
function($compile , $timeout) {
return {
restrict: 'E',
template: '<div class="compiled-template">' +
'<div><textarea cols="40" rows="15" readonly></textarea></div>' +
'<div class="output"></div>' +
'</div>',
scope: {
data: '=',
template: '='
},
link: function(scope,elem,attrs) {
var textarea = elem.find('textarea')[0];
var output = elem.children().children().eq(1);
var updateOutput = function(tpl) {
var compiled = $compile(tpl)(scope);
$timeout(function(){
var theHtml = compiled[0].outerHTML;
textarea.value = theHtml;
output.html(theHtml);
});
};
scope.$watch("template",function(tpl){
updateOutput(tpl);
});
scope.$watch("data",function(){
updateOutput(scope.template);
},true);
}
};
}
]);
app.controller('MainCtrl', function() {
this.data = {
name: 'John',
list: ['one duck','two ducks','three ducks']
};
//this.template = "<div>hi</div>";
var template = '';
template += '<div>\n';
template += ' <p>{{data.name}}</p>\n';
template += ' <ul>\n';
template += ' <li ng-repeat="item in data.list">{{item}}</li>\n';
template += ' </ul>\n';
template += '</div>\n';
this.template = template;
});
})();
.form-field {
padding-bottom: 10px;
}
.form-field span {
width: 70px;
display: inline-block;
}
.compiled-template {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: row;
flex-direction: row;
}
.compiled-template textarea {
background-color: #eee;
margin-right: 10px;
}
.compiled-template .output {
border: 1px solid #ccc;
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<div ng-app="soApp">
<div ng-controller="MainCtrl as main">
<div class="form-field">
<span class="form-label">Name:</span>
<input type="text" ng-model="main.data.name" /> <br/>
</div>
<div class="form-field">
<span class="form-label">Template:</span>
<textarea ng-model="main.template" cols="40" rows="8"></textarea> <br/>
</div>
<div>
<show-compiled-template data="main.data" template="main.template" />
<div>
</div>
</div>
It can be done by providing template to your directive like below.
app.directive('compile', function($compile) {
return{
restrict: 'A',
template: '<p>List of products from {{supplier.name}}</p>
<p ng-repeat="ref in refs">{{ref}}</p>'
scope: {
refs: '='
supplier: '='
},
link: function(scope, element, attrs) {
# Your code goes here
}
}
})

Categories

Resources