Angular - How to "bind" when "oninput" with "contenteditable span"? - angularjs

"<span contenteditable>{{ line.col2 }}</span>"
Hello,
This code is good at initialisation but if I edit the span, no bing is send and my array model never updated...
So, I have tried this :
<span contenteditable ng-model="line.col2" ng-blur="line.col2=element.text()"></span>
But "this.innerHTML" does not exist.
What can I do ?
Thank at all ;-)

you can remove the ng-blur and you will have to add this directive:
<span contenteditable ng-model="myModel"></span>
Here is the directive taken from the documentation:
.directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
}
});

I only will point you to possible solution, then you need to parse/clean HTML better.
<span contenteditable data-ng-blur="bar = $event.target.innerHTML">
{{bar}}
</span>
// upd.
Angular events such as click, blur, focus, ... - fired with scope context, e.g. this will be current scope.
Use $event, be happy.

Solution with Mirrage and gab help :
<span contenteditable="true" ng-model="ligne.col2">{{ ligne.col2 }}</span>
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('blur', function() {
scope.$apply(function() {
ctrl.$setViewValue(element.html());
});
});
// model -> view
ctrl.$render = function() {
element.html(ctrl.$viewValue);
};
// load init value from DOM
ctrl.$render();
}
};
});
Thank at all ;-)

Related

angular directive was only called once

I am using a directive to build a custom validator and it works fine. But, it was called only once! If my "roleItems" are updated, this directive was not called again! How can it be called every time when "roleItems" are updated?
Here are the markups. And "Not-empty" is my directive.
<form name="projectEditor">
<ul name="roles" ng-model="project.roleItems" not-empty>
<li ng-repeat="role in project.roleItems"><span>{{role.label}}</span> </li>
<span ng-show="projectEditor.roles.$error.notEmpty">At least one role!</span>
</ul>
</form>
This is my directive. It should check if the ng-model "roleItems" are empty.
angular.module("myApp", []).
directive('notEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.notEmpty = function (modelValue, viewValue) {
if(!modelValue.length){
return false;
}
return true;
};
}
};
});
Main purpose of validator is validate ngModel value of user input or model change, so it should be uset to checkbox/textara/input and etc. You cant validate ng-model of everything. Angular is enough intelligent to knows that ng-model makes no sens so he is just ignoring it .
I you wanna change only error message you can check it via .length property. If you wanna make whole form invalid , i suggest you to make custom directive , put it on , and then in validator of this directive check scope.number.length > 0
Basically just adjust your directive code to input element and hide it .... via css or type=hidden, but dont make ngModel="value" its not make sense because ng-model is expecting value which can be binded and overwriteen but project.roleItems is not bindable! so put ng-model="dummyModel" and actual items to another param ...
<input type="hidden" ng-model="dummyIgnoredModel" number="project.roleItems" check-empty>
angular.module("myApp", []).
directive('checkEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.push(function (modelValue, viewValue) {
if(!scope.number.length){
return false;
}
return true;
});
//now we must "touch" ngModel
scope.$watch(function()
{
return scope.number
}, function()
{
ctrl.$setViewValue(scope.number.length);
});
}
};
});

Contenteditable with ng-model doesn't work

I'm trying to store the value of a contenteditable to my JS code. But I can't find out why ng-model doesn't work in this case.
<div ng-app="Demo" ng-controller="main">
<input ng-model="inputValue"></input>
<div>{{inputValue}}</div> // Works fine with an input
<hr/>
<div contenteditable="true" ng-model="contentValue"></div>
<div>{{contentValue}}</div> // Doesn't work with a contenteditable
</div>
Is there a workaround to do that ?
See : JSFiddle
Note: I'm creating a Text editor, so the user should see the result, while I'm storing the HTML behind it. (ie. user see: "This is an example !", while I store: This is an <b>example</b> !)
contenteditable tag will not work directly with angular's ng-model because the way contenteditable rerender the dom element on every change.
You have to wrap it with a custom directive for that:
JS:
angular.module('customControl', ['ngSanitize']).
directive('contenteditable', ['$sce', function($sce) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$evalAsync(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
}]);
HTML
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
Source it from the original docs
Just move the read function call into $render
angular.module('customControl', ['ngSanitize']).
directive('contenteditable', ['$sce', function($sce) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
read(); // initialize
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$evalAsync(read);
});
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
}]);
Neither of the other answers worked for me. I needed the model's initial value to be rendered when the control was initialized. Instead of calling read(), I used this code inside the link function:
ngModel.$modelValue = scope.$eval(attrs.ngModel);
ngModel.$setViewValue(ngModel.$modelValue);
ngModel.$render()

