2015-09-11 16:35:46 -04:00
|
|
|
import {TypeMetadata, TemplateMetadata} from './api';
|
|
|
|
import {ViewEncapsulation} from 'angular2/src/core/render/api';
|
|
|
|
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
2015-08-25 18:36:02 -04:00
|
|
|
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
|
|
|
|
|
|
|
|
import {XHR} from 'angular2/src/core/render/xhr';
|
|
|
|
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
2015-09-02 18:07:31 -04:00
|
|
|
import {resolveStyleUrls} from './style_url_resolver';
|
2015-08-25 18:36:02 -04:00
|
|
|
|
|
|
|
import {
|
|
|
|
HtmlAstVisitor,
|
|
|
|
HtmlElementAst,
|
|
|
|
HtmlTextAst,
|
|
|
|
HtmlAttrAst,
|
|
|
|
HtmlAst,
|
|
|
|
htmlVisitAll
|
|
|
|
} from './html_ast';
|
|
|
|
import {HtmlParser} from './html_parser';
|
|
|
|
|
|
|
|
const NG_CONTENT_SELECT_ATTR = 'select';
|
|
|
|
const NG_CONTENT_ELEMENT = 'ng-content';
|
|
|
|
const LINK_ELEMENT = 'link';
|
|
|
|
const LINK_STYLE_REL_ATTR = 'rel';
|
|
|
|
const LINK_STYLE_HREF_ATTR = 'href';
|
|
|
|
const LINK_STYLE_REL_VALUE = 'stylesheet';
|
|
|
|
const STYLE_ELEMENT = 'style';
|
|
|
|
|
|
|
|
export class TemplateLoader {
|
|
|
|
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
|
2015-09-02 18:07:31 -04:00
|
|
|
private _domParser: HtmlParser) {}
|
2015-08-25 18:36:02 -04:00
|
|
|
|
2015-08-27 19:29:02 -04:00
|
|
|
loadTemplate(directiveType: TypeMetadata, encapsulation: ViewEncapsulation, template: string,
|
|
|
|
templateUrl: string, styles: string[],
|
|
|
|
styleUrls: string[]): Promise<TemplateMetadata> {
|
2015-08-25 18:36:02 -04:00
|
|
|
if (isPresent(template)) {
|
|
|
|
return PromiseWrapper.resolve(this.createTemplateFromString(
|
|
|
|
directiveType, encapsulation, template, directiveType.typeUrl, styles, styleUrls));
|
|
|
|
} else {
|
|
|
|
var sourceAbsUrl = this._urlResolver.resolve(directiveType.typeUrl, templateUrl);
|
|
|
|
return this._xhr.get(sourceAbsUrl)
|
|
|
|
.then(templateContent =>
|
|
|
|
this.createTemplateFromString(directiveType, encapsulation, templateContent,
|
|
|
|
sourceAbsUrl, styles, styleUrls));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-27 19:29:02 -04:00
|
|
|
createTemplateFromString(directiveType: TypeMetadata, encapsulation: ViewEncapsulation,
|
2015-08-25 18:36:02 -04:00
|
|
|
template: string, templateSourceUrl: string, styles: string[],
|
2015-08-27 19:29:02 -04:00
|
|
|
styleUrls: string[]): TemplateMetadata {
|
2015-08-25 18:36:02 -04:00
|
|
|
var domNodes = this._domParser.parse(template, directiveType.typeName);
|
|
|
|
var visitor = new TemplatePreparseVisitor();
|
|
|
|
var remainingNodes = htmlVisitAll(visitor, domNodes);
|
|
|
|
var allStyles = styles.concat(visitor.styles);
|
|
|
|
var allStyleUrls = styleUrls.concat(visitor.styleUrls);
|
2015-09-02 18:07:31 -04:00
|
|
|
var allResolvedStyles = allStyles.map(style => {
|
|
|
|
var styleWithImports = resolveStyleUrls(this._urlResolver, templateSourceUrl, style);
|
2015-08-25 18:36:02 -04:00
|
|
|
styleWithImports.styleUrls.forEach(styleUrl => allStyleUrls.push(styleUrl));
|
|
|
|
return styleWithImports.style;
|
|
|
|
});
|
|
|
|
|
|
|
|
var allStyleAbsUrls =
|
|
|
|
allStyleUrls.map(styleUrl => this._urlResolver.resolve(templateSourceUrl, styleUrl));
|
2015-08-27 19:29:02 -04:00
|
|
|
return new TemplateMetadata({
|
2015-08-25 18:36:02 -04:00
|
|
|
encapsulation: encapsulation,
|
2015-09-11 16:35:46 -04:00
|
|
|
template: this._domParser.unparse(remainingNodes),
|
2015-08-25 18:36:02 -04:00
|
|
|
styles: allResolvedStyles,
|
|
|
|
styleAbsUrls: allStyleAbsUrls,
|
|
|
|
ngContentSelectors: visitor.ngContentSelectors
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TemplatePreparseVisitor implements HtmlAstVisitor {
|
|
|
|
ngContentSelectors: string[] = [];
|
|
|
|
styles: string[] = [];
|
|
|
|
styleUrls: string[] = [];
|
|
|
|
|
2015-09-11 16:35:46 -04:00
|
|
|
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst {
|
2015-08-25 18:36:02 -04:00
|
|
|
var selectAttr = null;
|
|
|
|
var hrefAttr = null;
|
|
|
|
var relAttr = null;
|
|
|
|
ast.attrs.forEach(attr => {
|
|
|
|
if (attr.name == NG_CONTENT_SELECT_ATTR) {
|
|
|
|
selectAttr = attr.value;
|
|
|
|
} else if (attr.name == LINK_STYLE_HREF_ATTR) {
|
|
|
|
hrefAttr = attr.value;
|
|
|
|
} else if (attr.name == LINK_STYLE_REL_ATTR) {
|
|
|
|
relAttr = attr.value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
var nodeName = ast.name;
|
|
|
|
var keepElement = true;
|
|
|
|
if (nodeName == NG_CONTENT_ELEMENT) {
|
2015-09-11 16:35:46 -04:00
|
|
|
this.ngContentSelectors.push(normalizeNgContentSelect(selectAttr));
|
2015-08-25 18:36:02 -04:00
|
|
|
} else if (nodeName == STYLE_ELEMENT) {
|
|
|
|
keepElement = false;
|
|
|
|
var textContent = '';
|
|
|
|
ast.children.forEach(child => {
|
|
|
|
if (child instanceof HtmlTextAst) {
|
|
|
|
textContent += (<HtmlTextAst>child).value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.styles.push(textContent);
|
|
|
|
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
|
|
|
|
keepElement = false;
|
|
|
|
this.styleUrls.push(hrefAttr);
|
|
|
|
}
|
|
|
|
if (keepElement) {
|
|
|
|
return new HtmlElementAst(ast.name, ast.attrs, htmlVisitAll(this, ast.children),
|
|
|
|
ast.sourceInfo);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2015-09-11 16:35:46 -04:00
|
|
|
visitAttr(ast: HtmlAttrAst, context: any): HtmlAttrAst { return ast; }
|
|
|
|
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|
2015-09-11 16:35:46 -04:00
|
|
|
|
|
|
|
function normalizeNgContentSelect(selectAttr: string): string {
|
|
|
|
if (isBlank(selectAttr) || selectAttr.length === 0) {
|
|
|
|
return '*';
|
|
|
|
}
|
|
|
|
return selectAttr;
|
|
|
|
}
|