I have a form in AngularJS. In the form I have a field named "description".
If user enters description as:
This is description:
1)point 1
2)point 2
I am saving this as :
"This is description:<br/>1)point 1 <br/> 2)point 2"
Now after saving it,to show it on page I am using something like :
<span class="summary"><em ng-bind-html="(x.DES)"></em></span>
This code is working.
If users click on the record then I am loading the form in edit mode :
the form having the line in edit mode to show the description:
<textarea ng-focus="onFocusDescrption()" maxlength="600" name="cepDes" class="form-control" rows="3" cols="16" ng-model="description" ng-disabled="isDescDisable" placeholder="Enter a description ..." id="description"></textarea>
Now the problem comes here. In controller I am setting the model value as :
$scope.description = $scope.timeEntry.DES;
Where in $scope.timeEntry.DES is having the value which is saved. The value is displayed in textarea having <br>.
It doesn't seem to be possible to use markup in a textarea as you can see in this plunkr I have created, and more information in this post. A solution would be to use a directive which allows for editable content. This way you can use a div for displaying and editing:
<div ng-bind-html="modelname"
contenteditable="true"
ng-model="modelname">
</div>
The directive (also on github):
app.directive("contenteditable", function() {
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attrs, ngModel) {
function read() {
ngModel.$setViewValue(element.html());
}
ngModel.$render = function() {
element.html(ngModel.$viewValue || "");
};
element.bind("blur keyup change", function() {
scope.$apply(read);
});
}
};
});
Related
I'm trying to build an Angular JS form. I'd like user to be able to set the focus on a text field when entering data in text field.
Thanks
If you want default autofocus to a particular text field then you can add a directive like below-
.directive('autoFocus', function($timeout) {
return {
restrict: 'A',
link: function($scope, $element) {
$timeout(function() {
$element[0].focus();
}, 0);
}
};
})
And use it with text box like -
You can use ng-focus
<input type="text" ng-focus="focus=true;blur=false;" ng-blur="blur=true;focus=false;"/>
<p>focus: {{focus}}</p>
<p>blur: {{blur}} </p>
if you want to set focus, or you can do like this
<input type="text" ng-focus="validate('somevalue')"/>
and
$scope.validate= function(text)
{
// validate here
}
I'm using an input field value attribute to show the query that the has user entered on the SERP of my app. I want to hide the value that was queried on pages where the result is undefined. Is it possible to use ng-show to just hide an attribute and not the entire element?
Jade
div(ng-controller="SearchCtrl", class="header-search-form")
form(id="search-form-homepage" class="input-group input-lg")
input(type="search",
ng-enter="doSearch(query.term)",
ng-hide="query.term === 'undefined'",
ng-model="query.term",
class="form-control header-search-result",
value="#{data.query}")
You will have to create your own directive for this task. As an example - plnkr, I have created a directive that will remove the placeholder attribute.
Note the key difference here is that we can't "hide" attributes since hide is really
display: 0;
we can just remove or add them.
app.directive('attrHide', function(){
return {
restrict: 'A',
scope: {
attrHide: '='
},
link: function(scope, elm, attr){
var targetAttr = attr.hiddenAttribute;
var saveAttr = attr[targetAttr] || '';
scope.$watch('attrHide', function(newVal){
if (newVal){
elm.removeAttr(targetAttr);
} else {
elm.attr(targetAttr,saveAttr);
}
})
}
}
})
In the markup you can have
<input ng-model="target"/>
<input placeholder="hello" hidden-attribute="placeholder" attr-hide="target=='hello'"/>
Here in the attr-hide, you can put the true false expression and in hidden-attribute you can choose the attribute to remove
I have just started learning Angular and have set up Angular routing for my first app.
I have a shell page and some sub pages, for example products.html, help.html etc
the sub pages contains forms with bootstrap switches and validation, which for some reason stops working (the switch is rendered as a regular checkbox and validation does not run - please note that I am not using Angular JS).
However, if a place the same code directly in the shell page it works fine.
How do I get the sub pages to behave exactly like they the code in the shell page?
Basically, if I have the following code in a form in one of my subpages help.html:
<div class="form-group">
<label for="active">My checkbox<br />
<div class="switch">
<input type="checkbox" value="1" id="active" name="active">
</div>
</div>
...the switch does not render correctly, but if I move the code directly to the shell page it renders correctly.
So what is the difference in what happens in the sub page (which is shown in a on the shell page) or somw code that is placed directly in the shell page HTML.
I am assuming you are using this Bootstrap Switch plugin.
I am also assuming that you are initialising the switches that work on the shell page by doing something like:
$(function () {
$('input[type="checkbox"]').bootstrapSwitch();
});
The problem with this is that it will only apply the bootstrap switch plugin to the checkboxes that it finds on the first page load, not after you change pages within an ng-view element.
What I recommend you do instead is to create a simple AngularJS directive to apply the bootstrap switch plugin to the checkboxes. The reason this will work is that Angular will compile the contents of a view every time you change pages and the link() functions of all the directives found will be run. Thus, your checkboxes, if they use this new directive, will always have the plugin applied correctly.
The directive could be as simple as:
app.directive('bsSwitch', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
$(element).bootstrapSwitch({
onSwitchChange: function(event, state) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(state);
});
}
});
}
}
});
Then change your markup in the views to be:
<div class="form-group">
<label for="active">My checkbox</label>
<br />
<div class="switch">
<input type="checkbox" value="1" id="active" name="active" bs-switch>
</div>
</div>
EDIT: If you wish to apply the bootstrap switch to all checkboxes on the application without the need for additional attributes, you could instead create a directive that will apply to all <input>s, and then just check if they are checkboxes.
Like so:
app.directive('input', function() {
return {
restrict: 'E',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
if (attrs.type === 'checkbox')
$(element).bootstrapSwitch({
onSwitchChange: function(event, state) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(state);
});
}
});
}
}
});
And then you can omit the bs-switch attribute in your markup.
See my working Plunkr example (Plunkr is a better tool than jsFiddle for Angular examples, and allows you to create multiple HTML, CSS and JS files).
EDIT 2: As Mark pointed out, it was broken if the checkbox was initially checked. Here is a fixed version of the directive (and an updated Plunkr):
// As an element directive that will apply to all checkbox inputs:
// <input type="checkbox" ng-model="blah">
app.directive('input', function() {
return {
restrict: 'E',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
if (attrs.type === 'checkbox' && !Object.hasOwnProperty(attrs, 'bsSwitch')) {
$(element).bootstrapSwitch({
onSwitchChange: function(event, state) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(state);
});
}
});
var dereg = scope.$watch(function() {
return ngModelCtrl.$modelValue;
}, function(newVal) {
$(element).bootstrapSwitch('state', !! newVal, true);
dereg();
});
}
}
}
});
DEMO
Consider the following example:
<input type="text" ng-model="client.phoneNumber" phone-number>
<button ng-click="doSomething()">Do Something</button>
.directive("phoneNumber", function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
scope.mobileNumberIsValid = true;
var errorTemplate = "<span ng-show='!mobileNumberIsValid'>Error</span>";
element.after($compile(errorTemplate)(scope)).on('blur', function() {
scope.$apply(function() {
scope.mobileNumberIsValid = /^\d*$/.test(element.val());
});
});
}
};
});
Looking at the demo, if you add say 'a' at the end of the phone number, and click the button, doSomething() is not called. If you click the button again, then doSomething() is called.
Why doSomething() is not called for the first time? Any ideas how to fix this?
Note: It is important to keep the validation on blur.
Explain
Use click button, mousedown event is triggered on button element.
Input is on blur, blur callback triggered to validate input value.
If invalid, error span is displayed, pushing button tag down, thus cursor left button area. If user release mouse, mouseup event is not triggered. This acts like click on a button but move outside of it before releasing mouse to cancel the button click. This is the reason ng-click is not triggered. Because mouseup event is not triggered on button.
Solution
Use ng-pattern to dynamically validate the input value, and show/hide error span immediately according to ngModel.$invalid property.
Demo 1 http://jsbin.com/epEBECAy/14/edit
----- Update 1 -----
According to author's request, updated answer with another solution.
Demo 2 http://jsbin.com/epEBECAy/21/edit?html,js
HTML
<body ng-app="Demo" ng-controller="DemoCtrl as demoCtrl">
<pre>{{ client | json }}</pre>
<div id="wrapper">
<input type="text" phone-number
ng-model="client.phoneNumber"
ng-blur="demoCtrl.validateInput(client.phoneNumber)">
</div>
<button ng-mousedown="demoCtrl.pauseValidation()"
ng-mouseup="demoCtrl.resumeValidation()"
ng-click="doSomething()">Do Something</button>
</body>
Logic
I used ng-blur directive on input to trigger validation. If ng-mousedown is triggered before ng-blur, ng-blur callback will be deferred until ng-mouseup is fired. This is accomplished by utilizing $q service.
Here: http://jsbin.com/epEBECAy/25/edit
As explained by other answers, the button is moved by the appearance of the span before an onmouseup event on the button occurs, thus causing the issue you are experiencing. The easiest way to accomplish what you want is to style the form in such a way that the appearance of the span does not cause the button to move (this is a good idea in general from a UX perspective). You can do this by wrapping the input element in a div with a style of white-space:nowrap. As long as there is enough horizontal space for the span, the button will not move and the ng-click event will work as expected.
<div id="wrapper">
<div style='white-space:nowrap;'>
<input type="text" ng-model="client.phoneNumber" phone-number>
</div>
<button ng-click="doSomething()">Do Something</button>
</div>
It is because the directive is inserting the <span>Error</span> underneath where the button is currently placed, interfering with the click event location. You can see this by moving the button above the text box, and everything should work fine.
EDIT:
If you really must have the error in the same position, and solve the issue without creating your own click directive, you can use ng-mousedown instead of ng-click. This will trigger the click code before handling the blur event.
Not a direct answer, but a suggestion for writing the directive differently (the html is the same):
http://jsbin.com/OTELeFe/1/
angular.module("Demo", [])
.controller("DemoCtrl", function($scope) {
$scope.client = {
phoneNumber: '0451785986'
};
$scope.doSomething = function() {
console.log('Doing...');
};
})
.directive("phoneNumber", function($compile) {
var errorTemplate = "<span ng-show='!mobileNumberIsValid'> Error </span>";
var link = function(scope, element, attrs) {
$compile(element.find("span"))(scope);
scope.mobileNumberIsValid = true;
scope.$watch('ngModel', function(v){
scope.mobileNumberIsValid = /^\d*$/.test(v);
});
};
var compile = function(element, attrs){
var h = element[0].outerHTML;
var newHtml = [
'<div>',
h.replace('phone-number', ''),
errorTemplate,
'</div>'
].join("\n");
element.replaceWith(newHtml);
return link;
};
return {
scope: {
ngModel: '='
},
compile: compile
};
});
I would suggest using $parsers and $setValidity way while validating phone number.
app.directive('phoneNumber', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ctrl) {
if (!ctrl) return;
ctrl.$parsers.unshift(function(viewValue) {
var valid = /^\d*$/.test(viewValue);
ctrl.$setValidity('phoneNumber', valid);
return viewValue;
});
ctrl.$formatters.unshift(function(modelValue) {
var valid = /^\d*$/.test(modelValue);
ctrl.$setValidity('phoneNumber', valid);
return modelValue;
});
}
}
});
So, you will be able to use $valid property on a field in your view:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">(Show on error)Wrong phone number</p>
</form>
If you want to show errors only on blur you can use (found here: AngularJS Forms - Validate Fields After User Has Left Field):
var focusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs, ctrl) {
var elm = $(element);
if (!ctrl) return;
elm.on('focus', function () {
elm.addClass('has-focus');
ctrl.$hasFocus = true;
if(!scope.$$phase) scope.$digest();
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
})
}
}
};
app.directive('input', focusDirective);
So, you will have hasFocus property if field is focused now and hasVisited property if that field blured one or more times:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">[error] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& form.phone.$hasVisited">[error && visited] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& !form.phone.$hasFocus">[error && blur] Wrong phone number</p>
<div><input type="submit" value="Submit"/></div>
</form>
Demo: http://jsfiddle.net/zVpWh/4/
I fixed it with the following.
<button class="submitButton form-control" type="submit" ng-mousedown="this.form.submit();" >
fiddle here: http://jsfiddle.net/graphicsxp/QA4Fa/2/
I'm triying to create a directive for searching. Basically it's just a textbox that detects user input and after a 1 second delay, a search method is called.
It's not working yet and I'm having two issues.
First, why is the filterCriteria not updated in the span when user inputs text ?
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
<div ng-app="myApp" ng-controller="myController">
<delayed-search ng-model="filterCriteria"></delayed-search>
<span>filter criteria is : {{filterCriteria}}</span>
</div>
angular.module('myApp', []).directive("delayedSearch", ['$timeout', function($timeout) {
return {
restrict: "E",
template: '<input type="text" />',
scope: {
filterCriteria : '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
var timer = false;
$scope.$watch('filterCriteria', function () {
if (timer) {
$timeout.cancel(timer);
}
timer = $timeout(function () {
alert('timeout expired');
}, 1000)
});
}
}
}]).controller('myController', function($scope){ });
You should NOT use a controller with a directive ( until you understand it ! ) .
A controller in a directive is meant for directive to directive communication (I wish they had named it something else!).
#Langdon got it right.. But here is another implementation of the same. Note that in both the answer's the controller is missing.
http://jsfiddle.net/QA4Fa/4/
First, why is the filterCriteria not updated in the span when user inputs text ?
Your scope is wrong, it should be scope: { ngModel : '=' },, and your template should be template: '<input type="text" ng-model="ngModel" />.
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
Same as the first problem, you should be watching ngModel.
Also, you don't need the overhead of a controller for this, you can get away with just using the link function. Here's an updated fiddle: http://jsfiddle.net/QA4Fa/3/