How to reset custom input directive and its parent form to $pristine

I've implemented a custom input directive - counter with a reset capability. The directive has require: "ngModel".
I am resetting the pristine state of the directive's ngModel with $setPristine(). Unlike $setDirty(), $setPristine() does not touch the $pristine state of the parent form.
Q: How do I "notify" the parent form that this directive is no longer "dirty", such that the parent form could have its $pristine state reset?
Bear in mind that just calling form.$setPristine() is not enough as there may be other "dirty" controls in the form, which my directive wouldn't (and shouldn't) know about.
This is the directive's link function:
link: function(scope, element, attrs, ngModel){
var original;
ngModel.$render = function(){
original = scope.counter = ngModel.$viewValue;
};
scope.up = function(){
ngModel.$setViewValue(++scope.counter);
};
scope.reset = function(){
scope.counter = original;
ngModel.$setViewValue(scope.counter);
ngModel.$setPristine(); // this sets $pristine on the directive, but not the form
};
}
And here's how it is used:
<div ng-form="form">
<counter ng-model="count"></counter>
</div>
plunker
As of Angular 1.3.x, there is no built-in solution.
form.$setPristine() sets pristine on all its child controls. (link to code)
ngModel.$setPristine() only sets $pristine on itself (link to code)
One way to solve this is to create a directive that lives alongside a form directive and hijacks form.$setDirty to track dirty controls count. This is probably best done in a pre-link phase (i.e. before child controls start registering themselves).
app.directive("pristinableForm", function() {
return {
restrict: "A",
require: ["pristinableForm", "form"],
link: function(scope, element, attrs, ctrls) {
var me = ctrls[0],
form = ctrls[1];
me.form = form;
me.dirtyCounter = 0;
var formSetDirtyFn = form.$setDirty;
form.$setDirty = function() {
me.dirtyCounter++;
formSetDirtyFn();
};
},
controller: function() {
this.$notifyPristine = function() {
if (this.dirtyCounter === 0) return;
if (this.dirtyCounter === 1) {
this.dirtyCounter = 0;
if (this.form) this.form.$setPristine();
} else {
this.dirtyCounter--;
}
};
}
};
});
Then, the custom input directive needs to require: ["ngModel", "^pristinableForm"] and call pristinableForm.$notifyPristine() in its reset function:
scope.reset = function(){
if (ngModel.$dirty){
scope.counter = original;
ngModel.$setViewValue(scope.counter);
ngModel.$setPristine();
pristinableForm.$notifyPristine();
}
};
The usage is:
<div ng-form="form" pristinable-form>
<counter ng-model="count1"></counter>
<counter ng-model="count2"></counter>
<input ng-model="foo" name="anotherControl">
</div>
plunker
This is a not so good solution. Iterate through controls attached to the form and check if there still a dirty one.
i used the method explain here to get the controls.
Plunker
app.directive("counter", function(){
return {
require: "ngModel",
template: '<button ng-click="up()">{{counter}}</button><button ng-click="reset()">reset</button>',
link: function(scope, element, attrs, ngModel){
var original;
ngModel.$render = function(){
original = scope.counter = ngModel.$modelValue;
};
scope.up = function(){
ngModel.$setViewValue(++scope.counter);
};
scope.reset = function(){
scope.counter = original;
ngModel.$setViewValue(scope.counter);
ngModel.$setPristine();
// check if one of the controls attached to the form is still dirty
var dirty = false;
angular.forEach(scope.form, function(val, key) {
if (key[0] != '$') {
if (val.$dirty) {
dirty = true;
}
}
});
if(!dirty) scope.form.$setPristine();
};
}
};
});

