Angular directive for horizontal Bootstrap form - angularjs

I'm trying to build a directive for my Angular to help with the integration of form fields. I've implemented Scott Allens solution from his Angular playbook, and it works fine for a normal stacked form.
I need however to adapt it to a horizontal form instead. Here's my code:
Markup
<div form-group>
<label for="name">Name</label>
<input type="text" id="name" ng-model="vm.name">
</div>
formGroup directive
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
var label = element.querySelector("label");
label.classList.add("control-label");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
}
element.classList.add("form-group");
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
The output becomes:
<div form-group="" class="form-group">
<label for="name" class="control-label">Name</label>
<input type="text" id="name" ng-model="vm.name" class="form-control">
</div>
And that's fine for stacked form. Since I need a horizontal form, my output needs to look like this:
<div form-group="" class="form-group">
<label for="name" class="control-label col-sm-3">Name</label>
<div class="col-sm-9">
<input type="text" id="name" ng-model="vm.name" class="form-control">
</div>
</div>
I've tried many solutions and I can get it work with single elements like an input, textarea or a select. It becomes much more tricky when I have something like two radio buttons inside my markup like this:
<div form-group>
<label>Active</label>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active"> Yes
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active"> No
</label>
</div>
</div>
The desired output of the above mentioned code should be:
<div form-group class="form-group">
<label class="control-label col-sm-3">Active</label>
<div class="col-sm-9">
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active"> Yes
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active"> No
</label>
</div>
</div>
</div>
Please notice that the input(s) in the form-group is not fixed. It can be either a single input, textarea, select, a group of radio buttons or checkboxes. I'm lost for how I can make that happen. Any help is appreciated. Thanks!
UPDATE
I made some small changes to Mark Veenstra's code to make it (sort of) working:
function setupDom(element) {
element.classList.add("form-group");
var label = element.querySelector("label");
label.classList.add("control-label", "col-sm-3");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
angular.element(input).wrap(angular.element('<div class="col-sm-9"></div>'));
}
var div_radio = element.querySelector("div[class='radio']");
angular.element(div_radio).wrap(angular.element('<div class="col-sm-9"></div>'));
}
This does not work completely as intended with multiple radio inputs since it only wraps the <div> on the first radio input element.
The output from radio button example in my original post using Marks code is:
<div form-group="" class="form-group">
<label class="control-label col-sm-3">Active</label>
<div class="col-sm-9">
<div class="radio">
<label>
<input type="radio" name="active" ng-value="true" ng-model="vm.active" value="true"> Yes
</label>
</div>
</div>
<div class="radio">
<label>
<input type="radio" name="active" ng-value="false" ng-model="vm.active" value="false"> No
</label>
</div>
</div>
SOLUTION
Check out the Plunker with the final result: http://plnkr.co/edit/Wv6V86hHTCz3URS9DhdU?p=preview

In the angular.element documentation you can find the method wrap() to be able to wrap HTML around a selected element. Or see this direct link.
So what you could do in your directive is change the setupDom() function to match your requirements per type of form element.
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
element.classList.add("form-group");
var label = element.querySelector("label");
label.classList.add("control-label col-sm-3");
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox"){
input.classList.add("form-control");
input.wrap(angular.element('<div class="col-sm-9"></div>'));
}
var div_radio = element.querySelectorAll("div[class='radio']");
div_radio.wrap(angular.element('<div class="col-sm-9"></div>'));
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
NOTE: This code is not tested, maybe there are some minor mistakes, but I guess you'll get the point now.

Mark's suggestion came close, but it didn't solve my problem completely. I ended up using the following code in my formGroup directive:
(function (module) {
"use strict";
function link(scope, element) {
setupDom(element[0]);
}
function setupDom(element) {
element.classList.add("form-group");
var children = angular.element(element).children();
var labels = children.splice(0, 1);
// Set label classes
labels[0].classList.add("control-label", "col-sm-3");
// Wrap children in div
angular.element(children).wrapAll(angular.element("<div class='col-sm-9'></div>"));
// Handle inputs
var inputs = element.querySelectorAll("input, textarea, select");
for (var i = 0, len = inputs.length; i < len; i++) {
var input = inputs[i],
type = input.getAttribute("type");
if (type !== "radio" && type !== "checkbox") {
input.classList.add("form-control");
}
}
}
function formGroup() {
return {
restrict: "A",
link: link
}
}
module.directive("formGroup", formGroup);
}(angular.module("app.core")));
Check out this Plunker to see it in action: http://plnkr.co/edit/Wv6V86hHTCz3URS9DhdU?p=preview

