contenteditable in Angular js - angularjs

I am using contenteditable directive of angular js. and i try to access value of a tag using ng-model.but it gives undefined.
This is my html where i use contenteditable directive
<span ng-switch on="editgroupChatFieldFlag" style="width: 87%;margin-right: 0;float:left;" ng-hide="displatSessionTitleFlag==false">
<h5 style="float:left;margin: 7px 14px 0;margin-right: 0;">Chat Session : </h5>
<a ng-switch-when="true" contenteditable="true" strip-br="true" ng-model="chatMessageName" style="margin-right: 0;width: 59%;margin-left: 4px;" ng-keydown="changeNameFunc($event)">{{secondPresonName}}</a>
<a ng-switch-when="false" style="margin-right: 0;width: 59%;margin-left: 4px;">{{secondPresonName}}</a>
</span>
and this contenteditable directive
app.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);
}
}
};
});
can anyone tell me how i get a tag value using ng-model.

You are dealing with a scope issue (ng-switch creates a child scope). You need to either use . or $parent.
Example with $parent update ng-model to use $parent.chatMessageName:
<a ng-switch-default contenteditable="true"
strip-br="true"
ng-model="$parent.chatMessageName" >Enter</a>
Example with dot/object:
JS
--controller
$scope.myType = {
chatMessageName: ''
};
Html:
<!-- html -->
<a ng-switch-default
contenteditable="true"
strip-br="true"
ng-model="myType.chatMessageName" >Enter</a>
<a>{{"Text "+ myType.chatMessageName}}</a>
Educate yourself: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Side Note: when you are posting your question: include the fiddle in your question (not the comment) and remove unnecessary code, and format so its more readable.

Related

Angular Directive Pass back scope variable

I am trying to make a directive that is basically a text box with a max length counter on it. My directive is below. Basically a text box that will tell the user that they only have x number of characters left.
angular.module('InputApp', []);
angular.module('InputApp').directive('textAreaCounter', function () {
return {
templateUrl: '/Content/Directives/Inputs/TextAreaCounter.html',
restrict: 'AE',
multiElement: true,
scope: {
text: '='
},
link: function (scope, elem, attrs) {
if (scope.text == undefined || scope.text == '') {
scope.CharactersLeft = attrs.totalCharacters;
} else {
scope.CharactersLeft = attrs.totalCharacters - scope.text.length;
}
scope.TextValueChanged = function () {
scope.CharactersLeft = attrs.totalCharacters - scope.text.length;
}
}
}
});
The template html is:
<div class="row">
<div class="col-md-12">
<textarea ng-model="text" ng-change="TextValueChanged()" autogrow rows="5"></textarea>
</div>
</div>
<div class="row">
<div class="col-md-12 top-left smallText">
You have {{CharactersLeft}}.
</div>
</div>
And I use the directive like this.
<text-Area-Counter text="WholeDeletionText" total-Characters="250"></text-Area-Counter>
The issue that I am having is that the value 'WholeDeletionText' is not being updated by the directive.
My expectation is that the scope.WholeDeletionText in the parent would update with the text that was written in the textarea in the directive. At least my understanding of the isolated scope having the '=' sign is that they share it with the parent. What am I doing wrong or is there a better way to do it?
The problem occurs because you have not WholeDeletionText variable in the application scope.
To fix it you need:
1) create controller and initialize WholeDeletionText variable
.controller("MyController", function ($scope) {
$scope.WholeDeletionText = '123';
});
2) add ng-controller="MyController" directive in your main html file.
<body ng-app="InputApp" ng-controller="MyController">
<div>
<text-Area-Counter text="WholeDeletionText" total-Characters="250"></text-Area-Counter>
</div>
</body>
Full code see in Plunk.
Exactly, adding to the #Roman's point, when you use '=' in declaring the directive's scope variable it should be declared the the parent controller as well, otherwise you can use '#'.

angular bind html tags from controller to html view

