refactor(render): move services to render folder
property_setter_factory selector style_inliner style_url_resolver shadow_css
This commit is contained in:
@@ -3,13 +3,13 @@ import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Parser} from 'angular2/change_detection';
|
||||
|
||||
import {SelectorMatcher, CssSelector} from './selector';
|
||||
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
import {setterFactory} from './property_setter_factory';
|
||||
import {setterFactory} from 'angular2/src/render/dom/compiler/property_setter_factory';
|
||||
|
||||
import {DirectiveMetadata} from '../../api';
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from '../util';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
import {dashCaseToCamelCase} from '../util';
|
||||
import {setterFactory} from './property_setter_factory';
|
||||
import {setterFactory} from 'angular2/src/render/dom/compiler/property_setter_factory';
|
||||
|
||||
// Group 1 = "bind-"
|
||||
// Group 2 = "var-" or "#"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import {StringWrapper, RegExpWrapper, BaseException, isPresent, isBlank, isString, stringify} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {camelCaseToDashCase, dashCaseToCamelCase} from '../util';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
const STYLE_SEPARATOR = '.';
|
||||
var propertySettersCache = StringMapWrapper.create();
|
||||
var innerHTMLSetterCache;
|
||||
|
||||
export function setterFactory(property: string): Function {
|
||||
var setterFn, styleParts, styleSuffix;
|
||||
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
|
||||
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
|
||||
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
|
||||
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
|
||||
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
|
||||
styleParts = property.split(STYLE_SEPARATOR);
|
||||
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
|
||||
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
|
||||
} else if (StringWrapper.equals(property, 'innerHtml')) {
|
||||
if (isBlank(innerHTMLSetterCache)) {
|
||||
innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value);
|
||||
}
|
||||
setterFn = innerHTMLSetterCache;
|
||||
} else {
|
||||
property = resolvePropertyName(property);
|
||||
setterFn = StringMapWrapper.get(propertySettersCache, property);
|
||||
if (isBlank(setterFn)) {
|
||||
var propertySetterFn = reflector.setter(property);
|
||||
setterFn = function(receiver, value) {
|
||||
if (DOM.hasProperty(receiver, property)) {
|
||||
return propertySetterFn(receiver, value);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(propertySettersCache, property, setterFn);
|
||||
}
|
||||
}
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const ATTRIBUTE_PREFIX = 'attr.';
|
||||
var attributeSettersCache = StringMapWrapper.create();
|
||||
|
||||
function _isValidAttributeValue(attrName:string, value: any): boolean {
|
||||
if (attrName == "role") {
|
||||
return isString(value);
|
||||
} else {
|
||||
return isPresent(value);
|
||||
}
|
||||
}
|
||||
|
||||
function attributeSetterFactory(attrName:string): Function {
|
||||
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
|
||||
var dashCasedAttributeName;
|
||||
|
||||
if (isBlank(setterFn)) {
|
||||
dashCasedAttributeName = camelCaseToDashCase(attrName);
|
||||
setterFn = function(element, value) {
|
||||
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
|
||||
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||
} else {
|
||||
if (isPresent(value)) {
|
||||
throw new BaseException("Invalid " + dashCasedAttributeName +
|
||||
" attribute, only string values are allowed, got '" + stringify(value) + "'");
|
||||
}
|
||||
DOM.removeAttribute(element, dashCasedAttributeName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const CLASS_PREFIX = 'class.';
|
||||
var classSettersCache = StringMapWrapper.create();
|
||||
|
||||
function classSetterFactory(className:string): Function {
|
||||
var setterFn = StringMapWrapper.get(classSettersCache, className);
|
||||
var dashCasedClassName;
|
||||
if (isBlank(setterFn)) {
|
||||
dashCasedClassName = camelCaseToDashCase(className);
|
||||
setterFn = function(element, value) {
|
||||
if (value) {
|
||||
DOM.addClass(element, dashCasedClassName);
|
||||
} else {
|
||||
DOM.removeClass(element, dashCasedClassName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(classSettersCache, className, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const STYLE_PREFIX = 'style.';
|
||||
var styleSettersCache = StringMapWrapper.create();
|
||||
|
||||
function styleSetterFactory(styleName:string, styleSuffix:string): Function {
|
||||
var cacheKey = styleName + styleSuffix;
|
||||
var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey);
|
||||
var dashCasedStyleName;
|
||||
|
||||
if (isBlank(setterFn)) {
|
||||
dashCasedStyleName = camelCaseToDashCase(styleName);
|
||||
setterFn = function(element, value) {
|
||||
var valAsStr;
|
||||
if (isPresent(value)) {
|
||||
valAsStr = stringify(value);
|
||||
DOM.setStyle(element, dashCasedStyleName, valAsStr + styleSuffix);
|
||||
} else {
|
||||
DOM.removeStyle(element, dashCasedStyleName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(styleSettersCache, cacheKey, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
function resolvePropertyName(attrName:string): string {
|
||||
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
|
||||
return isPresent(mappedPropName) ? mappedPropName : attrName;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -3,8 +3,8 @@ import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {StyleInliner} from './style_inliner';
|
||||
import {StyleUrlResolver} from './style_url_resolver';
|
||||
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
|
||||
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
|
||||
import {EmulatedUnscopedShadowDomStrategy} from './emulated_unscoped_shadow_dom_strategy';
|
||||
import {
|
||||
getContentAttribute, getHostAttribute, getComponentId, shimCssForComponent, insertStyleElement
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {XHR} from 'angular2/src/services/xhr';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
import {StyleUrlResolver} from './style_url_resolver';
|
||||
|
||||
import {
|
||||
isBlank,
|
||||
isPresent,
|
||||
RegExp,
|
||||
RegExpWrapper,
|
||||
StringWrapper,
|
||||
normalizeBlank,
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
Promise,
|
||||
PromiseWrapper,
|
||||
} from 'angular2/src/facade/async';
|
||||
|
||||
/**
|
||||
* Inline @import rules in the given CSS.
|
||||
*
|
||||
* When an @import rules is inlined, it's url are rewritten.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StyleInliner {
|
||||
_xhr: XHR;
|
||||
_urlResolver: UrlResolver;
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
|
||||
constructor(xhr: XHR, styleUrlResolver: StyleUrlResolver, urlResolver: UrlResolver) {
|
||||
this._xhr = xhr;
|
||||
this._urlResolver = urlResolver;
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline the @imports rules in the given CSS text.
|
||||
*
|
||||
* The baseUrl is required to rewrite URLs in the inlined content.
|
||||
*
|
||||
* @param {string} cssText
|
||||
* @param {string} baseUrl
|
||||
* @returns {*} a Promise<string> when @import rules are present, a string otherwise
|
||||
*/
|
||||
// TODO(vicb): Union types: returns either a Promise<string> or a string
|
||||
// TODO(vicb): commented out @import rules should not be inlined
|
||||
inlineImports(cssText: string, baseUrl: string) {
|
||||
return this._inlineImports(cssText, baseUrl, []);
|
||||
}
|
||||
|
||||
_inlineImports(cssText: string, baseUrl: string, inlinedUrls: List<string>) {
|
||||
var partIndex = 0;
|
||||
var parts = StringWrapper.split(cssText, _importRe);
|
||||
|
||||
if (parts.length === 1) {
|
||||
// no @import rule found, return the original css
|
||||
return cssText;
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
|
||||
while (partIndex < parts.length - 1) {
|
||||
// prefix is the content before the @import rule
|
||||
var prefix = parts[partIndex];
|
||||
// rule is the parameter of the @import rule
|
||||
var rule = parts[partIndex + 1];
|
||||
var url = _extractUrl(rule);
|
||||
if (isPresent(url)) {
|
||||
url = this._urlResolver.resolve(baseUrl, url);
|
||||
}
|
||||
var mediaQuery = _extractMediaQuery(rule);
|
||||
var promise;
|
||||
|
||||
if (isBlank(url)) {
|
||||
promise = PromiseWrapper.resolve(`/* Invalid import rule: "@import ${rule};" */`);
|
||||
} else if (ListWrapper.contains(inlinedUrls, url)) {
|
||||
// The current import rule has already been inlined, return the prefix only
|
||||
// Importing again might cause a circular dependency
|
||||
promise = PromiseWrapper.resolve(prefix);
|
||||
} else {
|
||||
ListWrapper.push(inlinedUrls, url);
|
||||
promise = PromiseWrapper.then(
|
||||
this._xhr.get(url),
|
||||
(css) => {
|
||||
// resolve nested @import rules
|
||||
css = this._inlineImports(css, url, inlinedUrls);
|
||||
if (PromiseWrapper.isPromise(css)) {
|
||||
// wait until nested @import are inlined
|
||||
return css.then((css) => {
|
||||
return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n'
|
||||
}) ;
|
||||
} else {
|
||||
// there are no nested @import, return the css
|
||||
return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n';
|
||||
}
|
||||
},
|
||||
(error) => `/* failed to import ${url} */\n`
|
||||
);
|
||||
}
|
||||
ListWrapper.push(promises, promise);
|
||||
partIndex += 2;
|
||||
}
|
||||
|
||||
return PromiseWrapper.all(promises).then(function (cssParts) {
|
||||
var cssText = cssParts.join('');
|
||||
if (partIndex < parts.length) {
|
||||
// append then content located after the last @import rule
|
||||
cssText += parts[partIndex];
|
||||
}
|
||||
return cssText;
|
||||
});
|
||||
}
|
||||
|
||||
_transformImportedCss(css: string, mediaQuery: string, url: string): string {
|
||||
css = this._styleUrlResolver.resolveUrls(css, url);
|
||||
return _wrapInMediaRule(css, mediaQuery);
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the url from an import rule, supported formats:
|
||||
// - 'url' / "url",
|
||||
// - url(url) / url('url') / url("url")
|
||||
function _extractUrl(importRule: string): string {
|
||||
var match = RegExpWrapper.firstMatch(_urlRe, importRule);
|
||||
if (isBlank(match)) return null;
|
||||
return isPresent(match[1]) ? match[1] : match[2];
|
||||
}
|
||||
|
||||
// Extracts the media query from an import rule.
|
||||
// Returns null when there is no media query.
|
||||
function _extractMediaQuery(importRule: string): string {
|
||||
var match = RegExpWrapper.firstMatch(_mediaQueryRe, importRule);
|
||||
if (isBlank(match)) return null;
|
||||
var mediaQuery = match[1].trim();
|
||||
return (mediaQuery.length > 0) ? mediaQuery: null;
|
||||
}
|
||||
|
||||
// Wraps the css in a media rule when the media query is not null
|
||||
function _wrapInMediaRule(css: string, query: string): string {
|
||||
return (isBlank(query)) ? css : `@media ${query} {\n${css}\n}`;
|
||||
}
|
||||
|
||||
var _importRe = RegExpWrapper.create('@import\\s+([^;]+);');
|
||||
var _urlRe = RegExpWrapper.create(
|
||||
'url\\(\\s*?[\'"]?([^\'")]+)[\'"]?|' + // url(url) or url('url') or url("url")
|
||||
'[\'"]([^\'")]+)[\'"]' // "url" or 'url'
|
||||
);
|
||||
var _mediaQueryRe = RegExpWrapper.create('[\'"][^\'"]+[\'"]\\s*\\)?\\s*(.*)');
|
||||
@@ -0,0 +1,40 @@
|
||||
// Some of the code comes from WebComponents.JS
|
||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
|
||||
/**
|
||||
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StyleUrlResolver {
|
||||
_resolver: UrlResolver;
|
||||
|
||||
constructor(resolver: UrlResolver) {
|
||||
this._resolver = resolver;
|
||||
}
|
||||
|
||||
resolveUrls(cssText: string, baseUrl: string) {
|
||||
cssText = this._replaceUrls(cssText, _cssUrlRe, baseUrl);
|
||||
cssText = this._replaceUrls(cssText, _cssImportRe, baseUrl);
|
||||
return cssText;
|
||||
}
|
||||
|
||||
_replaceUrls(cssText: string, re: RegExp, baseUrl: string) {
|
||||
return StringWrapper.replaceAllMapped(cssText, re, (m) => {
|
||||
var pre = m[1];
|
||||
var url = StringWrapper.replaceAll(m[2], _quoteRe, '');
|
||||
var post = m[3];
|
||||
|
||||
var resolvedUrl = this._resolver.resolve(baseUrl, url);
|
||||
|
||||
return pre + "'" + resolvedUrl + "'" + post;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var _cssUrlRe = RegExpWrapper.create('(url\\()([^)]*)(\\))');
|
||||
var _cssImportRe = RegExpWrapper.create('(@import[\\s]+(?!url\\())[\'"]([^\'"]*)[\'"](.*;)');
|
||||
var _quoteRe = RegExpWrapper.create('[\'"]');
|
||||
Reference in New Issue
Block a user