mirror of https://github.com/grafana/grafana
parent
d7ef6daeb8
commit
7baad7ff10
@ -0,0 +1,678 @@ |
||||
/** |
||||
* @license AngularJS v1.3.4 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
var $sanitizeMinErr = angular.$$minErr('$sanitize'); |
||||
|
||||
/** |
||||
* @ngdoc module |
||||
* @name ngSanitize |
||||
* @description |
||||
* |
||||
* # ngSanitize |
||||
* |
||||
* The `ngSanitize` module provides functionality to sanitize HTML. |
||||
* |
||||
* |
||||
* <div doc-module-components="ngSanitize"></div> |
||||
* |
||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage. |
||||
*/ |
||||
|
||||
/* |
||||
* HTML Parser By Misko Hevery (misko@hevery.com) |
||||
* based on: HTML Parser By John Resig (ejohn.org) |
||||
* Original code by Erik Arvidsson, Mozilla Public License |
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
* |
||||
* // Use like so:
|
||||
* htmlParser(htmlString, { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* }); |
||||
* |
||||
*/ |
||||
|
||||
|
||||
/** |
||||
* @ngdoc service |
||||
* @name $sanitize |
||||
* @kind function |
||||
* |
||||
* @description |
||||
* 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. The input may also contain SVG markup. |
||||
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and |
||||
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. |
||||
* |
||||
* @param {string} html HTML input. |
||||
* @returns {string} Sanitized HTML. |
||||
* |
||||
* @example |
||||
<example module="sanitizeExample" deps="angular-sanitize.js"> |
||||
<file name="index.html"> |
||||
<script> |
||||
angular.module('sanitizeExample', ['ngSanitize']) |
||||
.controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { |
||||
$scope.snippet = |
||||
'<p style="color:blue">an html\n' + |
||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + |
||||
'snippet</p>'; |
||||
$scope.deliberatelyTrustDangerousSnippet = function() { |
||||
return $sce.trustAsHtml($scope.snippet); |
||||
}; |
||||
}]); |
||||
</script> |
||||
<div ng-controller="ExampleController"> |
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> |
||||
<table> |
||||
<tr> |
||||
<td>Directive</td> |
||||
<td>How</td> |
||||
<td>Source</td> |
||||
<td>Rendered</td> |
||||
</tr> |
||||
<tr id="bind-html-with-sanitize"> |
||||
<td>ng-bind-html</td> |
||||
<td>Automatically uses $sanitize</td> |
||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td> |
||||
<td><div ng-bind-html="snippet"></div></td> |
||||
</tr> |
||||
<tr id="bind-html-with-trust"> |
||||
<td>ng-bind-html</td> |
||||
<td>Bypass $sanitize by explicitly trusting the dangerous value</td> |
||||
<td> |
||||
<pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> |
||||
</div></pre> |
||||
</td> |
||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> |
||||
</tr> |
||||
<tr id="bind-default"> |
||||
<td>ng-bind</td> |
||||
<td>Automatically escapes</td> |
||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td> |
||||
<td><div ng-bind="snippet"></div></td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</file> |
||||
<file name="protractor.js" type="protractor"> |
||||
it('should sanitize the html snippet by default', function() { |
||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). |
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); |
||||
}); |
||||
|
||||
it('should inline raw snippet if bound to a trusted value', function() { |
||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). |
||||
toBe("<p style=\"color:blue\">an html\n" + |
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + |
||||
"snippet</p>"); |
||||
}); |
||||
|
||||
it('should escape snippet without any filter', function() { |
||||
expect(element(by.css('#bind-default div')).getInnerHtml()). |
||||
toBe("<p style=\"color:blue\">an html\n" + |
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + |
||||
"snippet</p>"); |
||||
}); |
||||
|
||||
it('should update', function() { |
||||
element(by.model('snippet')).clear(); |
||||
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); |
||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). |
||||
toBe('new <b>text</b>'); |
||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( |
||||
'new <b onclick="alert(1)">text</b>'); |
||||
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( |
||||
"new <b onclick=\"alert(1)\">text</b>"); |
||||
}); |
||||
</file> |
||||
</example> |
||||
*/ |
||||
function $SanitizeProvider() { |
||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) { |
||||
return function(html) { |
||||
var buf = []; |
||||
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { |
||||
return !/^unsafe/.test($$sanitizeUri(uri, isImage)); |
||||
})); |
||||
return buf.join(''); |
||||
}; |
||||
}]; |
||||
} |
||||
|
||||
function sanitizeText(chars) { |
||||
var buf = []; |
||||
var writer = htmlSanitizeWriter(buf, angular.noop); |
||||
writer.chars(chars); |
||||
return buf.join(''); |
||||
} |
||||
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = |
||||
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, |
||||
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, |
||||
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, |
||||
BEGIN_TAG_REGEXP = /^</, |
||||
BEGING_END_TAGE_REGEXP = /^<\//, |
||||
COMMENT_REGEXP = /<!--(.*?)-->/g, |
||||
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, |
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, |
||||
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, |
||||
// Match everything outside of normal chars and " (quote character)
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; |
||||
|
||||
|
||||
// Good source of info about elements and attributes
|
||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
||||
// http://simon.html5.org/html-elements
|
||||
|
||||
// Safe Void Elements - HTML5
|
||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
||||
var voidElements = makeMap("area,br,col,hr,img,wbr"); |
||||
|
||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
||||
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
||||
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), |
||||
optionalEndTagInlineElements = makeMap("rp,rt"), |
||||
optionalEndTagElements = angular.extend({}, |
||||
optionalEndTagInlineElements, |
||||
optionalEndTagBlockElements); |
||||
|
||||
// Safe Block Elements - HTML5
|
||||
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + |
||||
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + |
||||
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); |
||||
|
||||
// Inline Elements - HTML5
|
||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + |
||||
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + |
||||
"samp,small,span,strike,strong,sub,sup,time,tt,u,var")); |
||||
|
||||
// SVG Elements
|
||||
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
|
||||
var svgElements = makeMap("animate,animateColor,animateMotion,animateTransform,circle,defs," + |
||||
"desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient," + |
||||
"line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set," + |
||||
"stop,svg,switch,text,title,tspan,use"); |
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style"); |
||||
|
||||
var validElements = angular.extend({}, |
||||
voidElements, |
||||
blockElements, |
||||
inlineElements, |
||||
optionalEndTagElements, |
||||
svgElements); |
||||
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); |
||||
|
||||
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + |
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + |
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + |
||||
'scope,scrolling,shape,size,span,start,summary,target,title,type,' + |
||||
'valign,value,vspace,width'); |
||||
|
||||
// SVG attributes (without "id" and "name" attributes)
|
||||
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
||||
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + |
||||
'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' + |
||||
'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' + |
||||
'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' + |
||||
'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' + |
||||
'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' + |
||||
'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' + |
||||
'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' + |
||||
'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' + |
||||
'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' + |
||||
'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' + |
||||
'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' + |
||||
'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' + |
||||
'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' + |
||||
'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' + |
||||
'zoomAndPan'); |
||||
|
||||
var validAttrs = angular.extend({}, |
||||
uriAttrs, |
||||
svgAttrs, |
||||
htmlAttrs); |
||||
|
||||
function makeMap(str) { |
||||
var obj = {}, items = str.split(','), i; |
||||
for (i = 0; i < items.length; i++) obj[items[i]] = true; |
||||
return obj; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @example |
||||
* htmlParser(htmlString, { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* }); |
||||
* |
||||
* @param {string} html string |
||||
* @param {object} handler |
||||
*/ |
||||
function htmlParser(html, handler) { |
||||
if (typeof html !== 'string') { |
||||
if (html === null || typeof html === 'undefined') { |
||||
html = ''; |
||||
} else { |
||||
html = '' + html; |
||||
} |
||||
} |
||||
var index, chars, match, stack = [], last = html, text; |
||||
stack.last = function() { return stack[ stack.length - 1 ]; }; |
||||
|
||||
while (html) { |
||||
text = ''; |
||||
chars = true; |
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if (!stack.last() || !specialElements[ stack.last() ]) { |
||||
|
||||
// Comment
|
||||
if (html.indexOf("<!--") === 0) { |
||||
// comments containing -- are not allowed unless they terminate the comment
|
||||
index = html.indexOf("--", 4); |
||||
|
||||
if (index >= 0 && html.lastIndexOf("-->", index) === index) { |
||||
if (handler.comment) handler.comment(html.substring(4, index)); |
||||
html = html.substring(index + 3); |
||||
chars = false; |
||||
} |
||||
// DOCTYPE
|
||||
} else if (DOCTYPE_REGEXP.test(html)) { |
||||
match = html.match(DOCTYPE_REGEXP); |
||||
|
||||
if (match) { |
||||
html = html.replace(match[0], ''); |
||||
chars = false; |
||||
} |
||||
// end tag
|
||||
} else if (BEGING_END_TAGE_REGEXP.test(html)) { |
||||
match = html.match(END_TAG_REGEXP); |
||||
|
||||
if (match) { |
||||
html = html.substring(match[0].length); |
||||
match[0].replace(END_TAG_REGEXP, parseEndTag); |
||||
chars = false; |
||||
} |
||||
|
||||
// start tag
|
||||
} else if (BEGIN_TAG_REGEXP.test(html)) { |
||||
match = html.match(START_TAG_REGEXP); |
||||
|
||||
if (match) { |
||||
// We only have a valid start-tag if there is a '>'.
|
||||
if (match[4]) { |
||||
html = html.substring(match[0].length); |
||||
match[0].replace(START_TAG_REGEXP, parseStartTag); |
||||
} |
||||
chars = false; |
||||
} else { |
||||
// no ending tag found --- this piece should be encoded as an entity.
|
||||
text += '<'; |
||||
html = html.substring(1); |
||||
} |
||||
} |
||||
|
||||
if (chars) { |
||||
index = html.indexOf("<"); |
||||
|
||||
text += index < 0 ? html : html.substring(0, index); |
||||
html = index < 0 ? "" : html.substring(index); |
||||
|
||||
if (handler.chars) handler.chars(decodeEntities(text)); |
||||
} |
||||
|
||||
} else { |
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), |
||||
function(all, text) { |
||||
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); |
||||
|
||||
if (handler.chars) handler.chars(decodeEntities(text)); |
||||
|
||||
return ""; |
||||
}); |
||||
|
||||
parseEndTag("", stack.last()); |
||||
} |
||||
|
||||
if (html == last) { |
||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + |
||||
"of html: {0}", html); |
||||
} |
||||
last = html; |
||||
} |
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag(); |
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) { |
||||
tagName = angular.lowercase(tagName); |
||||
if (blockElements[ tagName ]) { |
||||
while (stack.last() && inlineElements[ stack.last() ]) { |
||||
parseEndTag("", stack.last()); |
||||
} |
||||
} |
||||
|
||||
if (optionalEndTagElements[ tagName ] && stack.last() == tagName) { |
||||
parseEndTag("", tagName); |
||||
} |
||||
|
||||
unary = voidElements[ tagName ] || !!unary; |
||||
|
||||
if (!unary) |
||||
stack.push(tagName); |
||||
|
||||
var attrs = {}; |
||||
|
||||
rest.replace(ATTR_REGEXP, |
||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { |
||||
var value = doubleQuotedValue |
||||
|| singleQuotedValue |
||||
|| unquotedValue |
||||
|| ''; |
||||
|
||||
attrs[name] = decodeEntities(value); |
||||
}); |
||||
if (handler.start) handler.start(tagName, attrs, unary); |
||||
} |
||||
|
||||
function parseEndTag(tag, tagName) { |
||||
var pos = 0, i; |
||||
tagName = angular.lowercase(tagName); |
||||
if (tagName) |
||||
// Find the closest opened tag of the same type
|
||||
for (pos = stack.length - 1; pos >= 0; pos--) |
||||
if (stack[ pos ] == tagName) |
||||
break; |
||||
|
||||
if (pos >= 0) { |
||||
// Close all the open elements, up the stack
|
||||
for (i = stack.length - 1; i >= pos; i--) |
||||
if (handler.end) handler.end(stack[ i ]); |
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos; |
||||
} |
||||
} |
||||
} |
||||
|
||||
var hiddenPre=document.createElement("pre"); |
||||
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; |
||||
/** |
||||
* decodes all entities into regular string |
||||
* @param value |
||||
* @returns {string} A string with decoded entities. |
||||
*/ |
||||
function decodeEntities(value) { |
||||
if (!value) { return ''; } |
||||
|
||||
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
||||
// so we must capture them and reattach them afterward
|
||||
var parts = spaceRe.exec(value); |
||||
var spaceBefore = parts[1]; |
||||
var spaceAfter = parts[3]; |
||||
var content = parts[2]; |
||||
if (content) { |
||||
hiddenPre.innerHTML=content.replace(/</g,"<"); |
||||
// innerText depends on styling as it doesn't display hidden elements.
|
||||
// Therefore, it's better to use textContent not to cause unnecessary
|
||||
// reflows. However, IE<9 don't support textContent so the innerText
|
||||
// fallback is necessary.
|
||||
content = 'textContent' in hiddenPre ? |
||||
hiddenPre.textContent : hiddenPre.innerText; |
||||
} |
||||
return spaceBefore + content + spaceAfter; |
||||
} |
||||
|
||||
/** |
||||
* Escapes all potentially dangerous characters, so that the |
||||
* resulting string can be safely inserted into attribute or |
||||
* element text. |
||||
* @param value |
||||
* @returns {string} escaped text |
||||
*/ |
||||
function encodeEntities(value) { |
||||
return value. |
||||
replace(/&/g, '&'). |
||||
replace(SURROGATE_PAIR_REGEXP, function(value) { |
||||
var hi = value.charCodeAt(0); |
||||
var low = value.charCodeAt(1); |
||||
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; |
||||
}). |
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value) { |
||||
return '&#' + value.charCodeAt(0) + ';'; |
||||
}). |
||||
replace(/</g, '<'). |
||||
replace(/>/g, '>'); |
||||
} |
||||
|
||||
/** |
||||
* create an HTML/XML writer which writes to buffer |
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string |
||||
* @returns {object} in the form of { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* } |
||||
*/ |
||||
function htmlSanitizeWriter(buf, uriValidator) { |
||||
var ignore = false; |
||||
var out = angular.bind(buf, buf.push); |
||||
return { |
||||
start: function(tag, attrs, unary) { |
||||
tag = angular.lowercase(tag); |
||||
if (!ignore && specialElements[tag]) { |
||||
ignore = tag; |
||||
} |
||||
if (!ignore && validElements[tag] === true) { |
||||
out('<'); |
||||
out(tag); |
||||
angular.forEach(attrs, function(value, key) { |
||||
var lkey=angular.lowercase(key); |
||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); |
||||
if (validAttrs[lkey] === true && |
||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) { |
||||
out(' '); |
||||
out(key); |
||||
out('="'); |
||||
out(encodeEntities(value)); |
||||
out('"'); |
||||
} |
||||
}); |
||||
out(unary ? '/>' : '>'); |
||||
} |
||||
}, |
||||
end: function(tag) { |
||||
tag = angular.lowercase(tag); |
||||
if (!ignore && validElements[tag] === true) { |
||||
out('</'); |
||||
out(tag); |
||||
out('>'); |
||||
} |
||||
if (tag == ignore) { |
||||
ignore = false; |
||||
} |
||||
}, |
||||
chars: function(chars) { |
||||
if (!ignore) { |
||||
out(encodeEntities(chars)); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
// define ngSanitize module and register $sanitize service
|
||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); |
||||
|
||||
/* global sanitizeText: false */ |
||||
|
||||
/** |
||||
* @ngdoc filter |
||||
* @name linky |
||||
* @kind function |
||||
* |
||||
* @description |
||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and |
||||
* plain email address links. |
||||
* |
||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed. |
||||
* |
||||
* @param {string} text Input text. |
||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. |
||||
* @returns {string} Html-linkified text. |
||||
* |
||||
* @usage |
||||
<span ng-bind-html="linky_expression | linky"></span> |
||||
* |
||||
* @example |
||||
<example module="linkyExample" deps="angular-sanitize.js"> |
||||
<file name="index.html"> |
||||
<script> |
||||
angular.module('linkyExample', ['ngSanitize']) |
||||
.controller('ExampleController', ['$scope', function($scope) { |
||||
$scope.snippet = |
||||
'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/.'; |
||||
$scope.snippetWithTarget = 'http://angularjs.org/'; |
||||
}]); |
||||
</script> |
||||
<div ng-controller="ExampleController"> |
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></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-html="snippet | linky"><br></div></pre> |
||||
</td> |
||||
<td> |
||||
<div ng-bind-html="snippet | linky"></div> |
||||
</td> |
||||
</tr> |
||||
<tr id="linky-target"> |
||||
<td>linky target</td> |
||||
<td> |
||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> |
||||
</td> |
||||
<td> |
||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></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> |
||||
</file> |
||||
<file name="protractor.js" type="protractor"> |
||||
it('should linkify the snippet with urls', function() { |
||||
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). |
||||
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + |
||||
'another@somewhere.org, and one more: ftp://127.0.0.1/.'); |
||||
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); |
||||
}); |
||||
|
||||
it('should not linkify snippet without the linky filter', function() { |
||||
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). |
||||
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + |
||||
'another@somewhere.org, and one more: ftp://127.0.0.1/.'); |
||||
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); |
||||
}); |
||||
|
||||
it('should update', function() { |
||||
element(by.model('snippet')).clear(); |
||||
element(by.model('snippet')).sendKeys('new http://link.'); |
||||
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). |
||||
toBe('new http://link.'); |
||||
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); |
||||
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) |
||||
.toBe('new http://link.'); |
||||
}); |
||||
|
||||
it('should work with the target property', function() { |
||||
expect(element(by.id('linky-target')). |
||||
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). |
||||
toBe('http://angularjs.org/'); |
||||
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); |
||||
}); |
||||
</file> |
||||
</example> |
||||
*/ |
||||
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { |
||||
var LINKY_URL_REGEXP = |
||||
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/, |
||||
MAILTO_REGEXP = /^mailto:/; |
||||
|
||||
return function(text, target) { |
||||
if (!text) return text; |
||||
var match; |
||||
var raw = text; |
||||
var 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; |
||||
addText(raw.substr(0, i)); |
||||
addLink(url, match[0].replace(MAILTO_REGEXP, '')); |
||||
raw = raw.substring(i + match[0].length); |
||||
} |
||||
addText(raw); |
||||
return $sanitize(html.join('')); |
||||
|
||||
function addText(text) { |
||||
if (!text) { |
||||
return; |
||||
} |
||||
html.push(sanitizeText(text)); |
||||
} |
||||
|
||||
function addLink(url, text) { |
||||
html.push('<a '); |
||||
if (angular.isDefined(target)) { |
||||
html.push('target="', |
||||
target, |
||||
'" '); |
||||
} |
||||
html.push('href="', |
||||
url.replace('"', '"'), |
||||
'">'); |
||||
addText(text); |
||||
html.push('</a>'); |
||||
} |
||||
}; |
||||
}]); |
||||
|
||||
|
||||
})(window, window.angular); |
File diff suppressed because it is too large
Load Diff
@ -1,321 +1,321 @@ |
||||
(function () { |
||||
"use strict"; |
||||
/** |
||||
* Bindonce - Zero watches binding for AngularJs |
||||
* @version v0.3.1 |
||||
* @link https://github.com/Pasvaz/bindonce
|
||||
* @author Pasquale Vazzana <pasqualevazzana@gmail.com> |
||||
* @license MIT License, http://www.opensource.org/licenses/MIT
|
||||
*/ |
||||
"use strict"; |
||||
/** |
||||
* Bindonce - Zero watches binding for AngularJs |
||||
* @version v0.3.2 |
||||
* @link https://github.com/Pasvaz/bindonce
|
||||
* @author Pasquale Vazzana <pasqualevazzana@gmail.com> |
||||
* @license MIT License, http://www.opensource.org/licenses/MIT
|
||||
*/ |
||||
|
||||
var bindonceModule = angular.module('pasvaz.bindonce', []); |
||||
var bindonceModule = angular.module('pasvaz.bindonce', []); |
||||
|
||||
bindonceModule.directive('bindonce', function () |
||||
{ |
||||
var toBoolean = function (value) |
||||
{ |
||||
if (value && value.length !== 0) |
||||
{ |
||||
var v = angular.lowercase("" + value); |
||||
value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]'); |
||||
} |
||||
else |
||||
{ |
||||
value = false; |
||||
} |
||||
return value; |
||||
}; |
||||
bindonceModule.directive('bindonce', function () |
||||
{ |
||||
var toBoolean = function (value) |
||||
{ |
||||
if (value && value.length !== 0) |
||||
{ |
||||
var v = angular.lowercase("" + value); |
||||
value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]'); |
||||
} |
||||
else |
||||
{ |
||||
value = false; |
||||
} |
||||
return value; |
||||
}; |
||||
|
||||
var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); |
||||
if (isNaN(msie)) |
||||
{ |
||||
msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); |
||||
} |
||||
var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); |
||||
if (isNaN(msie)) |
||||
{ |
||||
msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); |
||||
} |
||||
|
||||
var bindonceDirective = |
||||
{ |
||||
restrict: "AM", |
||||
controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate) |
||||
{ |
||||
var showHideBinder = function (elm, attr, value) |
||||
{ |
||||
var show = (attr === 'show') ? '' : 'none'; |
||||
var hide = (attr === 'hide') ? '' : 'none'; |
||||
elm.css('display', toBoolean(value) ? show : hide); |
||||
}; |
||||
var classBinder = function (elm, value) |
||||
{ |
||||
if (angular.isObject(value) && !angular.isArray(value)) |
||||
{ |
||||
var results = []; |
||||
angular.forEach(value, function (value, index) |
||||
{ |
||||
if (value) results.push(index); |
||||
}); |
||||
value = results; |
||||
} |
||||
if (value) |
||||
{ |
||||
elm.addClass(angular.isArray(value) ? value.join(' ') : value); |
||||
} |
||||
}; |
||||
var transclude = function (transcluder, scope) |
||||
{ |
||||
transcluder.transclude(scope, function (clone) |
||||
{ |
||||
var parent = transcluder.element.parent(); |
||||
var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1]; |
||||
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; |
||||
var afterNextSibling = (afterNode && afterNode.nextSibling) || null; |
||||
angular.forEach(clone, function (node) |
||||
{ |
||||
parentNode.insertBefore(node, afterNextSibling); |
||||
}); |
||||
}); |
||||
}; |
||||
var bindonceDirective = |
||||
{ |
||||
restrict: "AM", |
||||
controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate) |
||||
{ |
||||
var showHideBinder = function (elm, attr, value) |
||||
{ |
||||
var show = (attr === 'show') ? '' : 'none'; |
||||
var hide = (attr === 'hide') ? '' : 'none'; |
||||
elm.css('display', toBoolean(value) ? show : hide); |
||||
}; |
||||
var classBinder = function (elm, value) |
||||
{ |
||||
if (angular.isObject(value) && !angular.isArray(value)) |
||||
{ |
||||
var results = []; |
||||
angular.forEach(value, function (value, index) |
||||
{ |
||||
if (value) results.push(index); |
||||
}); |
||||
value = results; |
||||
} |
||||
if (value) |
||||
{ |
||||
elm.addClass(angular.isArray(value) ? value.join(' ') : value); |
||||
} |
||||
}; |
||||
var transclude = function (transcluder, scope) |
||||
{ |
||||
transcluder.transclude(scope, function (clone) |
||||
{ |
||||
var parent = transcluder.element.parent(); |
||||
var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1]; |
||||
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; |
||||
var afterNextSibling = (afterNode && afterNode.nextSibling) || null; |
||||
angular.forEach(clone, function (node) |
||||
{ |
||||
parentNode.insertBefore(node, afterNextSibling); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
var ctrl = |
||||
{ |
||||
watcherRemover: undefined, |
||||
binders: [], |
||||
group: $attrs.boName, |
||||
element: $element, |
||||
ran: false, |
||||
var ctrl = |
||||
{ |
||||
watcherRemover: undefined, |
||||
binders: [], |
||||
group: $attrs.boName, |
||||
element: $element, |
||||
ran: false, |
||||
|
||||
addBinder: function (binder) |
||||
{ |
||||
this.binders.push(binder); |
||||
addBinder: function (binder) |
||||
{ |
||||
this.binders.push(binder); |
||||
|
||||
// In case of late binding (when using the directive bo-name/bo-parent)
|
||||
// it happens only when you use nested bindonce, if the bo-children
|
||||
// are not dom children the linking can follow another order
|
||||
if (this.ran) |
||||
{ |
||||
this.runBinders(); |
||||
} |
||||
}, |
||||
// In case of late binding (when using the directive bo-name/bo-parent)
|
||||
// it happens only when you use nested bindonce, if the bo-children
|
||||
// are not dom children the linking can follow another order
|
||||
if (this.ran) |
||||
{ |
||||
this.runBinders(); |
||||
} |
||||
}, |
||||
|
||||
setupWatcher: function (bindonceValue) |
||||
{ |
||||
var that = this; |
||||
this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) |
||||
{ |
||||
if (newValue === undefined) return; |
||||
that.removeWatcher(); |
||||
that.checkBindonce(newValue); |
||||
}, true); |
||||
}, |
||||
setupWatcher: function (bindonceValue) |
||||
{ |
||||
var that = this; |
||||
this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) |
||||
{ |
||||
if (newValue === undefined) return; |
||||
that.removeWatcher(); |
||||
that.checkBindonce(newValue); |
||||
}, true); |
||||
}, |
||||
|
||||
checkBindonce: function (value) |
||||
{ |
||||
var that = this, promise = (value.$promise) ? value.$promise.then : value.then; |
||||
// since Angular 1.2 promises are no longer
|
||||
// undefined until they don't get resolved
|
||||
if (typeof promise === 'function') |
||||
{ |
||||
promise(function () |
||||
{ |
||||
that.runBinders(); |
||||
}); |
||||
} |
||||
else |
||||
{ |
||||
that.runBinders(); |
||||
} |
||||
}, |
||||
checkBindonce: function (value) |
||||
{ |
||||
var that = this, promise = (value.$promise) ? value.$promise.then : value.then; |
||||
// since Angular 1.2 promises are no longer
|
||||
// undefined until they don't get resolved
|
||||
if (typeof promise === 'function') |
||||
{ |
||||
promise(function () |
||||
{ |
||||
that.runBinders(); |
||||
}); |
||||
} |
||||
else |
||||
{ |
||||
that.runBinders(); |
||||
} |
||||
}, |
||||
|
||||
removeWatcher: function () |
||||
{ |
||||
if (this.watcherRemover !== undefined) |
||||
{ |
||||
this.watcherRemover(); |
||||
this.watcherRemover = undefined; |
||||
} |
||||
}, |
||||
removeWatcher: function () |
||||
{ |
||||
if (this.watcherRemover !== undefined) |
||||
{ |
||||
this.watcherRemover(); |
||||
this.watcherRemover = undefined; |
||||
} |
||||
}, |
||||
|
||||
runBinders: function () |
||||
{ |
||||
while (this.binders.length > 0) |
||||
{ |
||||
var binder = this.binders.shift(); |
||||
if (this.group && this.group != binder.group) continue; |
||||
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); |
||||
switch (binder.attr) |
||||
{ |
||||
case 'boIf': |
||||
if (toBoolean(value)) |
||||
{ |
||||
transclude(binder, binder.scope.$new()); |
||||
} |
||||
break; |
||||
case 'boSwitch': |
||||
var selectedTranscludes, switchCtrl = binder.controller[0]; |
||||
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) |
||||
{ |
||||
binder.scope.$eval(binder.attrs.change); |
||||
angular.forEach(selectedTranscludes, function (selectedTransclude) |
||||
{ |
||||
transclude(selectedTransclude, binder.scope.$new()); |
||||
}); |
||||
} |
||||
break; |
||||
case 'boSwitchWhen': |
||||
var ctrl = binder.controller[0]; |
||||
ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []); |
||||
ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element }); |
||||
break; |
||||
case 'boSwitchDefault': |
||||
var ctrl = binder.controller[0]; |
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []); |
||||
ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element }); |
||||
break; |
||||
case 'hide': |
||||
case 'show': |
||||
showHideBinder(binder.element, binder.attr, value); |
||||
break; |
||||
case 'class': |
||||
classBinder(binder.element, value); |
||||
break; |
||||
case 'text': |
||||
binder.element.text(value); |
||||
break; |
||||
case 'html': |
||||
binder.element.html(value); |
||||
break; |
||||
case 'style': |
||||
binder.element.css(value); |
||||
break; |
||||
case 'src': |
||||
binder.element.attr(binder.attr, value); |
||||
if (msie) binder.element.prop('src', value); |
||||
break; |
||||
case 'attr': |
||||
angular.forEach(binder.attrs, function (attrValue, attrKey) |
||||
{ |
||||
var newAttr, newValue; |
||||
if (attrKey.match(/^boAttr./) && binder.attrs[attrKey]) |
||||
{ |
||||
newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); |
||||
newValue = binder.scope.$eval(binder.attrs[attrKey]); |
||||
binder.element.attr(newAttr, newValue); |
||||
} |
||||
}); |
||||
break; |
||||
case 'href': |
||||
case 'alt': |
||||
case 'title': |
||||
case 'id': |
||||
case 'value': |
||||
binder.element.attr(binder.attr, value); |
||||
break; |
||||
} |
||||
} |
||||
this.ran = true; |
||||
} |
||||
}; |
||||
runBinders: function () |
||||
{ |
||||
while (this.binders.length > 0) |
||||
{ |
||||
var binder = this.binders.shift(); |
||||
if (this.group && this.group != binder.group) continue; |
||||
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); |
||||
switch (binder.attr) |
||||
{ |
||||
case 'boIf': |
||||
if (toBoolean(value)) |
||||
{ |
||||
transclude(binder, binder.scope.$new()); |
||||
} |
||||
break; |
||||
case 'boSwitch': |
||||
var selectedTranscludes, switchCtrl = binder.controller[0]; |
||||
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) |
||||
{ |
||||
binder.scope.$eval(binder.attrs.change); |
||||
angular.forEach(selectedTranscludes, function (selectedTransclude) |
||||
{ |
||||
transclude(selectedTransclude, binder.scope.$new()); |
||||
}); |
||||
} |
||||
break; |
||||
case 'boSwitchWhen': |
||||
var ctrl = binder.controller[0]; |
||||
ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []); |
||||
ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element }); |
||||
break; |
||||
case 'boSwitchDefault': |
||||
var ctrl = binder.controller[0]; |
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []); |
||||
ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element }); |
||||
break; |
||||
case 'hide': |
||||
case 'show': |
||||
showHideBinder(binder.element, binder.attr, value); |
||||
break; |
||||
case 'class': |
||||
classBinder(binder.element, value); |
||||
break; |
||||
case 'text': |
||||
binder.element.text(value); |
||||
break; |
||||
case 'html': |
||||
binder.element.html(value); |
||||
break; |
||||
case 'style': |
||||
binder.element.css(value); |
||||
break; |
||||
case 'src': |
||||
binder.element.attr(binder.attr, value); |
||||
if (msie) binder.element.prop('src', value); |
||||
break; |
||||
case 'attr': |
||||
angular.forEach(binder.attrs, function (attrValue, attrKey) |
||||
{ |
||||
var newAttr, newValue; |
||||
if (attrKey.match(/^boAttr./) && binder.attrs[attrKey]) |
||||
{ |
||||
newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); |
||||
newValue = binder.scope.$eval(binder.attrs[attrKey]); |
||||
binder.element.attr(newAttr, newValue); |
||||
} |
||||
}); |
||||
break; |
||||
case 'href': |
||||
case 'alt': |
||||
case 'title': |
||||
case 'id': |
||||
case 'value': |
||||
binder.element.attr(binder.attr, value); |
||||
break; |
||||
} |
||||
} |
||||
this.ran = true; |
||||
} |
||||
}; |
||||
|
||||
angular.extend(this, ctrl); |
||||
}], |
||||
angular.extend(this, ctrl); |
||||
}], |
||||
|
||||
link: function (scope, elm, attrs, bindonceController) |
||||
{ |
||||
var value = attrs.bindonce && scope.$eval(attrs.bindonce); |
||||
if (value !== undefined) |
||||
{ |
||||
bindonceController.checkBindonce(value); |
||||
} |
||||
else |
||||
{ |
||||
bindonceController.setupWatcher(attrs.bindonce); |
||||
elm.bind("$destroy", bindonceController.removeWatcher); |
||||
} |
||||
} |
||||
}; |
||||
link: function (scope, elm, attrs, bindonceController) |
||||
{ |
||||
var value = attrs.bindonce && scope.$eval(attrs.bindonce); |
||||
if (value !== undefined) |
||||
{ |
||||
bindonceController.checkBindonce(value); |
||||
} |
||||
else |
||||
{ |
||||
bindonceController.setupWatcher(attrs.bindonce); |
||||
elm.bind("$destroy", bindonceController.removeWatcher); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
return bindonceDirective; |
||||
}); |
||||
return bindonceDirective; |
||||
}); |
||||
|
||||
angular.forEach( |
||||
[ |
||||
{ directiveName: 'boShow', attribute: 'show' }, |
||||
{ directiveName: 'boHide', attribute: 'hide' }, |
||||
{ directiveName: 'boClass', attribute: 'class' }, |
||||
{ directiveName: 'boText', attribute: 'text' }, |
||||
{ directiveName: 'boBind', attribute: 'text' }, |
||||
{ directiveName: 'boHtml', attribute: 'html' }, |
||||
{ directiveName: 'boSrcI', attribute: 'src', interpolate: true }, |
||||
{ directiveName: 'boSrc', attribute: 'src' }, |
||||
{ directiveName: 'boHrefI', attribute: 'href', interpolate: true }, |
||||
{ directiveName: 'boHref', attribute: 'href' }, |
||||
{ directiveName: 'boAlt', attribute: 'alt' }, |
||||
{ directiveName: 'boTitle', attribute: 'title' }, |
||||
{ directiveName: 'boId', attribute: 'id' }, |
||||
{ directiveName: 'boStyle', attribute: 'style' }, |
||||
{ directiveName: 'boValue', attribute: 'value' }, |
||||
{ directiveName: 'boAttr', attribute: 'attr' }, |
||||
angular.forEach( |
||||
[ |
||||
{ directiveName: 'boShow', attribute: 'show' }, |
||||
{ directiveName: 'boHide', attribute: 'hide' }, |
||||
{ directiveName: 'boClass', attribute: 'class' }, |
||||
{ directiveName: 'boText', attribute: 'text' }, |
||||
{ directiveName: 'boBind', attribute: 'text' }, |
||||
{ directiveName: 'boHtml', attribute: 'html' }, |
||||
{ directiveName: 'boSrcI', attribute: 'src', interpolate: true }, |
||||
{ directiveName: 'boSrc', attribute: 'src' }, |
||||
{ directiveName: 'boHrefI', attribute: 'href', interpolate: true }, |
||||
{ directiveName: 'boHref', attribute: 'href' }, |
||||
{ directiveName: 'boAlt', attribute: 'alt' }, |
||||
{ directiveName: 'boTitle', attribute: 'title' }, |
||||
{ directiveName: 'boId', attribute: 'id' }, |
||||
{ directiveName: 'boStyle', attribute: 'style' }, |
||||
{ directiveName: 'boValue', attribute: 'value' }, |
||||
{ directiveName: 'boAttr', attribute: 'attr' }, |
||||
|
||||
{ directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 }, |
||||
{ directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } }, |
||||
{ directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' }, |
||||
{ directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' } |
||||
], |
||||
function (boDirective) |
||||
{ |
||||
var childPriority = 200; |
||||
return bindonceModule.directive(boDirective.directiveName, function () |
||||
{ |
||||
var bindonceDirective = |
||||
{ |
||||
priority: boDirective.priority || childPriority, |
||||
transclude: boDirective.transclude || false, |
||||
terminal: boDirective.terminal || false, |
||||
require: ['^bindonce'].concat(boDirective.require || []), |
||||
controller: boDirective.controller, |
||||
compile: function (tElement, tAttrs, transclude) |
||||
{ |
||||
return function (scope, elm, attrs, controllers) |
||||
{ |
||||
var bindonceController = controllers[0]; |
||||
var name = attrs.boParent; |
||||
if (name && bindonceController.group !== name) |
||||
{ |
||||
var element = bindonceController.element.parent(); |
||||
bindonceController = undefined; |
||||
var parentValue; |
||||
{ directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 }, |
||||
{ directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } }, |
||||
{ directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' }, |
||||
{ directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' } |
||||
], |
||||
function (boDirective) |
||||
{ |
||||
var childPriority = 200; |
||||
return bindonceModule.directive(boDirective.directiveName, function () |
||||
{ |
||||
var bindonceDirective = |
||||
{ |
||||
priority: boDirective.priority || childPriority, |
||||
transclude: boDirective.transclude || false, |
||||
terminal: boDirective.terminal || false, |
||||
require: ['^bindonce'].concat(boDirective.require || []), |
||||
controller: boDirective.controller, |
||||
compile: function (tElement, tAttrs, transclude) |
||||
{ |
||||
return function (scope, elm, attrs, controllers) |
||||
{ |
||||
var bindonceController = controllers[0]; |
||||
var name = attrs.boParent; |
||||
if (name && bindonceController.group !== name) |
||||
{ |
||||
var element = bindonceController.element.parent(); |
||||
bindonceController = undefined; |
||||
var parentValue; |
||||
|
||||
while (element[0].nodeType !== 9 && element.length) |
||||
{ |
||||
if ((parentValue = element.data('$bindonceController')) |
||||
&& parentValue.group === name) |
||||
{ |
||||
bindonceController = parentValue; |
||||
break; |
||||
} |
||||
element = element.parent(); |
||||
} |
||||
if (!bindonceController) |
||||
{ |
||||
throw new Error("No bindonce controller: " + name); |
||||
} |
||||
} |
||||
while (element[0].nodeType !== 9 && element.length) |
||||
{ |
||||
if ((parentValue = element.data('$bindonceController')) |
||||
&& parentValue.group === name) |
||||
{ |
||||
bindonceController = parentValue; |
||||
break; |
||||
} |
||||
element = element.parent(); |
||||
} |
||||
if (!bindonceController) |
||||
{ |
||||
throw new Error("No bindonce controller: " + name); |
||||
} |
||||
} |
||||
|
||||
bindonceController.addBinder( |
||||
{ |
||||
element: elm, |
||||
attr: boDirective.attribute || boDirective.directiveName, |
||||
attrs: attrs, |
||||
value: attrs[boDirective.directiveName], |
||||
interpolate: boDirective.interpolate, |
||||
group: name, |
||||
transclude: transclude, |
||||
controller: controllers.slice(1), |
||||
scope: scope |
||||
}); |
||||
}; |
||||
} |
||||
}; |
||||
bindonceController.addBinder( |
||||
{ |
||||
element: elm, |
||||
attr: boDirective.attribute || boDirective.directiveName, |
||||
attrs: attrs, |
||||
value: attrs[boDirective.directiveName], |
||||
interpolate: boDirective.interpolate, |
||||
group: name, |
||||
transclude: transclude, |
||||
controller: controllers.slice(1), |
||||
scope: scope |
||||
}); |
||||
}; |
||||
} |
||||
}; |
||||
|
||||
return bindonceDirective; |
||||
}); |
||||
}) |
||||
return bindonceDirective; |
||||
}); |
||||
}) |
||||
})(); |
||||
|
Loading…
Reference in new issue