Dynamically disable all ng-clicks within an element

I have a directive disable-ng-clicks and under certain conditions, I want to prevent all ng-clicks that are children of the directive. Here is some example markup:
<div disable-ng-clicks> <!-- directive -->
<a ng-click="someAction()"></a>
<div ng-controller="myController">
<a ng-click="anotherAction()"></a>
<a ng-click="moreActions()"></a>
</div>
</div>
If these were normal hyperlinks, I could do something like this in the link function:
function(scope, iElement, iAttrs) {
var ngClicks = angular.element(iElement[0].querySelectorAll('[ng-click]'));
ngClicks.on('click', function(event) {
if(trigger) { // a dynamic variable that triggers disabling the clicks
event.preventDefault();
}
});
}
But this does not work for ng-click directives. Is there another way to accomplish this?
Here is the best I could come up with. I created a new directive to replace ng-click:
directive('myClick', ['$parse', function($parse) {
return {
restrict: 'A',
compile: function($element, attrs) {
var fn = $parse(attrs.myClick);
return function (scope, element, attrs) {
var disabled = false;
scope.$on('disableClickEvents', function () {
disabled = true;
});
scope.$on('enableClickEvents', function () {
disabled = false;
});
element.on('click', function (event) {
if (!disabled) {
scope.$apply(function () {
fn(scope, { $event: event });
});
}
});
};
}
}
}]);
So in a different directive, I can have:
if (condition) {
scope.$broadcast('disableClickEvents');
}
and when I want to re-enable:
if (otherCondition) {
scope.$broadcast('enableClickEvents');
}
I don't like having to use a different directive for ng-click, but this is the best plan I could think of.
You are catching 'click' event on parent only because of JS events bubbling, so if you want to intercept it on all descendants, so your directive should get all descendants of current element, listen their 'click' event and prevent it if necessary.
This directive will iterate over all child elements, check to see if they have an ng-click attribute, and if they do, it will disable any registered click event handlers:
directive('disableNgClicks', function(){
return {
restrict: 'E',
link: function(scope, elem, attrs){
angular.forEach(elem.children(), function(childElem) {
if (childElem.outerHTML.indexOf("ng-click") > -1) {
angular.element(childElem).off('click');
}
});
}
}
})
Plunker demo
I know this is 2 years ago but I needed to do something similar and came up with a rather simple solution.
The object:
items: {
item1 : {
selected: 0,
other: 'stuff'
},
item2 : {
selected : 1,
other: 'stuff'
}
}
The HTML:
<div ng-repeat="item in items" ng-model="item.selected" ng-click="selectParent($event)">
<div ng-click="item.selected ? selectChild($event) : null">Child</div>
</div>
The functions:
$scope.selectParent = function($event) {
var itemScope = angular.element($event.currentTarget)scope().item;
itemScope.selected = !itemScope.selected;
}
$scope.selectChild = function($event) {
$event.stopPropagation;
console.log('I only get triggered if parent item is selected');
}
This is a pretty raw example of what I did. You should probably be using a directive that gives you $scope rather than angular.element($event.currentTarget).scope... either way the simplistic inline if logic is what I was really getting at. You can call a function or not based on some value.

angular dynamically adding contenteditable directive doesn't work?

Runnable CODE: my code
I try to dynamically add contenteditable directive to the <div> element when it is double-clicked.
when I put contenteditable directive to <div> in the beginning, the ng-model still work, but when I remove it and add it dynamically in ng-dblclick callback, ng-model seams not work anymore.
It's kind of like this Question.
but I can't think of a angular-friendly way to finish my work here.
How can I fix this?
code: html
<div ng-app="customControl">
<form name="myForm" ng-controller="mainControl">
<!-- Dynamically adding contenteditable directive : doesn't work -->
<div name="myWidget" ng-model="userContent" ng-click="enableEdit($event)"
strip-br="true"
required>Change me!</div>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</div>
code: js
angular.module('customControl', []).
controller('mainControl', function($scope) {
$scope.enableEdit = function(e) {
$(e.target).attr('contenteditable', '');
}
})
.directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser
// leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});

Resources