Related

Get rid of pesky borders in ngMessage

In this plunk I have a form with two fields, each validated with ngMessage, where the error message appears when the user tabs out of the field, or the form is submitted.
The issue is that if the message is shown and then hidden (because the problem was fixed) the borders are still shown.
Try tabbing out of a field, then entering a value, you will see only borders in the error message.
How to get rid of these borders?
HTML
<body ng-app="ngMessagesExample" ng-controller="ctl">
<form name="myForm" novalidate ng-submit="submitForm()">
<label>
Enter Aaa:
<input type="text"
name="aaa"
ng-model="aaa"
required ng-blur="aaaBlur()" />
</label>
<div ng-show="showAaa || formSubmitted"
ng-messages="myForm.aaa.$error"
style="color:red;background-color:yellow;border:1px solid brown">
<div ng-message="required">You did not enter a field</div>
</div>
<br/>
<label>
Enter Bbb:
<input type="text"
name="bbb"
ng-model="bbb"
ng-minlength="2"
ng-maxlength="5"
required ng-blur="bbbBlur()" />
</label>
<br/><br/>
<div ng-show="showBbb || formSubmitted" ng-messages="myForm.bbb.$error"
style="color:red;background-color:yellow;border:1px solid brown">
<div ng-message="required">You did not enter a field</div>
</div>
<br/>
<button style="float:left" type="submit">Submit</button>
</form>
</body>
Javascript
var app = angular.module('ngMessagesExample', ['ngMessages']);
app.controller('ctl', function ($scope) {
$scope.formSubmitted = false;
$scope.showAaa = false;
$scope.showBbb = false;
$scope.submitForm = function() {
$scope.formSubmitted = true;
};
$scope.aaaBlur = function() {
$scope.showAaa = true;
};
$scope.bbbBlur = function() {
$scope.showBbb = true;
};
});
It's because you show the div (showAaa=true) but there's no content. Solution? Don't show the div. :)
<div ng-show="!myForm.aaa.$valid && (showAaa || formSubmitted)"
In which field do you have the problem ? Both or only the 2nd one ?
I see that in the 2nd field you fixed a min and max length. If they're not right, you don't have any ng-message to handle them, but your field will be in error, so you'll end up with the red border and no message.
By the way : you can use [formName].[fieldName].$touched instead of using your native onblur.

Using Angular ng-show/ng-hide with radio buttons

