Skip to content

Commit 5a2e9ff

Browse files
author
Slava Fomin II
committed
- Done some serious refactoring.
Low-level details moved to a separate functions, so the main logic became more abstract and readable. Performance and memory footprint should also improve - Fixed bug when empty element would not recover it's master value when reset is invoked on the parent form - Introduced distribution files (normal and minified versions) and a proper build process using **Gulp** - Improved README
1 parent 2af44f7 commit 5a2e9ff

File tree

9 files changed

+502
-87
lines changed

9 files changed

+502
-87
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/.idea
1+
/.idea
2+
/node_modules

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "angular-input-modified",
33
"description": "AngularJS module to detect and indicate input modifications",
4-
"version": "1.1.2",
4+
"version": "1.1.3",
55
"main": "src/angular-input-modified.js",
66
"dependencies": {
77
"angular": "~1.2.26"

changelog.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
# angular-input-modified changelog
22

3+
## Version 1.1.3
4+
(13 October 2014)
5+
6+
- Done some serious refactoring.
7+
Low-level details moved to a separate functions, so the main logic became more abstract and readable.
8+
Performance and memory footprint should also improve
9+
- Fixed bug when empty element would not recover it's master value when reset is invoked on the parent form
10+
- Introduced distribution files (normal and minified versions) and a proper build process using **Gulp**
11+
- Improved README
12+
313
## Version 1.1.2
414
(13 October 2014)
515

616
- Updated README (no code changes)
17+
- Added new Demo via **Plunker**
718

819
## Version 1.1.1
920
(08 October 2014)

dist/angular-input-modified.js

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/**
2+
* AngularJS module "angular-input-modified".
3+
*
4+
* @version 1.1.3
5+
* @author Slava Fomin II <[email protected]>
6+
* @licence MIT
7+
* @copyright Slava Fomin II, Better Solutions, 2014
8+
*/
9+
(function(window, angular) {
10+
11+
'use strict';
12+
13+
var modifiedClassName = 'ng-modified';
14+
var notModifiedClassName = 'ng-not-modified';
15+
16+
var directiveSpecification = ['$animate', inputModifiedDirective];
17+
18+
// Registering AngularJS module.
19+
angular.module('ngInputModified', ['ng'])
20+
.directive('input', directiveSpecification)
21+
.directive('textarea', directiveSpecification)
22+
.directive('select', directiveSpecification)
23+
;
24+
25+
function inputModifiedDirective($animate)
26+
{
27+
return {
28+
restrict: 'E',
29+
require: ['?ngModel', '?^form'],
30+
link: function($scope, $element, attrs, controllers) {
31+
32+
var modelName = attrs.ngModel;
33+
34+
// Handling controllers.
35+
var ngModel = controllers[0];
36+
var ngForm = controllers[1];
37+
38+
// ngModel is required for this directive to operate.
39+
// ngForm is optional.
40+
if (null === ngModel) {
41+
return;
42+
}
43+
44+
/**
45+
* Decorates element with proper CSS classes.
46+
*/
47+
var toggleCssClasses = function() {
48+
$animate.addClass($element, (ngModel.modified ? modifiedClassName : notModifiedClassName));
49+
$animate.removeClass($element, (ngModel.modified ? notModifiedClassName : modifiedClassName));
50+
};
51+
52+
/**
53+
* This handler is called when form element is modified.
54+
*
55+
* @param {string} modelName
56+
* @param {boolean} modified
57+
*/
58+
var onElementModified = function(modelName, modified) {
59+
60+
// Updating form when one of the inputs is modified.
61+
if (ngForm) {
62+
var index = ngForm.modifiedModels.indexOf(modelName);
63+
var exists = (-1 !== index);
64+
65+
if (modified && !exists) {
66+
// Adding model name to the list of modified models.
67+
ngForm.modifiedModels.push(modelName);
68+
ngForm.modifiedCount++;
69+
} else if (!modified && exists) {
70+
// Removing model name from the list of modified models.
71+
ngForm.modifiedModels.splice(index, 1);
72+
ngForm.modifiedCount--;
73+
}
74+
75+
// Form is considered modified when it has at least one modified element.
76+
ngForm.modified = (ngForm.modifiedCount > 0);
77+
}
78+
};
79+
80+
/**
81+
* Sets proper modification state for model controller according to current and master value.
82+
*/
83+
var onInputValueChanged = function() {
84+
85+
// If master value is not set.
86+
if ('undefined' === typeof ngModel.masterValue) {
87+
88+
// Initializing the master value.
89+
ngModel.masterValue = ngModel.$modelValue;
90+
91+
// Initially decorating the element.
92+
toggleCssClasses();
93+
94+
} else {
95+
96+
// Comparing current input value with preserved master value
97+
// to determine if it's changed.
98+
var modified = !valuesEqual(ngModel.$modelValue, ngModel.masterValue);
99+
100+
// If modified flag is changed.
101+
if (ngModel.modified !== modified) {
102+
103+
onElementModified(modelName, modified);
104+
105+
// Setting new flag.
106+
ngModel.modified = modified;
107+
108+
// Re-decorating the element.
109+
toggleCssClasses();
110+
}
111+
}
112+
};
113+
114+
// Saving handle to original $setPristine method.
115+
var originalSetPristine = ngModel.$setPristine;
116+
117+
/**
118+
* This flag will show if input value was modified.
119+
*
120+
* @type {boolean}
121+
*/
122+
ngModel.modified = false;
123+
124+
/**
125+
* This property contains current master value for this input field.
126+
*
127+
* @type {*}
128+
*/
129+
ngModel.masterValue = undefined;
130+
131+
/**
132+
* Augmentation for original $setPristine method.
133+
*/
134+
ngModel.$setPristine = function() {
135+
136+
// Calling original $setPristine method.
137+
originalSetPristine.apply(this, arguments);
138+
139+
// Updating parameters.
140+
ngModel.masterValue = ngModel.$modelValue;
141+
ngModel.modified = false;
142+
143+
if (ngModel.modified) {
144+
onElementModified(modelName, false);
145+
}
146+
147+
// Re-decorating the element.
148+
toggleCssClasses();
149+
};
150+
151+
/**
152+
* Resets input value to the master.
153+
*/
154+
ngModel.reset = function() {
155+
eval('$scope.' + modelName + ' = ngModel.masterValue;');
156+
};
157+
158+
// If parent form element is present for this input and
159+
// is not yet initialized.
160+
if (ngForm && !isFormInitialized(ngForm)) {
161+
initializeForm(ngForm);
162+
}
163+
164+
// Watching for model value changes.
165+
$scope.$watch(modelName, onInputValueChanged);
166+
}
167+
};
168+
}
169+
170+
/**
171+
* Returns true if specified parameter is Angular model controller,
172+
* false otherwise.
173+
*
174+
* @param {*} ngModel
175+
*
176+
* @returns {boolean}
177+
*/
178+
function isModelController(ngModel) {
179+
return (
180+
'object' === typeof ngModel
181+
&& '$modelValue' in ngModel
182+
);
183+
}
184+
185+
/**
186+
* Returns true if specified parameter is initialized model controller,
187+
* false otherwise.
188+
*
189+
* @param {*} ngModel
190+
*
191+
* @returns {boolean}
192+
*/
193+
function isModelControllerInitialized(ngModel) {
194+
return ('modified' in ngModel);
195+
}
196+
197+
/**
198+
* Iterates child model controllers of specified form controller,
199+
* call specified iterator with every model controller.
200+
*
201+
* @param {object} ngForm
202+
* @param {function} iterator
203+
*/
204+
function iterateFormElements(ngForm, iterator) {
205+
angular.forEach(ngForm, function(element) {
206+
if (isModelController(element)) {
207+
iterator(element);
208+
}
209+
});
210+
}
211+
212+
/**
213+
* Initializes specified form controller.
214+
*
215+
* @param {object} ngForm
216+
*/
217+
function initializeForm(ngForm) {
218+
219+
ngForm.modified = false;
220+
ngForm.modifiedCount = 0;
221+
222+
// List of modified models.
223+
ngForm.modifiedModels = [];
224+
225+
/**
226+
* Resets all form inputs to it's master values.
227+
*/
228+
ngForm.reset = function() {
229+
iterateFormElements(ngForm, function(ngModel) {
230+
if (isModelControllerInitialized(ngModel)) {
231+
ngModel.reset();
232+
}
233+
});
234+
};
235+
}
236+
237+
/**
238+
* Returns true if specified form controller is initialized, false otherwise.
239+
*
240+
* @param {object} ngForm
241+
*
242+
* @returns {boolean}
243+
*/
244+
function isFormInitialized(ngForm) {
245+
return ('undefined' !== typeof ngForm.modified);
246+
}
247+
248+
/**
249+
* Returns true if specified values are equal, false otherwise.
250+
* Supports date comparison.
251+
*
252+
* @param {*} value1
253+
* @param {*} value2
254+
*
255+
* @returns {boolean}
256+
*/
257+
function valuesEqual(value1, value2)
258+
{
259+
value1 = unifyValue(value1);
260+
value2 = unifyValue(value2);
261+
262+
if ('object' === typeof value1 && 'object' === typeof value2) {
263+
if (value1 instanceof Date && value2 instanceof Date) {
264+
// Comparing two dates.
265+
return (value1.getTime() === value2.getTime());
266+
} else {
267+
// Comparing two generic objects using strong comparison.
268+
return (value1 === value2);
269+
}
270+
}
271+
272+
// In all other cases using weak comparison.
273+
return (value1 == value2);
274+
}
275+
276+
/**
277+
* Casting all null-like values to actual null for guaranteed comparison.
278+
*
279+
* @param {*} value
280+
*
281+
* @returns {*}
282+
*/
283+
function unifyValue(value)
284+
{
285+
if (undefined === value) {
286+
return null;
287+
}
288+
289+
if ('' === value) {
290+
return null;
291+
}
292+
293+
return value;
294+
}
295+
296+
})(window, angular);

dist/angular-input-modified.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gulpfile.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var del = require('del');
2+
var gulp = require('gulp');
3+
var clean = require('gulp-clean');
4+
var rename = require('gulp-rename');
5+
var uglify = require('gulp-uglify');
6+
var gutil = require('gulp-util');
7+
8+
gulp.task('clean', function (callback) {
9+
del(['dist'], callback);
10+
});
11+
12+
gulp.task('build', function () {
13+
gulp.src('src/angular-input-modified.js')
14+
.pipe(rename('angular-input-modified.js'))
15+
.pipe(gulp.dest('dist'))
16+
.pipe(uglify())
17+
.pipe(rename('angular-input-modified.min.js'))
18+
.pipe(gulp.dest('dist'))
19+
.on('error', gutil.log)
20+
;
21+
});
22+
23+
gulp.task('default', ['clean', 'build']);

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"devDependencies": {
3+
"del": "^0.1.3",
4+
"gulp": "^3.8.8",
5+
"gulp-rename": "^1.2.0",
6+
"gulp-uglify": "^0.3.0",
7+
"gulp-util": "^2.2.14"
8+
}
9+
}

0 commit comments

Comments
 (0)