mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-27 03:30:24 +00:00
Minor documentation fixes. Should not be any code changes. One test changed due to dependency on text in documentation.
672 lines
22 KiB
JavaScript
672 lines
22 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc overview
|
|
* @name angular.filter
|
|
* @description
|
|
*
|
|
* Filters are used for formatting data displayed to the user.
|
|
*
|
|
* The general syntax in templates is as follows:
|
|
*
|
|
* {{ expression | [ filter_name ] }}
|
|
*
|
|
* Following is the list of built-in angular filters:
|
|
*
|
|
* * {@link angular.filter.currency currency}
|
|
* * {@link angular.filter.date date}
|
|
* * {@link angular.filter.html html}
|
|
* * {@link angular.filter.json json}
|
|
* * {@link angular.filter.linky linky}
|
|
* * {@link angular.filter.lowercase lowercase}
|
|
* * {@link angular.filter.number number}
|
|
* * {@link angular.filter.uppercase uppercase}
|
|
*
|
|
* For more information about how angular filters work, and how to create your own filters, see
|
|
* {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
|
|
* Guide.
|
|
*/
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.currency
|
|
* @function
|
|
*
|
|
* @description
|
|
* Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
|
|
* symbol for current locale is used.
|
|
*
|
|
* @param {number} amount Input to filter.
|
|
* @param {string=} symbol Currency symbol or identifier to be displayed.
|
|
* @returns {string} Formatted number.
|
|
*
|
|
* @css ng-format-negative
|
|
* When the value is negative, this css class is applied to the binding making it (by default) red.
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
<input type="text" name="amount" value="1234.56"/> <br/>
|
|
default currency symbol ($): {{amount | currency}}<br/>
|
|
custom currency identifier (USD$): {{amount | currency:"USD$"}}
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should init with 1234.56', function(){
|
|
expect(binding('amount | currency')).toBe('$1,234.56');
|
|
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
|
|
});
|
|
it('should update', function(){
|
|
input('amount').enter('-1234');
|
|
expect(binding('amount | currency')).toBe('($1,234.00)');
|
|
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
|
|
expect(element('.doc-example-live .ng-binding').attr('className')).
|
|
toMatch(/ng-format-negative/);
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
angularFilter.currency = function(amount, currencySymbol){
|
|
this.$element.toggleClass('ng-format-negative', amount < 0);
|
|
if (isUndefined(currencySymbol)) currencySymbol = NUMBER_FORMATS.CURRENCY_SYM;
|
|
return formatNumber(amount, 2, 1).replace(/\u00A4/g, currencySymbol);
|
|
};
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.number
|
|
* @function
|
|
*
|
|
* @description
|
|
* Formats a number as text.
|
|
*
|
|
* If the input is not a number an empty string is returned.
|
|
*
|
|
* @param {number|string} number Number to format.
|
|
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
|
|
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
Enter number: <input name='val' value='1234.56789' /><br/>
|
|
Default formatting: {{val | number}}<br/>
|
|
No fractions: {{val | number:0}}<br/>
|
|
Negative number: {{-val | number:4}}
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should format numbers', function(){
|
|
expect(binding('val | number')).toBe('1,234.568');
|
|
expect(binding('val | number:0')).toBe('1,235');
|
|
expect(binding('-val | number:4')).toBe('-1,234.5679');
|
|
});
|
|
|
|
it('should update', function(){
|
|
input('val').enter('3374.333');
|
|
expect(binding('val | number')).toBe('3,374.333');
|
|
expect(binding('val | number:0')).toBe('3,374');
|
|
expect(binding('-val | number:4')).toBe('-3,374.3330');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
|
|
// PATTERNS[0] is an array for Decimal Pattern, PATTERNS[1] is an array Currency Pattern
|
|
// Following is the order in each pattern array:
|
|
// 0: minInteger,
|
|
// 1: minFraction,
|
|
// 2: maxFraction,
|
|
// 3: positivePrefix,
|
|
// 4: positiveSuffix,
|
|
// 5: negativePrefix,
|
|
// 6: negativeSuffix,
|
|
// 7: groupSize,
|
|
// 8: lastGroupSize
|
|
var NUMBER_FORMATS = {
|
|
DECIMAL_SEP: '.',
|
|
GROUP_SEP: ',',
|
|
PATTERNS: [[1, 0, 3, '', '', '-', '', 3, 3],[1, 2, 2, '\u00A4', '', '(\u00A4', ')', 3, 3]],
|
|
CURRENCY_SYM: '$'
|
|
};
|
|
var DECIMAL_SEP = '.';
|
|
|
|
angularFilter.number = function(number, fractionSize) {
|
|
if (isNaN(number) || !isFinite(number)) return '';
|
|
return formatNumber(number, fractionSize, 0);
|
|
};
|
|
|
|
function formatNumber(number, fractionSize, type) {
|
|
var isNegative = number < 0,
|
|
type = type || 0, // 0 is decimal pattern, 1 is currency pattern
|
|
pattern = NUMBER_FORMATS.PATTERNS[type];
|
|
|
|
number = Math.abs(number);
|
|
var numStr = number + '',
|
|
formatedText = '',
|
|
parts = [];
|
|
|
|
if (numStr.indexOf('e') !== -1) {
|
|
var formatedText = numStr;
|
|
} else {
|
|
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
|
|
|
|
//determine fractionSize if it is not specified
|
|
if (isUndefined(fractionSize)) {
|
|
fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]);
|
|
}
|
|
|
|
var pow = Math.pow(10, fractionSize);
|
|
number = Math.round(number * pow) / pow;
|
|
var fraction = ('' + number).split(DECIMAL_SEP);
|
|
var whole = fraction[0];
|
|
fraction = fraction[1] || '';
|
|
|
|
var pos = 0,
|
|
lgroup = pattern[8],
|
|
group = pattern[7];
|
|
|
|
if (whole.length >= (lgroup + group)) {
|
|
pos = whole.length - lgroup;
|
|
for (var i = 0; i < pos; i++) {
|
|
if ((pos - i)%group === 0 && i !== 0) {
|
|
formatedText += NUMBER_FORMATS.GROUP_SEP;
|
|
}
|
|
formatedText += whole.charAt(i);
|
|
}
|
|
}
|
|
|
|
for (i = pos; i < whole.length; i++) {
|
|
if ((whole.length - i)%lgroup === 0 && i !== 0) {
|
|
formatedText += NUMBER_FORMATS.GROUP_SEP;
|
|
}
|
|
formatedText += whole.charAt(i);
|
|
}
|
|
|
|
// format fraction part.
|
|
while(fraction.length < fractionSize) {
|
|
fraction += '0';
|
|
}
|
|
if (fractionSize) formatedText += NUMBER_FORMATS.DECIMAL_SEP + fraction.substr(0, fractionSize);
|
|
}
|
|
|
|
parts.push(isNegative ? pattern[5] : pattern[3]);
|
|
parts.push(formatedText);
|
|
parts.push(isNegative ? pattern[6] : pattern[4]);
|
|
return parts.join('');
|
|
}
|
|
|
|
function padNumber(num, digits, trim) {
|
|
var neg = '';
|
|
if (num < 0) {
|
|
neg = '-';
|
|
num = -num;
|
|
}
|
|
num = '' + num;
|
|
while(num.length < digits) num = '0' + num;
|
|
if (trim)
|
|
num = num.substr(num.length - digits);
|
|
return neg + num;
|
|
}
|
|
|
|
|
|
function dateGetter(name, size, offset, trim) {
|
|
return function(date) {
|
|
var value = date['get' + name]();
|
|
if (offset > 0 || value > -offset)
|
|
value += offset;
|
|
if (value === 0 && offset == -12 ) value = 12;
|
|
return padNumber(value, size, trim);
|
|
};
|
|
}
|
|
|
|
function dateStrGetter(name, shortForm) {
|
|
return function(date) {
|
|
var value = date['get' + name]();
|
|
|
|
if(name == 'Month') {
|
|
value = MONTH[value];
|
|
} else {
|
|
value = DAY[value];
|
|
}
|
|
|
|
return shortForm ? value.substr(0,3) : value;
|
|
};
|
|
}
|
|
|
|
function timeZoneGetter(numFormat) {
|
|
return function(date) {
|
|
var timeZone;
|
|
if (numFormat || !(timeZone = GET_TIME_ZONE.exec(date.toString()))) {
|
|
var offset = date.getTimezoneOffset();
|
|
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
|
|
}
|
|
return timeZone[0];
|
|
};
|
|
}
|
|
|
|
var DAY = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(',');
|
|
|
|
var MONTH = 'January,February,March,April,May,June,July,August,September,October,November,December'.
|
|
split(',');
|
|
|
|
var DATE_FORMATS = {
|
|
yyyy: dateGetter('FullYear', 4),
|
|
yy: dateGetter('FullYear', 2, 0, true),
|
|
y: dateGetter('FullYear', 1),
|
|
MMMM: dateStrGetter('Month'),
|
|
MMM: dateStrGetter('Month', true),
|
|
MM: dateGetter('Month', 2, 1),
|
|
M: dateGetter('Month', 1, 1),
|
|
dd: dateGetter('Date', 2),
|
|
d: dateGetter('Date', 1),
|
|
HH: dateGetter('Hours', 2),
|
|
H: dateGetter('Hours', 1),
|
|
hh: dateGetter('Hours', 2, -12),
|
|
h: dateGetter('Hours', 1, -12),
|
|
mm: dateGetter('Minutes', 2),
|
|
m: dateGetter('Minutes', 1),
|
|
ss: dateGetter('Seconds', 2),
|
|
s: dateGetter('Seconds', 1),
|
|
EEEE: dateStrGetter('Day'),
|
|
EEE: dateStrGetter('Day', true),
|
|
a: function(date){return date.getHours() < 12 ? 'am' : 'pm';},
|
|
z: timeZoneGetter(false),
|
|
Z: timeZoneGetter(true)
|
|
};
|
|
|
|
var DEFAULT_DATETIME_FORMATS = {
|
|
long: 'MMMM d, y h:mm:ss a z',
|
|
medium: 'MMM d, y h:mm:ss a',
|
|
short: 'M/d/yy h:mm a',
|
|
fullDate: 'EEEE, MMMM d, y',
|
|
longDate: 'MMMM d, y',
|
|
mediumDate: 'MMM d, y',
|
|
shortDate: 'M/d/yy',
|
|
longTime: 'h:mm:ss a z',
|
|
mediumTime: 'h:mm:ss a',
|
|
shortTime: 'h:mm a'
|
|
};
|
|
|
|
var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/;
|
|
var DATE_FORMATS_SPLIT = /([^yMdHhmsazZE]*)(E+|y+|M+|d+|H+|h+|m+|s+|a|Z|z)(.*)/;
|
|
var OPERA_TOSTRING_PATTERN = /^[\d].*Z$/;
|
|
var NUMBER_STRING = /^\d+$/;
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.date
|
|
* @function
|
|
*
|
|
* @description
|
|
* Formats `date` to a string based on the requested `format`.
|
|
*
|
|
* `format` string can be composed of the following elements:
|
|
*
|
|
* * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
|
|
* * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
|
|
* * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
|
|
* * `'MMMM'`: Month in year (January-December)
|
|
* * `'MMM'`: Month in year (Jan-Dec)
|
|
* * `'MM'`: Month in year, padded (01-12)
|
|
* * `'M'`: Month in year (1-12)
|
|
* * `'dd'`: Day in month, padded (01-31)
|
|
* * `'d'`: Day in month (1-31)
|
|
* * `'EEEE'`: Day in Week,(Sunday-Saturday)
|
|
* * `'EEE'`: Day in Week, (Sun-Sat)
|
|
* * `'HH'`: Hour in day, padded (00-23)
|
|
* * `'H'`: Hour in day (0-23)
|
|
* * `'hh'`: Hour in am/pm, padded (01-12)
|
|
* * `'h'`: Hour in am/pm, (1-12)
|
|
* * `'mm'`: Minute in hour, padded (00-59)
|
|
* * `'m'`: Minute in hour (0-59)
|
|
* * `'ss'`: Second in minute, padded (00-59)
|
|
* * `'s'`: Second in minute (0-59)
|
|
* * `'a'`: am/pm marker
|
|
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
|
|
* * `'z'`: short form of current timezone name (e.g. PDT)
|
|
*
|
|
* `format` string can also be the following default formats for `en_US` locale (support for other
|
|
* locales will be added in the future versions):
|
|
*
|
|
* * `'long'`: equivalent to `'MMMM d, y h:mm:ss a z'` for en_US locale
|
|
* (e.g. September 3, 2010 12:05:08 pm PDT)
|
|
* * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
|
|
* (e.g. Sep 3, 2010 12:05:08 pm)
|
|
* * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
|
|
* * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
|
|
* (e.g. Friday, September 3, 2010)
|
|
* * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
|
|
* * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
|
|
* * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
|
|
* * `'longTime'`: equivalent to `'h:mm:ss a z'` for en_US locale (e.g. 12:05:08 pm PDT)
|
|
* * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
|
|
* * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
|
|
*
|
|
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
|
|
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
|
|
* @param {string=} format Formatting rules (see Description). If not specified,
|
|
* Date#toLocaleDateString is used.
|
|
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
<span ng:non-bindable>{{1288323623006 | date:'medium'}}</span>:
|
|
{{1288323623006 | date:'medium'}}<br/>
|
|
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
|
|
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
|
|
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
|
|
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should format date', function(){
|
|
expect(binding("1288323623006 | date:'medium'")).
|
|
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (am|pm)/);
|
|
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
|
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
|
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
|
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/);
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
angularFilter.date = function(date, format) {
|
|
format = DEFAULT_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;
|
|
}
|
|
|
|
var text = date.toLocaleDateString(), fn;
|
|
if (format && isString(format)) {
|
|
text = '';
|
|
var parts = [], match;
|
|
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) : value;
|
|
});
|
|
}
|
|
return text;
|
|
};
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.json
|
|
* @function
|
|
*
|
|
* @description
|
|
* Allows you to convert a JavaScript object into JSON string.
|
|
*
|
|
* This filter is mostly useful for debugging. When using the double curly {{value}} notation
|
|
* the binding is automatically converted to JSON.
|
|
*
|
|
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
|
|
* @returns {string} JSON string.
|
|
*
|
|
* @css ng-monospace Always applied to the encapsulating element.
|
|
*
|
|
* @example:
|
|
<doc:example>
|
|
<doc:source>
|
|
<input type="text" name="objTxt" value="{a:1, b:[]}"
|
|
ng:eval="obj = $eval(objTxt)"/>
|
|
<pre>{{ obj | json }}</pre>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should jsonify filtered objects', function() {
|
|
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
|
|
});
|
|
|
|
it('should update', function() {
|
|
input('objTxt').enter('[1, 2, 3]');
|
|
expect(binding('obj | json')).toBe('[1,2,3]');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*
|
|
*/
|
|
angularFilter.json = function(object) {
|
|
this.$element.addClass("ng-monospace");
|
|
return toJson(object, true);
|
|
};
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.lowercase
|
|
* @function
|
|
*
|
|
* @see angular.lowercase
|
|
*/
|
|
angularFilter.lowercase = lowercase;
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.uppercase
|
|
* @function
|
|
*
|
|
* @see angular.uppercase
|
|
*/
|
|
angularFilter.uppercase = uppercase;
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.html
|
|
* @function
|
|
*
|
|
* @description
|
|
* Prevents the input from getting escaped by angular. By default the input is sanitized and
|
|
* inserted into the DOM as is.
|
|
*
|
|
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
* it into the returned string, however, since our parser is more strict than a typical browser
|
|
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
|
* browser, won't make it through the sanitizer.
|
|
*
|
|
* If you hate your users, you may call the filter with optional 'unsafe' argument, which bypasses
|
|
* the html sanitizer, but makes your application vulnerable to XSS and other attacks. Using this
|
|
* option is strongly discouraged and should be used only if you absolutely trust the input being
|
|
* filtered and you can't get the content through the sanitizer.
|
|
*
|
|
* @param {string} html Html input.
|
|
* @param {string=} option If 'unsafe' then do not sanitize the HTML input.
|
|
* @returns {string} Sanitized or raw html.
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
Snippet: <textarea name="snippet" cols="60" rows="3">
|
|
<p style="color:blue">an html
|
|
<em onmouseover="this.textContent='PWN3D!'">click here</em>
|
|
snippet</p></textarea>
|
|
<table>
|
|
<tr>
|
|
<td>Filter</td>
|
|
<td>Source</td>
|
|
<td>Rendered</td>
|
|
</tr>
|
|
<tr id="html-filter">
|
|
<td>html filter</td>
|
|
<td>
|
|
<pre><div ng:bind="snippet | html"><br/></div></pre>
|
|
</td>
|
|
<td>
|
|
<div ng:bind="snippet | html"></div>
|
|
</td>
|
|
</tr>
|
|
<tr id="escaped-html">
|
|
<td>no filter</td>
|
|
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
|
<td><div ng:bind="snippet"></div></td>
|
|
</tr>
|
|
<tr id="html-unsafe-filter">
|
|
<td>unsafe html filter</td>
|
|
<td><pre><div ng:bind="snippet | html:'unsafe'"><br/></div></pre></td>
|
|
<td><div ng:bind="snippet | html:'unsafe'"></div></td>
|
|
</tr>
|
|
</table>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should sanitize the html snippet ', function(){
|
|
expect(using('#html-filter').binding('snippet | html')).
|
|
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
});
|
|
|
|
it('should escape snippet without any filter', function() {
|
|
expect(using('#escaped-html').binding('snippet')).
|
|
toBe("<p style=\"color:blue\">an html\n" +
|
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
"snippet</p>");
|
|
});
|
|
|
|
it('should inline raw snippet if filtered as unsafe', function() {
|
|
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).
|
|
toBe("<p style=\"color:blue\">an html\n" +
|
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
"snippet</p>");
|
|
});
|
|
|
|
it('should update', function(){
|
|
input('snippet').enter('new <b>text</b>');
|
|
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
|
|
expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>");
|
|
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
angularFilter.html = function(html, option){
|
|
return new HTML(html, option);
|
|
};
|
|
|
|
|
|
/**
|
|
* @workInProgress
|
|
* @ngdoc filter
|
|
* @name angular.filter.linky
|
|
* @function
|
|
*
|
|
* @description
|
|
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
|
* plain email address links.
|
|
*
|
|
* @param {string} text Input text.
|
|
* @returns {string} Html-linkified text.
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
Snippet: <textarea name="snippet" cols="60" rows="3">
|
|
Pretty text with some links:
|
|
http://angularjs.org/,
|
|
mailto:us@somewhere.org,
|
|
another@somewhere.org,
|
|
and one more: ftp://127.0.0.1/.</textarea>
|
|
<table>
|
|
<tr>
|
|
<td>Filter</td>
|
|
<td>Source</td>
|
|
<td>Rendered</td>
|
|
</tr>
|
|
<tr id="linky-filter">
|
|
<td>linky filter</td>
|
|
<td>
|
|
<pre><div ng:bind="snippet | linky"><br/></div></pre>
|
|
</td>
|
|
<td>
|
|
<div ng:bind="snippet | linky"></div>
|
|
</td>
|
|
</tr>
|
|
<tr id="escaped-html">
|
|
<td>no filter</td>
|
|
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
|
<td><div ng:bind="snippet"></div></td>
|
|
</tr>
|
|
</table>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should linkify the snippet with urls', function(){
|
|
expect(using('#linky-filter').binding('snippet | linky')).
|
|
toBe('Pretty text with some links:\n' +
|
|
'<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' +
|
|
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' +
|
|
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' +
|
|
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
|
});
|
|
|
|
it ('should not linkify snippet without the linky filter', function() {
|
|
expect(using('#escaped-html').binding('snippet')).
|
|
toBe("Pretty text with some links:\n" +
|
|
"http://angularjs.org/,\n" +
|
|
"mailto:us@somewhere.org,\n" +
|
|
"another@somewhere.org,\n" +
|
|
"and one more: ftp://127.0.0.1/.");
|
|
});
|
|
|
|
it('should update', function(){
|
|
input('snippet').enter('new http://link.');
|
|
expect(using('#linky-filter').binding('snippet | linky')).
|
|
toBe('new <a href="http://link">http://link</a>.');
|
|
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
//TODO: externalize all regexps
|
|
angularFilter.linky = function(text){
|
|
if (!text) return text;
|
|
var URL = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/;
|
|
var match;
|
|
var raw = text;
|
|
var html = [];
|
|
var writer = htmlSanitizeWriter(html);
|
|
var url;
|
|
var i;
|
|
while (match=raw.match(URL)) {
|
|
// 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:/, ''));
|
|
writer.end('a');
|
|
raw = raw.substring(i + match[0].length);
|
|
}
|
|
writer.chars(raw);
|
|
return new HTML(html.join(''));
|
|
};
|