I need to render the $scope.htmlView tags in to html view.
I already tried using ng-bind-html. It renders the html tags but scope variable values will not appear.
How can I render both html tags and and scope variable values?
This is the controller:
$scope.newObj = {
billStatus : true;
eventTime : "2015-01-10"
};
$scope.htmlView = '<p>{{newObj.eventTime}}</p> <div style="margin-top: -15px;"><md-checkbox ng-checked="{{newObj.billStatus}}" style="margin-left: 0px;" aria-label="Bilable"><span style="margin-left:0px;">Bilable</span> </md-checkbox></div>'
Expected result is:
<p> 2015-01-10</p>
<div style="margin-top: -15px;">
<md-checkbox ng-checked="true" style="margin-left: 0px;" aria- label="Bilable">
<span style="margin-left:0px;">Bilable</span>
</md-checkbox>
</div>
I search over the internet over days and still could't find out a way to figure out this. please help me. thank you.
You have to do 2 things.
Use data-ng-bind-html=""
Use $sce.trustAsHtml(string)
UPDATED:
If you wont to use angular expressions, you have to compile them using
$compile.
You can read more via this $SCE
I will tell you a long way but it will help you.Make a custom directive like this.
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
Use as
<span dynamic="{{htmlView}}" >
Hi please check this fiddle
https://plnkr.co/edit/iqNltdDYv2n9Agke0C2C?p=preview
HTML
<div ng-controller="ExampleController">
<p >{{newObj.eventTime}}</p>
<p dynamic="htmlView"></p>
</div
and JS
(function(angular) {
'use strict';
angular.module('bindHtmlExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.newObj = {
billStatus : true,
eventTime : "2015-01-10"
}
$scope.htmlView = '<p> {{newObj.eventTime}}</p> <div style="margin-top: -15px;">Hello <md-checkbox ng-checked="{{newObj.billStatus}}" style="margin-left: 0px;" aria-label="Bilable"><span style="margin-left:0px;">Bilable</span> </md-checkbox></div>'
}])
.directive('dynamic', function($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(attrs.dynamic, function(html) {
element[0].innerHTML = html;
$compile(element.contents())(scope);
});
}
};
});
})(window.angular);

uib-timepicker broken when $compile

I want to use a 'uib-timepicker' inside a popover, so as I read, I need to $compile the html to bind it to angular scope:
app.directive('bsPopover', function($compile) {
return function(scope, element, attrs) {
element.find('#formula-nro').popover({
html : true,
title : '<span>Title</span>',
content : function() {
return $compile($('#formula_form_nro').html())(scope);
// return $('#formula_form_nro').html();
}
});
};
});
If I compile the code, the component brokes, but when I don't, it's shows and behaves correctly, but it is not binded to angular scope.
This is the HTML code of the component:
<div id="formula_form_nro" class="padding-block hide">
.....
<uib-timepicker ng-model="time" minute-step="15" readonly-input="true"> </uib-timepicker>
.....
</div>
The following is the popover button:
<button id="formula-nro" type="button" class="btn btn-default" data-container="body" data-toggle="popover" data-placement="top">f(x)</button>
The button is inside a ng-repeat:
<div ng-repeat="item in items" bs-popover>
It looks like a bug, but I don't know if am I doing well. Maybe there is a workaround for this ..
thanks,

Angularjs change view template after link clicked

I am trying to change html template after link is clicked. Value is boolean, initial value is true and appropriate template is loaded, but when value changed to false new template is not loaded, I don't know the reason. When initial value of boolean is true other template is loaded successfully, but on method called not. Please, help.
Here is my code:
TaskCtrl
app.controller('TasksCtrl', ['$scope', 'TaskService', function ($scope, TaskService) {
// initialize function
var that = this;
that.newTask = true;
that.name = "My name is Nedim";
that.templates = {
new: "views/task/addTask.html",
view: "views/task/viewTask.html"
};
// load all available tasks
TaskService.loadAllTasks().then(function (data) {
that.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
that.items.concat(data.data);
});
that.changeTaskView = function(){
that.newTask = false;
console.log("New task value: " + that.newTask);
};
return $scope.TasksCtrl = this;
}]);
task.html
<!-- Directive showing list of available tasks -->
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView()"></entity-task-list>
</div>
<div class="col-sm-6" ng-controller="TaskDetailCtrl as taskDetailCtrl">
<!-- form for adding new task -->
<div ng-if="taskCtrl.newTask" ng-include="taskCtrl.templates.new"></div>
<!-- container for displaying existing tasks -->
<div ng-if="!taskCtrl.newTask" ng-include="taskCtrl.templates.view"></div>
</div>
</div>
entityList directive
app.directive('entityTaskList', function () {
return {
restrict: 'E',
templateUrl: 'views/task/taskList.html',
scope: {
items: '='
},
bindToController: true,
controller: 'TasksCtrl as taskCtrl',
link: function (scope, element, attrs) {
}
};
});
directive template
<ul class="list-group">
<li ng-repeat="item in taskCtrl.items" class="list-group-item">
<a ng-click="taskCtrl.changeTaskView()">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"> </span>
<span>{{item.name}}</span>
<span class="task-description">{{item.description}}</span>
</a>
</li>
{{taskCtrl.newTask}}
Without any plunker or JSFiddle I can't tell for sure, but it might be issue with ng-if. I'm thinking of two workarounds.
First that I think is better. Use only 1 ng-include and only change the template.
HTML:
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView('view')"></entity-task-list>
...
<div ng-include="taskCtrl.currentTemplate"></div>
JS:
that.currentTemplate = that.templates.new;
...
that.changeTaskView = function(template) {
that.currentTemplate = that.templates[template];
};
Or if you don't like this solution, try with ng-show instead of ng-if. With ng-show the elements will be rendered with display: none; property when the page loads, while with ng-if they will be rendered when the passed value is true.
Hope this helps you.

