Dynamically insert directives using meta data in AngularJS? - javascript

I'd like to define meta data which will dynamically use the correct directive based on a "type" value:
$scope.items = [
{
type: 'directive-one',
value: 'One'
},{
type: 'directive-two',
value: 'Two'
},{
type: 'directive-three',
value: 'Three'
}
];
and then
<li ng-repeat="item in items" {{type}}>
{{value}}
</li>
I've created a jsfiddle here. So far I've had no success
Is this possible? How would I accomplish this?

Here is an alternative way of solving the problem:
Use ngSwitch to map between type and directive.
<li ng-repeat="item in items">
<div ng-switch on="item.type">
<div ng-switch-when="type-one" directive-one>
</div>
<div ng-switch-when="type-two" directive-two>
</div>
<div ng-switch-when="type-three" directive-three>
</div>
</div>
</li>
See jsfiddle
But if you really need to define the directive in the metadata, you can add a directive that will generate the div element with the appropriate directive
angular.module('myApp').directive('dynamicDirective', function($compile) {
return {
restrict: 'A',
link: function (scope, ele) {
//add a child div element that contains the directive specified in the type property
var itemEl = angular.element('<div>').attr(scope.item.type,'');
$compile(itemEl)(scope);
ele.append(itemEl);
}
};
});
See jsfiddle

Related

jquery $ selector to apply only to template scope (AngularJS)

