<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap-css@3.3.7" data-semver="3.3.7" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css" />
<script data-require="angular.js@1.6.2" data-semver="1.6.2" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
<script data-require="ng-messages@*" data-semver="1.3.16" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular-messages.min.js"></script>
<link rel="stylesheet" href="bootstrap-override.css" />
<script src="script.js"></script>
<script src="ra-text-input.component.js"></script>
<script src="ra-app.component.js"></script>
<script src="user.service.js"></script>
</head>
<body ng-app="app">
<h2>Angular JS custom text input component</h2>
<h3>Related to <a href="https://medium.com/front-end-hacking/angularjs-custom-text-input-component-4c1060b41118" target="_blank">AngularJS: custom text input component</a></h3>
<ra-app></ra-app>
</body>
</html>
angular.module("app", ["ngMessages"]);
/* Styles go here */
function InputController ($window, $document, $timeout) {
var $ctrl = this;
var sizes = {
'sm': 'form-group-sm',
'lg': 'form-group-lg',
};
$ctrl.$onInit = function () {
$ctrl.elementId = $window.angular.copy($ctrl.name);
}
$ctrl.$onChanges = function (changes) {
if (changes.required) {
$ctrl.isRequired = $ctrl.required === "true";
}
if (changes.submitted) {
$ctrl.isFormSubmitted = !!$ctrl.submitted;
}
if (changes.value) {
$ctrl.internalValue = $window.angular.copy($ctrl.value);
}
if (changes.helpText) {
$ctrl.hasHelpText = typeof $ctrl.helpText === 'string' &&
$ctrl.helpText.length > 0;
}
if (changes.size) {
$ctrl.sizeClass = sizes[$ctrl.size];
}
if (changes.characterCounter) {
$ctrl.hasCharacterCounter = $ctrl.characterCounter === "true";
}
}
var domElement = undefined;
$ctrl.$doCheck = function () {
if (!$ctrl.hasCharacterCounter) {
return;
}
if (!domElement) {
if (!$ctrl.elementId) {
return;
}
domElement = $document[0].querySelector('input[type=text]#' + $ctrl.elementId);
if (!domElement) {
return;
}
}
var currentLength = domElement.value.length;
if (currentLength !== $ctrl.currentLength) {
$timeout(function _forceDigestToDisplayDataOnView () {
$ctrl.currentLength = currentLength;
});
}
};
$ctrl.change = function () {
$ctrl.onChange({
name: $ctrl.name,
value: $ctrl.internalValue,
});
}
$ctrl.isNotValid = function () {
return $ctrl.isFormSubmitted && (!$ctrl.form[$ctrl.name].$valid);
}
$ctrl.isValid = function () {
return (!$ctrl.form[$ctrl.name].$pristine) && $ctrl.form[$ctrl.name].$valid;
}
}
function raTextInputComponent () {
return {
templateUrl: "ra-text-input.component.html",
controller: InputController,
bindings: {
name: "@",
label: "@",
required: "@",
minLength: "@",
maxLength: "@",
helpText: "@",
size: "@",
characterCounter: "@",
value: "<",
onChange: "&",
form: "<",
submitted: "<",
},
};
}
angular
.module("app")
.component("raTextInput", raTextInputComponent());
<div class="form-group has-feedback"
ng-class="[$ctrl.sizeClass, {'has-error': $ctrl.isNotValid(), 'has-success': $ctrl.isValid()}]"
>
<label class="control-label col-sm-3 col-xs-12" for="{{$ctrl.name}}">
<span ng-bind="$ctrl.label"></span>
<small ng-if="$ctrl.isRequired">
<span aria-hidden="true" class="text-danger">
*
</span>
<span class="sr-only">
(field is required)
</span>
</small>
</label>
<div
class="col-sm-9 col-xs-12"
id="{{$ctrl.name + '_input'}}"
>
<input type="text"
class="form-control"
id="{{$ctrl.name}}"
name="{{$ctrl.name}}"
ng-model="$ctrl.internalValue"
ng-required="$ctrl.isRequired"
ng-minlength="$ctrl.minLength"
ng-maxlength="$ctrl.maxLength"
ng-change="$ctrl.change()"
aria-describedby="{{$ctrl.name + '_validation' + ' ' + $ctrl.name + '_validationErrors' + ($ctrl.hasHelpText? (' ' + $ctrl.name + '_validation') : '')}}"
>
<span
class="glyphicon form-control-feedback"
ng-class="{'glyphicon-remove': $ctrl.isNotValid(), 'glyphicon-ok': $ctrl.isValid()}"
aria-hidden="true"
>
</span>
<small
id="{{$ctrl.name + '_validation'}}"
class="sr-only"
aria-role="alert"
aria-live="assertive"
>
<span ng-if="$ctrl.isValid()">
Field has correct value
</span>
<span ng-if="$ctrl.isNotValid()">
Field has incorrect value
</span>
</small>
<p
class="help-block"
ng-if="$ctrl.helpText"
ng-bind="$ctrl.helpText"
id="{{$ctrl.name + '_helpText'}}"
></p>
<p
class="help-block"
ng-if="$ctrl.hasCharacterCounter"
>
<span
ng-class="{'text-success': $ctrl.currentLength <= $ctrl.maxLength, 'text-danger' : $ctrl.currentLength > $ctrl.maxLength}"
>
Characters count: {{$ctrl.currentLength}} / {{$ctrl.maxLength}}
</span>
</p>
<div
class="text-danger"
id="{{$ctrl.name + '_validationErrors'}}"
aria-role="alert"
aria-live="assertive"
>
<div ng-if="$ctrl.isFormSubmitted" ng-messages="$ctrl.form[$ctrl.name].$error">
<div ng-message="required">
Field is required
</div>
<div ng-message="minlength">
Field should have at least {{$ctrl.minLength}} characters
</div>
<div ng-message="maxlength">
Field should have maximum {{$ctrl.maxLength}} characters
</div>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-info" ng-click="$ctrl.load()">
Load "from server" (faked with $q.when())
</button>
<form id="elementForm" name="elementForm" class="form-horizontal"
novalidate=""
ng-submit="elementForm.$valid && $ctrl.onSubmit()"
>
<ra-text-input
name="firstName"
label="First name (large)"
min-length="2"
max-length="10"
value="$ctrl.element.firstName"
on-change="$ctrl.updateModel(name, value)"
form="elementForm"
required="true"
submitted="elementForm.$submitted"
size="lg"
character-counter="true"
></ra-text-input>
<ra-text-input
name="middleName"
label="Middle name"
min-length="2"
max-length="10"
value="$ctrl.element.middleName"
on-change="$ctrl.updateModel(name, value)" form="elementForm"
required="false"
submitted="elementForm.$submitted"
help-text="Is not required, cause not everybody has middle name"
></ra-text-input>
<ra-text-input
name="surname"
label="Surname (small)"
min-length="2"
max-length="15"
value="$ctrl.element.surname"
on-change="$ctrl.updateModel(name, value)"
form="elementForm"
required="true"
submitted="elementForm.$submitted"
size="sm"
></ra-text-input>
<div class="form-group col-sm-offset-2">
<div class="col-sm-offset-3 col-sm-9 col-xs-12">
<button type="submit" class="btn btn-success">
Submit!
</button>
</div>
</div>
</form>
function AppController (UserService) {
var $ctrl = this;
$ctrl.$onInit = function () {
$ctrl.element = {};
}
$ctrl.updateModel = function (key, newValue) {
$ctrl.element[key] = newValue;
}
$ctrl.onSubmit = function () {
alert(JSON.stringify($ctrl.element));
}
$ctrl.load = function () {
UserService.getElement().then(function bindElement (element) {
$ctrl.element = element;
});
}
}
function raAppComponent () {
return {
templateUrl: "ra-app.component.html",
controller: AppController,
bindings: {
}
};
}
angular.module("app").component("raApp", raAppComponent());
/* http://stackoverflow.com/a/43664264/3368498 */
.form-horizontal .form-group-sm .control-label {
font-size: 12px;
}
.form-horizontal .form-group-lg .control-label {
font-size: 18px;
}
angular.module("app").factory("UserService", UserService);
function UserService ($q) {
return {
getElement: getElement,
}
function getElement () {
return $q.when(
{
firstName: "John",
middleName: "Maximilian",
surname: "Doe",
}
);
}
}