I'm using $scope.checked to show/hide the "Top" text box, name="numRows" in my html.
However, even when I click on the 'top' radio button, $scope.checked always retains the value of `all' when I debug my controller code.
This simply plunker seems to work fine, http://plnkr.co/edit/6GoUZw7zkf8oqUmKg9hf?p=preview, but it won't work in my application.
In the plunker, there's a simple button whose click value will hit the controller event $scope.showval to show the value of either "top" or "all".
My HTML:
<div class="form-group">
<div class="row-fluid">
<label class="col-md-2 col-lg-2 control-label" for="numRows">Returned Rows</label>
<!-- ALL ROWS RADIO -->
<div class="col-md-1 col-lg-1">
<label class="radio-inline" for="radio-all">
<input name="radios" id="radio-all" value="all" type="radio" ng-model="checked" ng-click="setTopRows('all')">All
</label>
</div>
<!-- TOP NUM OF ROWS RADIO -->
<div class="col-md-1 col-lg-1">
<label class="radio-inline" for="radio-top">
<input name="radios" id="radio-top" value="top" type="radio" ng-model="checked" ng-click="setTopRows('top')">Top
</label>
</div>
<!-- NUM OF ROWS TEXT BOX -->
<div class="col-md-2">
<input ng-show="checked == 'top'" ng-hide="checked == 'all'" type="text" class="form-control" name="numRows" ng-model="settings.numRowsReturned" placeholder="" >
</div>
<div class="col-md-3">
Click to show<input type="button" ng-click="showval(e)">
</div>
</div>
</div>
A snippet from my angular controller code :
The $scope.checked value is always 'all', even when I click on 'top' from the html.
(function () {
'use strict';
angular.module('rage')
.controller('GadgetSettingsCtrl_NEW', ['$rootScope', '$scope', '$modalInstance', gridSettings]);
function gridSettings($rootScope, $scope, $modalInstance,) {
var settings = this;
$scope.checked = 'all';
$scope.showval = function (e) {
var test = $scope.checked;
}
$scope.setTopRows = function (numRows) {
if (numRows === 'top') {
$scope.checked = 'top';
}
else {
$scope.checked = 'all';
}
}
function checkOptions(){
var top = '';
if ($scope.checked === 'top') {
top = (settings.numRowsReturned != undefined ? settings.numRowsReturned : '');
}
}
}; // end of gridSettings()
})();
Can someone help me clear up why $scope.checked is NOT changing when I click on the radio buttons ?
thank you,
Bob
****** UPDATE *********
I've added a simple ng-click="setTopRows('all')" to force my scope variable to change. This works, but it seems like too much code to accomplish this.

Angular directive multiple inputs one model

HTML:
<html ng-app="app">
<div class="container" style="margin-top: 30px">
<input type="text" ng-model="newName" key-filter/>
<input type="text" ng-model="newName" key-filter/>
<input type="text" ng-model="newName" key-filter/>
<input type="text" ng-model="newName" key-filter/>
</div>
</html>
JS:
var app = angular.module('app', []);
app.directive('keyFilter', function() {
var pattern = /([\s !$%^&*()_+|~=`{}\[\]:";'<>?,.\/])/;
function link(scope) {
scope.$watch('model', function() {
if(scope.model === undefined)
return
if(pattern.test(scope.model)) {
scope.model = scope.model.replace(pattern, '');
Materialize.toast('Denied symbol', 4000, 'rounded');
}
});
}
return {
restrict: 'A',
scope: {
model: '=ngModel'
},
link: link
}
});
I have many inputs which are bind to the same model, and I am filtering user input, when user press a denied key I wanted to show a toast to inform him that he can't use this symbol, but the count of toasts is equal to the count of inputs bind to the same model.
I thought i'm working only with model which is one.
Example here:
http://codepen.io/anon/pen/XbLjVY?editors=101
How can I fix that, or change the logic how it works
p.s. angular beginner
If they are all bind to the same model every change in one is send to the others, so just put your directive on one input not all of them.
Here is a working plunkr :
http://plnkr.co/edit/dI5TMHms2wsPHc9Xqewf?p=preview
using :
<input type="text" ng-model="newName" key-filter/>
<input type="text" ng-model="newName" />
<input type="text" ng-model="newName" />
<input type="text" ng-model="newName" />
You can see in the console the message being displayed only once and from any input field

In angularjs, how to set focus on input on form submit if input has error?

This is my code and I want to set focus on first name textbox on form submit if first name textbox has error like $error.required,$error.pattern,$error.minlength or $error.maxlength.
<form class="form-horizontal" name="clientForm" id="clientForm" novalidate ng-submit="clientForm.$valid" ng-class="{ loading:form.submitting }">
<div class="form-group">
<label class="col-lg-2 control-label">First Name</label>
<div class="col-lg-8">
<input type="text" ng-model="client.firstName" class="form-control" required autofocus name="firstName" autocomplete="off" ng-maxlength="100" ng-minlength=3 ng-pattern="/^[a-zA-Z]*$/" />
<!--<span ng-show="clientForm.firstName.$dirty && clientForm.firstName.$invalid" class="error">First Name is required.</span>-->
<div class="errors" ng-show="clientForm.$submitted || clientForm.firstName.$touched">
<div class="error" ng-show="clientForm.firstName.$error.required">
First Name is required.
</div>
<div class="error" ng-show="clientForm.firstName.$error.pattern">
Enter valid name.
</div>
<div class="error" ng-show="clientForm.firstName.$error.minlength">
First Name is required to be at least 3 characters
</div>
<div class="error" ng-show="clientForm.firstName.$error.maxlength">
First Name cannot be longer than 100 characters
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Save</button></form>
This question is a duplicate of:
Set focus on first invalid input in AngularJs form
You can use a directive on the form:
<form accessible-form>
...
</form>
app.directive('accessibleForm', function () {
return {
restrict: 'A',
link: function (scope, elem) {
// set up event handler on the form element
elem.on('submit', function () {
// find the first invalid element
var firstInvalid = elem[0].querySelector('.ng-invalid');
// if we find one, set focus
if (firstInvalid) {
firstInvalid.focus();
}
});
}
};
});
Here is a working Plunker:
http://plnkr.co/edit/wBFY9ZZRzLuDUi2SyErC?p=preview
You'll have to handle this using directive. For example:
It will iterate through the element(s) and check if the focusNow attribute is true or not. Make sure that the error handler code sets the expression true/false.
.directive('focusNow', function ($timeout) {
return {
link: function (scope, element, attrs) {
scope.$watch(attrs.focusNow, function (value) {
if (value === true) {
for (var i = 0; i < element.length; i++) {
var ele = angular.element(element[i].parentNode);
if (!ele.hasClass('ng-hide')) { //Skip those elements which are hidden.
element[i].focus();
}
}
}
});
}
};
});
and in the HTML, you'll have:
<input type="text" focus-now="expression" />
where expression will be a normal expression which will evaluate to true in case of error.
You can try this: i.e add ng-focus="clientForm.$error" in first name input
<input type="text" ng-focus="clientForm.$invalid" ng-model="client.firstName" class="form-control" required autofocus name="firstName" autocomplete="off" ng-maxlength="100" ng-minlength=3 ng-pattern="/^[a-zA-Z]*$/" />