I'm generating a template multiple times in the same page, however a function of this template's controller will work on the whole page. How can make sure it only works within the respective template scope?
In this example, when the user clicks a small div, its content it will travel to another div. How does this work? I select the clicked div with a specific class name using jquery and append it to the div it should travel to. However, as I'm calling this template multiple times, the function will select all classes in the page with that same class name, instead of just the template scope.
How can I make it that the selector will only look into the template's scope?
Here's the template:
<div ng-controller="templateController">
<div class="source">
<div ng-repeat="item in info">
<div class="content" data-value="{{item.ID}}">{{item.name}}</div>
</div>
</div>
<br style="clear:both" />
<div class="receiver"></div>
<div>
</div>
And here's the controller with some jquery functions:
angular
.module("demo")
.controller("templateController", ["$scope", "$timeout", function($scope, $timeout) {
$scope.info = [
{
name: "john",
ID: 1
},
{
name: "Edward",
ID: 0
},
{
name: "Carl",
ID: 2
}
];
$timeout(function () {
$(".source .content").click(function () {
console.log("leaving source");
$(this).appendTo($(".receiver"));
})
});
$timeout(function () {
$(".receiver .content").click(function () {
console.log("leaving receiver");
$(this).appendTo($(".source"));
})
});
I also would like to travel back to its original container (as shown in the second function but seems to not be working), but it only travel in one direction.
Here's a simple plunk with the whole code so you can see it work and see what's wrong
enter link description here
Thank you

$scope and ng-model in the same directive

I have a directive that saves in a model in the Controller. It is a "text-button" (as per requirements) which is just a read-only textbox. There are three text-buttons per "Line" and 13 "Lines".
I need to pull up a selection modal and load some data in depending on what was clicked, so that a new selection can be made.
Although the model on the controller is changed, I don't know what was changed by the time the selection modal pops up.
Model:
$scope.Lines = {
"eg1": { one: '', two: '', three: '' },
"eg2": { one: '', two: '', three: '' },
"eg3": { one: '', two: '', three: '' },
"eg4": { one: '', two: '', three: '' },
... 9 more ...
};
Directive:
.directive('textButton', function () {
return {
restrict: 'E',
require: 'ngModel',
link: function ($scope, elm, attrs, ctrl) {
elm.on('click', function () {
$scope.popModal(this); //<--I want the ng-model here!
});
},
template: '<input type="text" readonly="readonly" class="form-control" />'
};
});
View:
<ul>
<li> <text-button ng-model="Lines.eg1.one"></text-button> </li>
<li> <text-button ng-model="Lines.eg1.two"></text-button> </li>
<li> <text-button ng-model="Lines.eg1.three"></text-button> </li>
<ul>
<ul>
<li> <text-button ng-model="Lines.eg2.one"></text-button> </li>
<li> <text-button ng-model="Lines.eg2.two"></text-button> </li>
<li> <text-button ng-model="Lines.eg2.three"></text-button> </li>
<ul>
... 11 more ...
I've looked at $watch and $scope.watch, but nothing seems to be able to tell me what in a particular model has changed.
https://stackoverflow.com/a/15113029/1913371
In the end, you need to isolate the scope of your directive so you have a chance to get to the ngModel of each line:
scope: {
ngModel: '#',
popModal: '='
}
then you can use it in your callback:
elm.on('click', function () {
$scope.popModal($scope.ngModel); //<--you get the ng-model here!
});
However, this means you also lose access to popModal() which I guess is defined in the controller scope. To fix this, you need to hand it in as a second parameter (I named it pop-modal):
<text-button ng-model="Lines.eg1.one" pop-modal="popModal"></text-button>
Tying it all together, here's a JSBin using Angular 1.2 (although you really should get away from that).
If you are using AngularJs 1.3+, you can use 'controllerAs' in the directive definition object. Then create a controller to do popModal.

AngularJS and BootstrapUI - pass function and argument to BootstrapUI directive

I'd like to pass a function with to BootstrapUI's popover directive. The attribute is normally a string, but I need to do an AJAX call to supply the attribute to the directive. Currently, the popover displays the function as a string e.g. "showItem(one)" rather than the result of calling the function, e.g. "Item is one". Thanks!
The HTML
<li ng-repeat="item in items"
popover-placement="top"
popover-trigger="mouseenter"
uib-popover="showItem({{item.id}})">
{{item.id}}
</li>
The JS
app.controller("uibController", ["$scope", function ($scope) {
$scope.items = [
{id: "one"},
{id: "two"},
{id: "three"}
];
$scope.showItem = function(item){
$http.get('url').success(function(response){
//data for popover directive
return "Item is " + item.id;
})
};
}]);
Codepen
http://codepen.io/anon/pen/PZQOdY
<div ng-repeat="item in items"
popover-placement="bottom"
popover-trigger="mouseenter"
uib-popover="{{showItem(item)}}">
{{item.id}}
</div>

AngularJS append html to dom element

Im working on a small web app, and there is a side menu that has nav links in it. Each link when clicked pulls out a hidden panel and should display a list of items specific to that link.
I have most of the functionality working except Im stuck on how to append either a templateURL or just html to the panel.
Any guidance would be great.
heres what I have so far:
html
<!-- Pullout menu -->
<nav id="sidebar-pullout">
<div id="menu-list"></div>
</nav>
app.js
var configApp = angular.module("configApp", ['ngRoute','ui.bootstrap'])
.config(function($routeProvider){
$routeProvider..when('/organizations', {
templateUrl: 'templates/dashboard/organizations/organizations-title.html',
controller: 'OrganizationController',
activetab: 'organizations'
})
.otherwise( {redirectTo: '/dashboard'} );
});
// Side Nav Link Controllers
configApp.controller('OrganizationController', function($scope) {});
configApp.controller('SideNavCtrl', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
}
});
// adding html to the menu-list
configApp.directive('menu-list', function(){
return {
template: '<span ng-transclude >append som html here</span>',
replace: true,
transclude: true,
controller: 'OrganizationController'
};
});
Here is another way you might be able to go about it. By keeping a reference to menu items and contents. You could keep the side panel content in separate HTML files.
configApp.directive('menuList', function() {
return {
restrict: 'EA',
link: function(scope, el, attr) {
var activeId = null;
scope.showContent = function(id) {
activeId = id;
};
scope.isActive = function(id) {
return activeId === id;
}
scope.menuItems = [{
id: 'item1',
name: 'Menu Item 1',
content: 'path/to/menuItem1content.html'
}, {
id: 'item2',
name: 'Menu Item 2',
content: 'path/to/menuItem2content.html'
}]
}
};
});
Then in you HTML maybe something like this.
<div menuList>
<nav id="sidebar-menu">
<ul>
<li ng-repeat="item in menuItems">
<a ng-click="showContent(item.id)">{{ item.name }}</a>
</li>
</ul>
</nav>
<div id="sidebar-content">
<div class="content"
ng-repeat="item in menuItems"
ng-include="item.content"
ng-show="isActive(item.id)"></div>
</div>
</div>
This is just an idea and you could use angular animation to animate the sidebar sliding and stuff.
You are specifying your ng-transclude directive on the wrong element. You are placing it on your span tag. Try something like this instead:
<div>
<span>/*My template html here*/</span>
<div ng-transclude></div>
</div>
It also looks like you're specifying your directive incorrectly. Try this:
configApp.directive('menuList', function () {
return {
restrict: 'A',
replace: true, // note: this syntax will soon be deprecated
template: '<see above snippet>'
};
});
In particular, notice restrict, which specifies how this directive will be used (A: attribute on html element, E: as an element itself, C: as a class). Here we are saying we want to use our directive as an element, E. Also, note that I used the name menuList instead of menu-list. AngularJS uses camel-case in directive definition, and maps the directive names found in the html into their camel case counterparts. So, in the html we will still use this directive like this: menu-list, but we will declare it using camel-case.
Hope this helps!

AngularJS converting object to string in directive

I need some help with getting AngularJS to maintain my non-string values in directive attributes.
I was looking for a way to render a tree structure in HTML from a piece of JSON, and I found this code: http://jsfiddle.net/n8dPm/
I've been trying to adapt that for my project, as shown in the code below.
My controller/directive is shown here:
cxpControllers.controller("ProductTocCtrl", ["$scope", "$http", "$routeParams",
function ProductTocController($scope, $http, $routeParams) {
$scope.typeOf = typeOf;
//test value
$scope.contents = {
"id": 1,
"name": "Test",
subsections: [
{
id: 2,
name: "Test1.1",
link: "test11.xml",
test: 34
},
{
id: 3,
name: "Test1.2",
link: "test12.xml",
test: 95
}
]
}
}]);
cxpControllers.directive('tree', function($compile) {
return {
restrict: 'E',
scope: {key: "=", content: "="},
templateUrl: "tree_renderer.html",
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
And then this is my template:
<script type="text/ng-template" id="tree_renderer.html">
{{key}}:
<ul ng-if="typeOf(content) == 'object' && content != null">
<li ng-repeat="(key, content) in content">
<tree key="key" content="content"></tree>
</li>
</ul>
<span ng-if="typeOf(content) != 'object'">
"{{content}}"
</span>
</script>
<ul>
<li ng-repeat="(key, content) in contents">
<tree key="key" content="content"></tree>
</li>
</ul>
This would work, except for one problem. Angular is turning the value of "content" into a string, preventing the recursion from working because it can't iterate over a string.
I have seen other questions like this, for example here, but their problem is that they used "#" in the directive scope, which converts to a string. But since I'm using "=", it should maintain the type.
Here's the output I'm seeing with the test data shown in the code above:
I would appreciate any help you can give. If you need more information I'll be happy to supply it.
The problem is with the typeOf function in your template. The compiled template doesn't find this function so it is never equal to 'object'. Add a controller to your directive to define it.
I took the plunkr and added this:
controller: function($scope) {
$scope.typeOf = function(val) {
return typeof val;
};
},
It does recognize it as an object. Check out the updated plunkr here.

Categories

Resources