How to show form input errors using AngularJS UI Bootstrap tooltip?

For example I have the form where I am showing form input errors.
I need to show red badge (with 'hover to show errors') near input label if there are some errors. If user will hover this red badge - he will see list of errors using AngularJS UI Bootstrap tooltip.
I don't want to put list of errors into tooltip-html-unsafe attribute, because it is not convenient to edit and maintain.
This code is more declarative:
<validation-tooltip ng-show="appForm.number.$invalid && appForm.number.$dirty">
<ul>
<li ng-show="appForm.number.$error.required">this field is required</li>
<li ng-show="appForm.number.$error.number">should be number</li>
<li ng-show="appForm.number.$error.min">minimum - 5</li>
<li ng-show="appForm.number.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
than this code:
<span tooltip-html-unsafe="{{<ul><li>This field is required;</li><li>...</li></ul>}}">hover to show errors</span>
How can I write such validation-tooltip directive using AngularJS UI Bootstrap tooltip?
Or maybe can you suggest another approach to maintain validation error messages?
Demo Fiddle
Validation Tooltip Directive
The validationTooltip is the main directive. It has the following responsibilities:
Define the tool tip template through its transcluded contents
Keep track of validation expressions so that they can be evaluated with each digest cycle.
Expose a controller API for allowing valiationMessage directives to register themselves
Provide a 'target' attribute on the directive to specify which form field the badge (and the associated tooltip) will be bound to
Additional Notes
The tooltip template uses the transclusion function from the link function to bind the template to the directive's scope. There are two properties that are within scope that the template can bind to:
$form: Bound to the form model defined in parent scope. i.e. $scope.myForm
$field: Bound to the form.name model in parent scope. i.e. $scope.myForm.myInput
This allows the template to bind to validation properties such as $valid, $invalid, $pristine, $dirty, and $error without referring to the form name or the input field's name directly. For example, all of the following expressions are valid binding expressions:
$form properties:
`$form.$valid`
`$form.$invalid`
`$form.$dirty`
`$form.$pristine`
`$form.$error.required` etc...
$field properties:
`$field.$valid`
`$field.$invalid`
`$field.$dirty`
`$field.$pristine`
`$field.$error.required` etc...
Directive Implementation
app.directive('validationTooltip', function ($timeout) {
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {},
template: '<span class="label label-danger span1" ng-show="errorCount > 0">hover to show err</span>',
controller: function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
});
},
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var badge = element.find('.label');
var tooltip = angular.element('<div class="validationMessageTemplate tooltip-danger" />');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
badge.tooltip({
placement: 'right',
html: true,
title: clone
});
});
});
}
}
});
Validation Message Directive
The validationMessage directive keeps track of the validation messages to display in the tooltip. It uses ng-if to define the expression to evaluate. If there is no ng-if found on the element, then the expression simply evaluates to true (always shown).
app.directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true );
}
}
});
Usage in HTML
Add a form with a name attribute
Add one or more form fields - each with a name attribute and an ng-model directive.
Declare a <validation-tooltip> element with a target attribute referring to the name of one of the form fields.
Apply the validation-message directive to each message with an optional ng-if binding expression.
<div ng-class="{'form-group': true, 'has-error':form.number.$invalid}">
<div class="row">
<div class="col-md-4">
<label for="number">Number</label>
<validation-tooltip target="number">
<ul class="list-unstyled">
<li validation-message ng-if="$field.$error.required">this field is required </li>
<li validation-message ng-if="$field.$error.number">should be number</li>
<li validation-message ng-if="$field.$error.min">minimum - 5</li>
<li validation-message ng-if="$field.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
</div>
</div>
<div class="row">
<div class="col-md-4">
<input type="number" min="5" max="20" ng-model="number" name="number" class="form-control" required />
</div>
</div>
</div>
#pixelbits answer is great. I used this instead:
<div class="form-group" ng-class="{ 'has-error': form.name.$dirty && form.name.$invalid }">
<label for="name" class="col-sm-4 control-label">What's your name?</label>
<div class="col-sm-6">
<input class="form-control has-feedback" id="name" name="name"
required
ng-minlength="4"
ng-model="formData.name"
tooltip="{{form.name.$valid ? '' : 'How clients see your name. Min 4 chars.'}}" tooltip-trigger="focus"
tooltip-placement="below">
<span class="glyphicon glyphicon-ok-sign text-success form-control-feedback" aria-hidden="true"
ng-show="form.name.$valid"></span>
</div>
</div>
The technique is ui-bootstrap's tooltip and set the tooltip text to '' when valid.
http://jsbin.com/ditekuvipa/2/edit
Great answer from #pixelbits. I used his directives and modified them slightly to allow the tooltip to display over the actual input as some users requested.
angular.module('app')
.directive('validationTooltip', ['$timeout', function ($timeout) {
function toggleTooltip(scope) {
if (!scope.tooltipInstance) {
return;
}
$timeout(function() {
if (scope.errorCount > 0 && (scope.showWhen == undefined || scope.showWhen())) {
scope.tooltipInstance.enable();
scope.tooltipInstance.show();
} else {
scope.tooltipInstance.disable();
scope.tooltipInstance.hide();
}
});
}
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {
showWhen: '&',
placement: '#',
},
template: '<div></div>',
controller: ['$scope', function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
toggleTooltip($scope);
});
}],
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var tooltip = angular.element('<div class="validationMessageTemplate" style="display: none;"/>');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
var targetElm = $('[name=' + attr.target + ']');
targetElm.tooltip({
placement: scope.placement != null ? scope.placement : 'bottom',
html: true,
title: clone,
});
scope.tooltipInstance = targetElm.data('bs.tooltip');
toggleTooltip(scope);
if (scope.showWhen) {
scope.$watch(scope.showWhen, function () {
toggleTooltip(scope);
});
}
});
});
}
}
}]);
The major change is that the directive uses jQuery to find the target element (which should be an input) via the name attribute, and initializes the tooltip on that element rather than the element of the directive. I also added a showWhen property to the scope as you may not always want your tooltip to show when the input is invalid (see example below).
The validationMessage directive is unchanged
angular.module('app').directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true);
}
}
});
Usage in Html is also similar, with just the addition of showWhen if you want:
<div class="form-group" ng-class="{ 'has-error' : aForm.note.$invalid && (aForm.note.$dirty) }">
<label class="col-md-3 control-label">Note</label>
<div class="col-md-15">
<textarea
name="note"
class="form-control"
data-ng-model="foo.Note"
ng-required="bar.NoteRequired"></textarea>
<validation-tooltip target="note" show-when="aForm.note.$invalid && (aForm.note.$dirty)">
<ul class="validation-list">
<li validation-message ng-if="$field.$error.required">Note required</li>
</ul>
</validation-tooltip>
</div>
</div>
you can actually just use the tooltip-enable property:
<div class="showtooltip" tooltip-placement="left" tooltip-enable="$isValid" tooltip="tooltip message"></div>
My goal was to leverage both ng-messages and ui-bootstrap popover for validation feedback. I prefer the popover vs. the tooltip as it displays the help-block styles more clearly.
Here's the code:
<!-- Routing Number -->
<div class="form-group-sm" ng-class="{ 'has-error' : form.routingNumber.$invalid && !form.routingNumber.$pristine }">
<label class="control-label col-sm-4" for="routing-number">Routing #</label>
<div class="col-sm-8">
<input class="form-control input-sm text-box"
id="routing-number"
name="routingNumber"
ng-model="entity.ROUTINGNUM"
popover-class="help-block"
popover-is-open="form.routingNumber.$invalid"
popover-trigger="none"
required
uib-popover-template="'routing-number-validators'"
string-to-number
type="number" />
</div>
<!-- Validators -->
<script type="text/ng-template" id="routing-number-validators">
<div ng-messages="form.routingNumber.$error">
<div ng-messages-include="/app/modules/_core/views/validationMessages.html"></div>
</div>
</script>
</div>
Here is the validationMessages.html
<span ng-message="required">Required</span>
<span ng-message="max">Too high</span>
<span ng-message="min">Too low</span>
<span ng-message="minlength">Too short</span>
<span ng-message="maxlength">Too long</span>
<span ng-message="email">Invalid email</span>
Note: I had to upgrade to jQuery 2.1.4 to get the uib-popover-template directive to work.
Dependencies:
string-to-number
UI Bootstrap
ng-messages

Resources