patternjavascriptMinor
A directive allowing to make a whole DOM subtree readonly
Viewed 0 times
allowingwholedirectivemakedomsubtreereadonly
Problem
What do you think about this directive? For each
An
The purpose is to allow to make a whole subtree readonly, no matter what's inside. It works, but I can imagine there can be performance problems and/or interferences with
An explanation why I'm not simply binding to a scope variable
While I really appreciate Thomas' answer, I disagree with this part:
Again, I highly recommend that you simply bind to a scope variable instead of checking for the presence of a class further up the tree.
I really find it both complicated and error-prone: I'm having a big form in which many fields already have their
input, it traverses the DOM towards the root and if it finds an element with the class readonly, it makes the input readonly.An
ng-readonly directive on the input itself get honored: The input becomes readonly whenever ng-readonly evaluates to true or any enclosing element has the class readonly..directive("input", function($parse) {
return {
restrict: "E",
link: function($scope, element, attr) {
if (attr.type === "radio" || attr.type === "checkbox") {
return; // for simplicity, let's ignore them
}
var org = attr.ngReadonly;
if (!org && attr.readonly) {
return; // readonly seems to be set manually, so let's not touch it
}
$scope.$watch(function() {
var readonly = $parse(org)($scope);
for (var e = element; e.length && !readonly; e=e.parent()) {
readonly = e.hasClass("readonly");
}
console.log(element.attr("ng-model"), readonly)
attr.$set("readonly", !!readonly);
});
},
};
})The purpose is to allow to make a whole subtree readonly, no matter what's inside. It works, but I can imagine there can be performance problems and/or interferences with
ng-readonly setting the value independently of this directive.An explanation why I'm not simply binding to a scope variable
While I really appreciate Thomas' answer, I disagree with this part:
Again, I highly recommend that you simply bind to a scope variable instead of checking for the presence of a class further up the tree.
I really find it both complicated and error-prone: I'm having a big form in which many fields already have their
readonly logic (e.g., bank name is read only when a known BIC is entered). Now, I'd have to add is_readonly to all existing ng-readonly directives and add the directiSolution
Generally speaking, you should avoid looking outside the current directive for anything that would affect your internal state (bar events). The preferred method of communication is scopes. Here is how I would recommend you do this.
Simply toggling
Now, if there is a genuine need to have the behaviour you described, then you have 2 options.
-
Stick with your current implementation, but this will not be performant, since your DOM traversal will fire any time the scope changes, for any reason, anywhere up the scope tree.
-
Rewrite to use directive's
Here is how you would do the latter.
This tells angular that it should pass you a reference to the readonly controller if found, or null otherwise. In this way, you can tell at link time whether the parent node exists. This saves you from performing the DOM traversal yourself, and should be faster too.
Again, I highly recommend that you simply bind to a scope variable instead of checking for the presence of a class further up the tree.
Simply toggling
$scope.is_readonly from MyCtrl is enough to disable all child inputs that have ng-readonly="is_readonly", and it's bindable too, so you can add/remove readonly status at runtime.Now, if there is a genuine need to have the behaviour you described, then you have 2 options.
-
Stick with your current implementation, but this will not be performant, since your DOM traversal will fire any time the scope changes, for any reason, anywhere up the scope tree.
-
Rewrite to use directive's
require property.Here is how you would do the latter.
.directive("readonly", function() {
return {
restrict: "C",
link: function() {},
controller: function() {},
}
})
.directive("input", function()
return {
restrict: "E",
// search for optional parent directive named `readonly`
require: "^?readonly",
// 4th parameter is `readonly` controller or null.
link: function($scope, element, attr, has_readonly) {
var org = attr.ngReadonly;
if (org) {
return; // readonly seems to be set manually, so let's not touch it
} else if (has_readonly) {
attr.$set("readonly", true)
}
},
};
})This tells angular that it should pass you a reference to the readonly controller if found, or null otherwise. In this way, you can tell at link time whether the parent node exists. This saves you from performing the DOM traversal yourself, and should be faster too.
Again, I highly recommend that you simply bind to a scope variable instead of checking for the presence of a class further up the tree.
Code Snippets
<div ng-controller="MyCtrl">
<div>
<input ng-readonly="is_readonly" />
</div>
<div>
<div>
<input ng-readonly="is_readonly" />
</div>
</div>
</div>.directive("readonly", function() {
return {
restrict: "C",
link: function() {},
controller: function() {},
}
})
.directive("input", function()
return {
restrict: "E",
// search for optional parent directive named `readonly`
require: "^?readonly",
// 4th parameter is `readonly` controller or null.
link: function($scope, element, attr, has_readonly) {
var org = attr.ngReadonly;
if (org) {
return; // readonly seems to be set manually, so let's not touch it
} else if (has_readonly) {
attr.$set("readonly", true)
}
},
};
})Context
StackExchange Code Review Q#57515, answer score: 7
Revisions (0)
No revisions yet.