Skip to content

Commit e9111a4

Browse files
committed
feat(input) add support to for week
Add support to date filter for outputing week of year Partially closes angular#757
1 parent d026c99 commit e9111a4

File tree

4 files changed

+362
-44
lines changed

4 files changed

+362
-44
lines changed

src/ng/directive/input.js

+209-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\
1212
var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
1313
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
1414
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
15-
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/;
15+
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/;
16+
<<<<<<< HEAD
17+
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
18+
=======
19+
>>>>>>> e60a44c... feat(input) add support for datetime-local
1620

1721
var inputType = {
1822

@@ -164,7 +168,7 @@ var inputType = {
164168
* @description
165169
* HTML5 or text input with datetime validation and transformation. In browsers that do not yet support
166170
* the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601
167-
* local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:12`. Will also accept a valid ISO
171+
* local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. Will also accept a valid ISO
168172
* datetime string or Date object as model input, but will always output a Date object to the model.
169173
*
170174
* @param {string} ngModel Assignable angular expression to data-bind to.
@@ -183,27 +187,27 @@ var inputType = {
183187
<doc:source>
184188
<script>
185189
function Ctrl($scope) {
186-
$scope.value = '2010-12-28T14:57:12';
190+
$scope.value = '2010-12-28T14:57';
187191
}
188192
</script>
189193
<form name="myForm" ng-controller="Ctrl as dateCtrl">
190-
Pick a date between in 2013:
191-
<input type="date" name="input" ng-model="value"
192-
placeholder="yyyy-MM-dd" min="2013-01-01T00:00:00" max="2013-12-31T00:00:00" required />
193-
<span class="error" ng-show="myForm.input.$error.required">
194-
Required!</span>
195-
<span class="error" ng-show="myForm.input.$error.datetimelocal">
196-
Not a valid date!</span>
197-
<tt>value = {{value}}</tt><br/>
198-
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
199-
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
200-
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
201-
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
194+
Pick a date between in 2013:
195+
<input type="datetime-local" name="input" ng-model="value"
196+
placeholder="yyyy-MM-ddTHH:mm" min="2001-01-01T00:00" max="2013-12-31T00:00" required />
197+
<span class="error" ng-show="myForm.input.$error.required">
198+
Required!</span>
199+
<span class="error" ng-show="myForm.input.$error.datetimelocal">
200+
Not a valid date!</span>
201+
<tt>value = {{value}}</tt><br/>
202+
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
203+
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
204+
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
205+
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
202206
</form>
203207
</doc:source>
204208
<doc:scenario>
205209
it('should initialize to model', function() {
206-
expect(binding('value')).toEqual('2010-12-28T14:57:12');
210+
expect(binding('value')).toEqual('2010-12-28T14:57');
207211
expect(binding('myForm.input.$valid')).toEqual('true');
208212
});
209213
@@ -214,14 +218,84 @@ var inputType = {
214218
});
215219
216220
it('should be invalid if over max', function() {
217-
input('value').enter('2015-01-01T23:59:59');
221+
input('value').enter('2015-01-01T23:59');
218222
expect(binding('value')).toEqual('');
219223
expect(binding('myForm.input.$valid')).toEqual('false');
220224
});
221225
</doc:scenario>
222226
</doc:example>
223227
*/
224228
'datetime-local': dateTimeLocalInputType,
229+
<<<<<<< HEAD
230+
231+
/**
232+
* @ngdoc inputType
233+
* @name ng.directive:input.week
234+
*
235+
* @description
236+
* HTML5 or text input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
237+
* the HTML5 week input, a text element will be used. The text must be entered in a valid ISO-8601
238+
* week format (yyyy-W##), for example: `2013-W02`. Will also accept a valid ISO
239+
* week string or Date object as model input, but will always output a Date object to the model.
240+
*
241+
* @param {string} ngModel Assignable angular expression to data-bind to.
242+
* @param {string=} name Property name of the form under which the control is published.
243+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
244+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
245+
* @param {string=} required Sets `required` validation error key if the value is not entered.
246+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
247+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
248+
* `required` when you want to data-bind to the `required` attribute.
249+
* @param {string=} ngChange Angular expression to be executed when input changes due to user
250+
* interaction with the input element.
251+
*
252+
* @example
253+
<doc:example>
254+
<doc:source>
255+
<script>
256+
function Ctrl($scope) {
257+
$scope.value = '2013-W01';
258+
}
259+
</script>
260+
<form name="myForm" ng-controller="Ctrl as dateCtrl">
261+
Pick a date between in 2013:
262+
<input type="week" name="input" ng-model="value"
263+
placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required />
264+
<span class="error" ng-show="myForm.input.$error.required">
265+
Required!</span>
266+
<span class="error" ng-show="myForm.input.$error.week">
267+
Not a valid date!</span>
268+
<tt>value = {{value}}</tt><br/>
269+
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
270+
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
271+
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
272+
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
273+
</form>
274+
</doc:source>
275+
<doc:scenario>
276+
it('should initialize to model', function() {
277+
expect(binding('value')).toEqual('2013-W01');
278+
expect(binding('myForm.input.$valid')).toEqual('true');
279+
});
280+
281+
it('should be invalid if empty', function() {
282+
input('value').enter('');
283+
expect(binding('value')).toEqual('');
284+
expect(binding('myForm.input.$valid')).toEqual('false');
285+
});
286+
287+
it('should be invalid if over max', function() {
288+
input('value').enter('2015-W01');
289+
expect(binding('value')).toEqual('');
290+
expect(binding('myForm.input.$valid')).toEqual('false');
291+
});
292+
</doc:scenario>
293+
</doc:example>
294+
*/
295+
'week': weekInputType,
296+
297+
=======
298+
>>>>>>> e60a44c... feat(input) add support for datetime-local
225299
/**
226300
* @ngdoc inputType
227301
* @name ng.directive:input.number
@@ -669,6 +743,121 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
669743
}
670744
}
671745

746+
<<<<<<< HEAD
747+
748+
function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
749+
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
750+
751+
ctrl.$parsers.push(function(value) {
752+
if(ctrl.$isEmpty(value)) {
753+
ctrl.$setValidity('week', true);
754+
return value;
755+
}
756+
757+
if(WEEK_REGEXP.test(value)) {
758+
ctrl.$setValidity('week', true);
759+
return new Date(getTime(value).time);
760+
}
761+
762+
ctrl.$setValidity('week', false);
763+
return undefined;
764+
});
765+
766+
ctrl.$formatters.push(function(value) {
767+
if(isDate(value)) {
768+
return $filter('date')(value, 'yyyy-Www');
769+
}
770+
return ctrl.$isEmpty(value) ? '' : ''+value;
771+
});
772+
773+
if(attr.min) {
774+
var minValidator = function(value) {
775+
var valTime = getTime(value),
776+
minTime = getTime(attr.min);
777+
778+
var valid = ctrl.$isEmpty(value) ||
779+
valTime.time >= minTime.time;
780+
781+
ctrl.$setValidity('min', valid);
782+
return valid ? value : undefined;
783+
};
784+
785+
ctrl.$parsers.push(minValidator);
786+
ctrl.$formatters.push(minValidator);
787+
}
788+
789+
if(attr.max) {
790+
var maxValidator = function(value) {
791+
var valTime = getTime(value),
792+
maxTime = getTime(attr.max);
793+
794+
var valid = ctrl.$isEmpty(value) ||
795+
valTime.time <= maxTime.time;
796+
797+
ctrl.$setValidity('max', valid);
798+
return valid ? value : undefined;
799+
};
800+
801+
ctrl.$parsers.push(maxValidator);
802+
ctrl.$formatters.push(maxValidator);
803+
}
804+
805+
function getFirstThursday(year) {
806+
var d = 1, date;
807+
while(true) {
808+
date = new Date(year, 0, d++);
809+
if(date.getDay() === 4) {
810+
return date;
811+
}
812+
}
813+
}
814+
815+
function getThisThursday(date) {
816+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + (4 - date.getDay()));
817+
}
818+
819+
var MILLISECONDS_PER_WEEK = 6.048e8;
820+
821+
function getWeek(date) {
822+
var firstThurs = getFirstThursday(date.getFullYear()),
823+
thisThurs = getThisThursday(date),
824+
diff = +thisThurs - +firstThurs;
825+
826+
return 1 + Math.round(diff / MILLISECONDS_PER_WEEK);
827+
}
828+
829+
function getTime(isoWeek) {
830+
if(isDate(isoWeek)) {
831+
return {
832+
year: isoWeek.getFullYear(),
833+
week: getWeek(isoWeek),
834+
time: +isoWeek
835+
};
836+
}
837+
838+
if(isString(isoWeek)) {
839+
WEEK_REGEXP.lastIndex = 0;
840+
var parts = WEEK_REGEXP.exec(isoWeek);
841+
if(parts) {
842+
var year = +parts[1],
843+
week = +parts[2],
844+
firstThurs = getFirstThursday(year),
845+
addDays = (week - 1) * 7;
846+
847+
return {
848+
time: +new Date(year, 0, firstThurs.getDate() + addDays),
849+
week: week,
850+
year: year
851+
};
852+
}
853+
}
854+
855+
return NaN;
856+
}
857+
}
858+
859+
=======
860+
>>>>>>> e60a44c... feat(input) add support for datetime-local
672861
function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
673862
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
674863

@@ -689,7 +878,7 @@ function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser,
689878

690879
ctrl.$formatters.push(function(value) {
691880
if(isDate(value)) {
692-
return $filter('date')(value, 'yyyy-MM-ddTHH:mm:ss');
881+
return $filter('date')(value, 'yyyy-MM-ddTHH:mm');
693882
}
694883
return ctrl.$isEmpty(value) ? '' : '' + value;
695884
});
@@ -730,10 +919,9 @@ function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser,
730919
MM = +parts[2] - 1,
731920
dd = +parts[3],
732921
HH = +parts[4],
733-
mm = +parts[5],
734-
ss = +parts[6];
922+
mm = +parts[5];
735923

736-
return +new Date(yyyy, MM, dd, HH, mm, ss);
924+
return +new Date(yyyy, MM, dd, HH, mm);
737925
}
738926

739927
return NaN;

src/ng/filter/filters.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,35 @@ function timeZoneGetter(date) {
228228
return paddedZone;
229229
}
230230

231+
function getFirstThursday(year) {
232+
var d = 1,
233+
date = new Date(year, 0, d);
234+
while(date.getDay() !== 4) {
235+
d++;
236+
date = new Date(year, 0, d);
237+
}
238+
return date;
239+
}
240+
241+
function getThursdayThisWeek(date) {
242+
var day = date.getDay(),
243+
d = date.getDate();
244+
245+
return new Date(date.getFullYear(), date.getMonth(), d + (4 - day));
246+
}
247+
248+
function weekGetter(size) {
249+
return function(date) {
250+
var firstThurs = getFirstThursday(date.getFullYear()),
251+
thisThurs = getThursdayThisWeek(date);
252+
253+
var diff = +thisThurs - +firstThurs,
254+
result = 1 + Math.round(diff / 6.048e8);
255+
256+
return padNumber(result, size);
257+
};
258+
}
259+
231260
function ampmGetter(date, formats) {
232261
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
233262
}
@@ -256,10 +285,12 @@ var DATE_FORMATS = {
256285
EEEE: dateStrGetter('Day'),
257286
EEE: dateStrGetter('Day', true),
258287
a: ampmGetter,
259-
Z: timeZoneGetter
288+
Z: timeZoneGetter,
289+
ww: weekGetter(2),
290+
w: weekGetter(1)
260291
};
261292

262-
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
293+
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
263294
NUMBER_STRING = /^\-?\d+$/;
264295

265296
/**
@@ -294,6 +325,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
294325
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
295326
* * `'a'`: am/pm marker
296327
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
328+
* * `'ww'`: ISO-8601 week of year (00-53)
329+
* * `'w'`: ISO-8601 week of year (0-53)
297330
*
298331
* `format` string can also be one of the following predefined
299332
* {@link guide/i18n localizable formats}:

0 commit comments

Comments
 (0)