refactor(filter): filters are now injectable and services

BREAK:
 - removed CSS support from filters
This commit is contained in:
Misko Hevery
2011-11-03 15:59:18 -07:00
parent 6022f3df39
commit cb6f832f38
9 changed files with 180 additions and 179 deletions

View File

@@ -109,8 +109,6 @@ var _undefined = undefined,
angularDirective = extensionMap(angular, 'directive', lowercase),
/** @name angular.widget */
angularWidget = extensionMap(angular, 'widget', shivForIE),
/** @name angular.filter */
angularFilter = extensionMap(angular, 'filter'),
/** @name angular.service */
angularInputType = extensionMap(angular, 'inputType', lowercase),
/** @name angular.service */
@@ -1054,6 +1052,7 @@ function ngModule($provide, $injector) {
$provide.service('$defer', $DeferProvider);
$provide.service('$document', $DocumentProvider);
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
$provide.service('$filter', $FilterProvider);
$provide.service('$formFactory', $FormFactoryProvider);
$provide.service('$locale', $LocaleProvider);
$provide.service('$location', $LocationProvider);

View File

@@ -236,7 +236,7 @@ angularDirective("ng:controller", function(expression){
angularDirective("ng:bind", function(expression, element){
element.addClass('ng-binding');
return ['$exceptionHandler', '$parse', '$element', function($exceptionHandler, $parse, element) {
var exprFn = parser(expression),
var exprFn = $parse(expression),
lastValue = Number.NaN;
this.$watch(function(scope) {

27
src/service/filter.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict';
$FilterProvider.$inject = ['$provide'];
function $FilterProvider($provide) {
var suffix = '$Filter';
$provide.filter = function(name, factory) {
return $provide.factory(name + suffix, factory);
};
this.$get = ['$injector', function($injector) {
return function(name) {
return $injector(name + suffix);
}
}];
////////////////////////////////////////
$provide.filter('currency', currencyFilter);
$provide.filter('number', numberFilter);
$provide.filter('date', dateFilter);
$provide.filter('json', jsonFilter);
$provide.filter('lowercase', lowercaseFilter);
$provide.filter('uppercase', uppercaseFilter);
$provide.filter('html', htmlFilter);
$provide.filter('linky', linkyFilter);
}

View File

@@ -72,13 +72,15 @@
</doc:scenario>
</doc:example>
*/
angularFilter.currency = function(amount, currencySymbol){
var formats = this.$service('$locale').NUMBER_FORMATS;
this.$element.toggleClass('ng-format-negative', amount < 0);
if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
replace(/\u00A4/g, currencySymbol);
};
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(amount, currencySymbol){
if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
replace(/\u00A4/g, currencySymbol);
};
}
/**
* @ngdoc filter
@@ -126,14 +128,17 @@ angularFilter.currency = function(amount, currencySymbol){
</doc:example>
*/
numberFilter.$inject = ['$locale'];
function numberFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(number, fractionSize) {
return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
fractionSize);
};
}
var DECIMAL_SEP = '.';
angularFilter.number = function(number, fractionSize) {
var formats = this.$service('$locale').NUMBER_FORMATS;
return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP,
formats.DECIMAL_SEP, fractionSize);
};
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (isNaN(number) || !isFinite(number)) return '';
@@ -260,9 +265,7 @@ var DATE_FORMATS = {
Z: timeZoneGetter
};
var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/,
DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
OPERA_TOSTRING_PATTERN = /^[\d].*Z$/,
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
NUMBER_STRING = /^\d+$/;
/**
@@ -343,49 +346,51 @@ var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/,
</doc:scenario>
</doc:example>
*/
angularFilter.date = function(date, format) {
var $locale = this.$service('$locale'),
text = '',
parts = [],
fn, match;
dateFilter.$inject = ['$locale'];
function dateFilter($locale) {
return function(date, format) {
var text = '',
parts = [],
fn, match;
format = format || 'mediumDate'
format = $locale.DATETIME_FORMATS[format] || format;
if (isString(date)) {
if (NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
} else {
date = angularString.toDate(date);
format = format || 'mediumDate'
format = $locale.DATETIME_FORMATS[format] || format;
if (isString(date)) {
if (NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
} else {
date = angularString.toDate(date);
}
}
}
if (isNumber(date)) {
date = new Date(date);
}
if (!isDate(date)) {
return date;
}
while(format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = concat(parts, match, 1);
format = parts.pop();
} else {
parts.push(format);
format = null;
if (isNumber(date)) {
date = new Date(date);
}
}
forEach(parts, function(value){
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
if (!isDate(date)) {
return date;
}
return text;
};
while(format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = concat(parts, match, 1);
format = parts.pop();
} else {
parts.push(format);
format = null;
}
}
forEach(parts, function(value){
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
return text;
};
}
/**
@@ -417,10 +422,11 @@ angularFilter.date = function(date, format) {
</doc:example>
*
*/
angularFilter.json = function(object) {
this.$element.addClass("ng-monospace");
return toJson(object, true, /^(\$|this$)/);
};
function jsonFilter() {
return function(object) {
return toJson(object, true);
};
}
/**
@@ -430,7 +436,7 @@ angularFilter.json = function(object) {
*
* @see angular.lowercase
*/
angularFilter.lowercase = lowercase;
var lowercaseFilter = valueFn(lowercase);
/**
@@ -440,7 +446,7 @@ angularFilter.lowercase = lowercase;
*
* @see angular.uppercase
*/
angularFilter.uppercase = uppercase;
var uppercaseFilter = valueFn(uppercase);
/**
@@ -537,9 +543,11 @@ angularFilter.uppercase = uppercase;
</doc:scenario>
</doc:example>
*/
angularFilter.html = function(html, option){
return new HTML(html, option);
};
function htmlFilter() {
return function(html, option){
return new HTML(html, option);
};
}
/**
@@ -619,29 +627,31 @@ angularFilter.html = function(html, option){
</doc:scenario>
</doc:example>
*/
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
MAILTO_REGEXP = /^mailto:/;
function linkyFilter() {
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
MAILTO_REGEXP = /^mailto:/;
angularFilter.linky = function(text) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var writer = htmlSanitizeWriter(html);
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
writer.start('a', {href:url});
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return new HTML(html.join(''));
return function(text) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var writer = htmlSanitizeWriter(html);
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
writer.start('a', {href:url});
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return new HTML(html.join(''));
};
};

View File

@@ -217,7 +217,7 @@ function lex(text){
/////////////////////////////////////////
function parser(text, json){
function parser(text, json, $filter){
var ZERO = valueFn(0),
value,
tokens = lex(text),
@@ -227,8 +227,7 @@ function parser(text, json){
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
filterChain = _filterChain,
functionIdent = _functionIdent,
pipeFunction = _pipeFunction;
functionIdent = _functionIdent;
if(json){
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
@@ -239,7 +238,6 @@ function parser(text, json){
assignable =
filterChain =
functionIdent =
pipeFunction =
function() { throwError("is not valid json", {text:text, index:0}); };
value = primary();
} else {
@@ -346,13 +344,9 @@ function parser(text, json){
}
function filter() {
return pipeFunction(angularFilter);
}
function _pipeFunction(fnScope){
var fn = functionIdent(fnScope);
var token = expect();
var fn = $filter(token.text);
var argsFn = [];
var token;
while(true) {
if ((token = expect(':'))) {
argsFn.push(expression());
@@ -719,13 +713,13 @@ function getterFn(path) {
function $ParseProvider() {
var cache = {};
this.$get = ['$injector', function($injector) {
this.$get = ['$filter', function($filter) {
return function(exp) {
switch(typeof exp) {
case 'string':
return cache.hasOwnProperty(exp)
? cache[exp]
: cache[exp] = parser(exp);
: cache[exp] = parser(exp, false, $filter);
case 'function':
return exp;
default:
@@ -735,10 +729,14 @@ function $ParseProvider() {
}];
}
function noFilters(){
throw Error('Filters not supported!');
}
// This is a special access for JSON parser which bypasses the injector
var parseJson = function(json) {
return parser(json, true);
return parser(json, true, noFilters);
};
// TODO(misko): temporary hack, until we get rid of the type augmentation
var expressionCompile = new $ParseProvider().$get[1](null);
var expressionCompile = new $ParseProvider().$get[1](noFilters);