I have an angular directive:
var myApp = angular.module('myApp',[]);
var HelloDirective = function() {
return {
scope : {
t : "=",
list: "="
}, // use a new isolated scope
restrict : 'AE',
replace : false,
template : '<h3>{{list}}</h3>',
link : function(scope, elem, attrs) {
var p = document.createElement("p");
p.innerHTML = "asdddf";
var ul = document.createElement("ul");
var li = document.createElement("li")
li.innerHTML = "List Item";
ul.appendChild(li);
elem.append(p);
elem.append(ul);
scope.list = "HI";
}
};
}
myApp.directive("hello", HelloDirective);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}
How can I make scope.list display properly?
Also, what is the best way to make the ul show up as a list? Should I use elem.append(ul); or can I say scope.list = ul to make it show up?
I don't get it...
Here's the fiddle:
http://jsfiddle.net/mbaranski/znhnseep/
try this
app.directive('hello', ['$sce', function ($sce) {
return {
scope: {
t: "=?",
list: "=?"
},
restrict: 'AE',
replace: false,
template: "<h3 ng-bind-html='list'></h3>",
link: function (scope, elem, attrs) {
var p = document.createElement("p");
p.innerHTML = "asdddf";
var ul = document.createElement("ul");
var li = document.createElement("li");
li.innerHTML = "List Item";
ul.appendChild(li);
//elem.append(p);
//elem.append(ul);
scope.list = $sce.trustAsHtml(ul.outerHTML);
}
};
}]);
For anyone that comes across this, I did get it sorted out. Here is what works, and it uses the compile function in the directive.
Here's the directive:
/**
* Shows how to modify the original element in compile
* to add a class to the DOM.
*/
var myApp = angular.module('myApp', []);
var HelloDirective = function($sce) {
return {
scope: {
list: "="
}, // use a new isolated scope
restrict: 'AE',
replace: false,
template: '<h3 ng-bind-html="list"></h3>',
compile: function(tElem, attrs) {
var d = document.createElement("div");
d.className += " tree";
var baseElem = tElem[0];
baseElem.className ? baseElem.className += " compiled-element" : baseElem.className = "compiled-element";
baseElem.appendChild(d);
console.log(tElem);
return function(scope, elem, attrs) {
scope.list = $sce.trustAsHtml("<i>Link function value, too</i>");
var ul = document.createElement("ul");
var li = document.createElement("li")
li.innerHTML = "ASDF";
ul.appendChild(li);
var elementResult = elem[0].getElementsByClassName('tree');
console.log(elementResult[0]);
elementResult[0].appendChild(ul);
console.log(scope.list);
}
}
};
}
/**
var p = document.createElement("p");
p.innerHTML = "asdddf";
var ul = document.createElement("ul");
var li = document.createElement("li")
li.innerHTML = "List Item";
ul.appendChild(li);
elem.append(p);
elem.append(ul);
*/
myApp.directive("helloDirective", HelloDirective);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
myApp.controller('MyCtrl', function($scope) {
$scope.name = 'Angular Directive';
$scope.osList = "Original value";
});
Here is a fiddle: http://jsfiddle.net/mbaranski/46dd6p1p/
Here is a blog post that expands on it a little: http://blog.mikeski.net/blog_post/455
Related
I am using Angularjs 1.6, I made this transclusion type of thing.
var app = angular.module('plunker', []);
app.controller("mainCtrl", function ($scope) {
$scope.testmodel1 = {
testmodel1prop: "testmodel1prop"
};
$scope.testmodel2 = {
testmodel2prop: "testmodel2prop"
}
})
app.directive('tabs', function ($compile) {
return {
restrict: 'E',
scope: {},
transclude: true ,
link: function (scope, el, attrs, ctrl, transcludeFn) {
// transcluded content's scope should inherit from parent
transcludeFn(scope.$parent, function (clonedTranscludedContent) {
var tabs = [];
for (var i = 1; i < clonedTranscludedContent.length; i = i + 2) {
tabs.push(clonedTranscludedContent[i]);
}
for (var i = 0; i < tabs.length; i++) {
debugger;
var jqueryTab = $(clonedTranscludedContent[1]);
var stringTab = jqueryTab.prop('outerHTML');
var model = jqueryTab.attr('model');
var pocoModelFromParent = scope.$parent[model];
var newScope = angular.merge(scope.$parent.$new(), pocoModelFromParent)
var linkFn = $compile(stringTab);
var compiledContent = linkFn(newScope);
el.append(compiledContent);
}
});
}
};
});
app.directive('test', function () {
return {
restrict: 'E',
scope: {},
link: function ($scope, $element, attr) {
$scope.var1 = "test";
},
template: '<p>{{ var1 }}</p>'
};
});
<div ng-app="plunker" >
<div ng-controller="mainCtrl">
<tabs>
<tab model="testmodel1" title="tab2">{{ testmodel1prop }}</tab>
<tab model="testmodel2" title="tab1"> {{ testmodel2prop }} </tab>
</tabs>
</div>
</div>
I would like to get some feedback if this is okay or not.
It works perfectly by the way.
The first thing that pops into my mind is why does 'clonedTranscludedContent' has a sort of empty object at position 1 3 5 7 and so on.I have yet to figure out what that is, probably the ::before in chrome?
I am planning to use this on production actually.
I have created custom-directive, I want to use it with 'tags-input'. But When I select any value from auto-complete result, It does not select. I don't know why it not working, Please find
Demo Plunker
Index.html
<my-directive apipoint="customerApi" modeldisplay="customer.selected"
ng-model="customer.selected" searchresults="customeruserprofile" change="customerChanged"></my-directive>
Directive:
app.directive("myDirective", ['$http',function($http){
return {
restrict: "E",
templateUrl: 'auto-complete.html',
require: 'ngModel',
scope : {
modeldisplay: "=",
apipoint: "=",
change: "="
},
link : function(scope, element, attrs, ctrl){
scope.loadTags = function(query) {
return $http.get(scope.apipoint[0]);
};
scope.addCustomerTag = function($tag){
var selectedCustomer = scope.modeldisplay;
if(selectedCustomer === undefined){
return true;
}
return false;
};
scope.tagAdded = function($tag){
scope.selectedmodel = $tag
var selectFun=scope.change;
selectFun();
};
scope.removedCustomerTag = function(){
scope.modeldisplay = undefined;
scope.selectedmodel = undefined;
};
scope.tagAdded = function($tag){
scope.selectedmodel = $tag;
scope.tagid = $tag.customer_id;
scope.change();
};
}
};
}]);
I created a fiddle that simulate my issue. I'm using ng-repeat to create some nodes. But these nodes are to be used by another library (Openlayers) which "move" (appendChild) these nodes to another place in the DOM.
So, a fight for these nodes is happening. Is there a way to tell ngRepeat to stop re-sorting, re-creating (not sure about the best term)?
http://jsfiddle.net/jonataswalker/dbxmbxu9/
Markup
<button ng-click="create()">New Record</button>
<div data-label="Created here">
<div
id="hint-{{$index}}"
class="hint--always hint--right"
data-hint="{{row.desc}}"
ng-repeat="row in rows track by row.id"
on-render>{{row.desc}}
</div>
</div>
<div id="wrap" data-label="I'd like all to be here"></div>
JS
app.controller('ctrl', ['$scope', function($scope) {
$scope.rows = [];
$scope.count = 0;
var wrap = document.getElementById('wrap');
$scope.create = function(){
var c = $scope.count++;
$scope.rows.push({
id: c,
desc: 'dummy-desc-' + c
});
};
$scope.move = function(div){
wrap.appendChild(div);
};
}]);
app.directive('onRender', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function(){
scope.move(element[0]);
});
}
}
};
}]);
At the end, I had to give up using ng-repeat. Thanks for all comments.
The solution I found is to use $compile service and let the DOM manipulation freely.
http://jsfiddle.net/jonataswalker/y4j679jp/
app.controller('ctrl', ['$scope', function($scope) {
$scope.rows = [];
$scope.count = 0;
var wrap = document.getElementById('wrap');
$scope.move = function(div){
wrap.appendChild(div);
};
}]);
app.directive('button', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope){
var div = document.getElementById('c1');
scope.create = function(){
var c = scope.count++;
var row = { id: 'hint-' + c, desc: 'any desc #' + c };
var index = scope.rows.push(row) - 1;
var html = [
'<div id="'+row.id+'"',
'class="hint--always hint--right"',
'data-hint="{{rows['+index+'].desc}}"',
'data-index="'+index+'"',
'on-render>{{rows['+index+'].desc}}</div>'
].join(' ');
angular.element(div).append($compile(html)(scope));
};
}
};
}]);
app.directive('onRender', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
$timeout(function(){
scope.move(element[0]);
}, 2000).then(function(){
$timeout(function(){
scope.rows[attr.index].desc = 'changed .... ' + attr.index;
}, 2000);
});
}
};
}]);
I have created a access key Angular directive.
angular.module('tcne.common').directive("accessKey", function () {
return {
restrict: "A",
scope: {
},
link: function (scope, element, attrs) {
var $element = $(element);
$element.attr("accesskey", attrs.accessKey);
var content = $element.html();
for (var i = 0; i < content.length; i++) {
var char = content[i];
if (char.toLowerCase() === attrs.accessKey.toLowerCase()) {
content = content.substr(0, i) + "<u>" + char + "</u>" + content.substr(i + 1);
break;
}
}
$element.html(content);
},
replace: false
};
});
It underscores the access key in the button Label and adds the access key attribute to the element. Can I somehow prevent the accesskey from setting the button in focus? It kills the purpose of keyboard short cuts
edit: Rolled my own acccess key
angular.module('tcne.common').directive('accessKey', ['$compile', '$interval', function ($compile, $interval) {
var modifierPressed = false;
$("body").keyup(function (e) {
if (modifierPressed && !e.altKey) {
modifierPressed = false;
digestScopes();
}
});
$("body").keydown(function (e) {
modifierPressed = e.altKey;
if (modifierPressed && scopes.hasOwnProperty(String.fromCharCode(e.which).toLowerCase())) {
var scope = scopes[String.fromCharCode(e.which).toLowerCase()];
scope.handle();
return;
}
if (modifierPressed) {
e.preventDefault();
digestScopes();
}
});
function digestScopes() {
for (var index in scopes) {
if (scopes.hasOwnProperty(index)) {
var scope = scopes[index]
scope.$digest();
}
}
}
function isModifierPressed() {
return modifierPressed;
}
var scopes = {};
return {
restrict: 'A',
scope: {
},
link: function (scope, element, attrs) {
var key = attrs.accessKey.toLowerCase();
var content = element.html();
var char;
for (var i = 0; i < content.length; i++) {
char = content[i];
if (char.toLowerCase() === key) {
content = content.substr(0, i) + '<u><strong ng-if="highlight()">{{char}}</strong><span ng-if="!highlight()">{{char}}</span></u>' + content.substr(i + 1);
break;
}
}
element.html(content);
var underscoreScope = scope.$new();
underscoreScope.char = char;
underscoreScope.highlight = isModifierPressed;
underscoreScope.handle = element.click.bind(element);
scopes[key] = underscoreScope;
scope.$on('$destroy', function () {
delete scopes[key];
});
$compile(element.find("u"))(underscoreScope);
},
replace: false
};
}]);
It also highlights the access key button when alt key is pressed which is nice
Any pit falls with this code? Thanks
Found a pitfall, element.html and then $compile will break any directives inside the element that is already compiled. So I changed to
var captionElement = element.contents().first(":text");
var content = captionElement.text();
And then I add my custom content like
var view = $("<span>").html(content);
captionElement.replaceWith(view);
$compile(view)(vm);
Please let me know if this is considered bad practice
I'm learning AngularJs now, and trying to write my first directives.
So i have a question: is there any way to pass complex options to directive. For example i want to write directive wrapper for slick grid. It has a lot of options, columns for example, and it's imposible to configure it using attributes. Can i do simething like this?
<s-grid>
<s-grid-columns>
<s-grid-column id="title" title="Title"/>
<s-grid-column id="duration" title="Duration"/>
</s-grid-columns>
...
</s-grid>
And get all this properties as json object in s-grid directive?
So i could do it. Is it here any mistakes?
module
.directive('sGrid', [function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.columns = [];
this.setColumns = function(columns) {
$scope.columns = columns;
};
},
link: function (scope, element, attrs, controller, transclude) {
// for clearer present I initialize data right in directive
// start init data
var columns = scope.columns;
var options = {
enableCellNavigation: true,
enableColumnReorder: true
};
var data = [];
for (var i = 0; i < 50000; i++) {
var d = (data[i] = {});
d["id"] = "id_" + i;
d["num"] = i;
d["title"] = "Task " + i;
d["duration"] = "5 days";
d["percentComplete"] = Math.round(Math.random() * 100);
d["start"] = "01/01/2009";
d["finish"] = "01/05/2009";
d["effortDriven"] = (i % 5 == 0);
}
// end init data
// finally render layout
scope.grid = new Slick.Grid(element, data, columns, options);
$(window).resize(function () {
scope.grid.resizeCanvas();
})
}
}
}])
.directive("sGridColumns", function(){
return {
require: '^sGrid',
restrict: 'E',
controller: function($scope) {
var columns = $scope.columns = [];
this.addColumn = function(pane) {
columns.push(pane);
};
},
link: function (scope, element, attrs, gridCtrl){
gridCtrl.setColumns(scope.columns);
}
}
})
.directive('sGridColumn', function() {
return {
require: '^sGridColumns',
restrict: 'E',
transclude: 'element',
link: function (scope, element, attrs, gridCtrl) {
scope.field = scope.field || scope.id;
scope.title = scope.title || scope.field;
gridCtrl.addColumn({
id: attrs.id,
field: attrs.field || attrs.id,
name: attrs.name || attrs.field || attrs.id
});
}
};
});
And declaration:
<s-grid>
<s-grid-columns>
<s-grid-column id="title" name="Title"></s-grid-column>
<s-grid-column id="duration" name="Duration"></s-grid-column>
</s-grid-columns>
</s-grid>