AngularJS - Trigger when radio button is selected

I searched and tried many ng-xxxx kind of options but couldn't find the one..
I just want to call some function in the controller when radio button is selected.
So it might be similar to following..(Of course, below code is not working)
<input type="radio" ng-model="value" value="one" ng-click="checkStuff()"/>
Is there any way to achieve what I want?
There are at least 2 different methods of invoking functions on radio button selection:
1) Using ng-change directive:
<input type="radio" ng-model="value" value="foo" ng-change='newValue(value)'>
and then, in a controller:
$scope.newValue = function(value) {
console.log(value);
}
Here is the jsFiddle: http://jsfiddle.net/ZPcSe/5/
2) Watching the model for changes. This doesn't require anything special on the input level:
<input type="radio" ng-model="value" value="foo">
but in a controller one would have:
$scope.$watch('value', function(value) {
console.log(value);
});
And the jsFiddle: http://jsfiddle.net/vDTRp/2/
Knowing more about your the use case would help to propose an adequate solution.
Should use ngChange instead of ngClick if trigger source is not from click.
Is the below what you want ? what exactly doesn't work in your case ?
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.value = "none" ;
$scope.isChecked = false;
$scope.checkStuff = function () {
$scope.isChecked = !$scope.isChecked;
}
}
<div ng-controller="MyCtrl">
<input type="radio" ng-model="value" value="one" ng-change="checkStuff()" />
<span> {{value}} isCheck:{{isChecked}} </span>
</div>
In newer versions of angular (I'm using 1.3) you can basically set the model and the value and the double binding do all the work this example works like a charm:
angular.module('radioExample', []).controller('ExampleController', ['$scope', function($scope) {
$scope.color = {
name: 'blue'
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<html>
<body ng-app="radioExample">
<form name="myForm" ng-controller="ExampleController">
<input type="radio" ng-model="color.name" value="red"> Red <br/>
<input type="radio" ng-model="color.name" value="green"> Green <br/>
<input type="radio" ng-model="color.name" value="blue"> Blue <br/>
<tt>color = {{color.name}}</tt><br/>
</form>
</body>
</html>
For dynamic values!
<div class="col-md-4" ng-repeat="(k, v) in tiposAcesso">
<label class="control-label">
<input type="radio" name="tipoAcesso" ng-model="userLogin.tipoAcesso" value="{{k}}" ng-change="changeTipoAcesso(k)" />
<span ng-bind="v"></span>
</label>
</div>
in controller
$scope.changeTipoAcesso = function(value) {
console.log(value);
};
Another approach is using Object.defineProperty to set valueas a getter setter property in the controller scope, then each change on the value property will trigger a function specified in the setter:
The HTML file:
<input type="radio" ng-model="value" value="one"/>
<input type="radio" ng-model="value" value="two"/>
<input type="radio" ng-model="value" value="three"/>
The javascript file:
var _value = null;
Object.defineProperty($scope, 'value', {
get: function () {
return _value;
},
set: function (value) {
_value = value;
someFunction();
}
});
see this plunker for the implementation
i prefer to use ng-value with ng-if,
[ng-value] will handle trigger changes
<input type="radio" name="isStudent" ng-model="isStudent" ng-value="true" />
//to show and hide input by removing it from the DOM, that's make me secure from malicious data
<input type="text" ng-if="isStudent" name="textForStudent" ng-model="job">
<form name="myForm" ng-submit="submitForm()">
<label data-ng-repeat="i in [1,2,3]"><input type="radio" name="test" ng-model="$parent.radioValue" value="{{i}}"/>{{i}}</label>
<div>currently selected: {{radioValue}}</div>
<button type="submit">Submit</button>
</form>

Resources