2015-05-18 11:57:20 -07:00
|
|
|
import {Injectable} from 'angular2/di';
|
2015-06-15 15:57:42 +02:00
|
|
|
import {isBlank, isPresent, BaseException, stringify, isPromise} from 'angular2/src/facade/lang';
|
|
|
|
import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
|
2015-03-23 14:10:55 -07:00
|
|
|
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
|
|
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
|
|
|
2015-06-09 10:21:25 -07:00
|
|
|
import {XHR} from 'angular2/src/render/xhr';
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-04-09 21:20:11 +02:00
|
|
|
import {ViewDefinition} from '../../api';
|
2015-06-15 15:57:42 +02:00
|
|
|
|
|
|
|
import {StyleInliner} from './style_inliner';
|
|
|
|
import {StyleUrlResolver} from './style_url_resolver';
|
2015-03-23 14:10:55 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Strategy to load component templates.
|
2015-04-13 21:00:52 -07:00
|
|
|
* TODO: Make public API once we are more confident in this approach.
|
2015-03-23 14:10:55 -07:00
|
|
|
*/
|
2015-04-02 14:40:49 -07:00
|
|
|
@Injectable()
|
2015-06-24 10:43:36 +02:00
|
|
|
export class ViewLoader {
|
2015-06-17 16:21:40 -07:00
|
|
|
_cache: Map<string, Promise<string>> = new Map();
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
constructor(private _xhr: XHR, private _styleInliner: StyleInliner,
|
|
|
|
private _styleUrlResolver: StyleUrlResolver) {}
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-06-10 14:40:24 +02:00
|
|
|
load(view: ViewDefinition): Promise</*element*/ any> {
|
2015-06-15 15:57:42 +02:00
|
|
|
let tplElAndStyles: List<string | Promise<string>> = [this._loadHtml(view)];
|
2015-06-10 14:40:24 +02:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
if (isPresent(view.styles)) {
|
|
|
|
view.styles.forEach((cssText: string) => {
|
|
|
|
let textOrPromise = this._resolveAndInlineCssText(cssText, view.templateAbsUrl);
|
|
|
|
tplElAndStyles.push(textOrPromise);
|
|
|
|
});
|
2015-06-10 14:40:24 +02:00
|
|
|
}
|
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
if (isPresent(view.styleAbsUrls)) {
|
|
|
|
view.styleAbsUrls.forEach(url => {
|
|
|
|
let promise = this._loadText(url).then(
|
|
|
|
cssText => this._resolveAndInlineCssText(cssText, view.templateAbsUrl));
|
|
|
|
tplElAndStyles.push(promise);
|
|
|
|
});
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
2015-06-10 14:40:24 +02:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
// Inline the styles from the @View annotation and return a template element
|
|
|
|
return PromiseWrapper.all(tplElAndStyles)
|
2015-06-10 14:40:24 +02:00
|
|
|
.then((res: List<string>) => {
|
2015-06-15 15:57:42 +02:00
|
|
|
let tplEl = res[0];
|
|
|
|
let cssTexts = ListWrapper.slice(res, 1);
|
2015-06-10 14:40:24 +02:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
_insertCssTexts(DOM.content(tplEl), cssTexts);
|
2015-06-10 14:40:24 +02:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
return tplEl;
|
2015-06-10 14:40:24 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private _loadText(url: string): Promise<string> {
|
2015-06-17 16:21:40 -07:00
|
|
|
var response = this._cache.get(url);
|
2015-06-10 14:40:24 +02:00
|
|
|
|
|
|
|
if (isBlank(response)) {
|
|
|
|
// TODO(vicb): change error when TS gets fixed
|
|
|
|
// https://github.com/angular/angular/issues/2280
|
|
|
|
// throw new BaseException(`Failed to fetch url "${url}"`);
|
|
|
|
response = PromiseWrapper.catchError(
|
|
|
|
this._xhr.get(url),
|
|
|
|
_ => PromiseWrapper.reject(new BaseException(`Failed to fetch url "${url}"`), null));
|
|
|
|
|
2015-06-17 16:21:40 -07:00
|
|
|
this._cache.set(url, response);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
2015-06-10 14:40:24 +02:00
|
|
|
return response;
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
2015-06-15 15:57:42 +02:00
|
|
|
|
|
|
|
// Load the html and inline any style tags
|
|
|
|
private _loadHtml(view: ViewDefinition): Promise<any /* element */> {
|
|
|
|
let html;
|
|
|
|
|
|
|
|
// Load the HTML
|
|
|
|
if (isPresent(view.template)) {
|
|
|
|
html = PromiseWrapper.resolve(view.template);
|
|
|
|
} else if (isPresent(view.templateAbsUrl)) {
|
|
|
|
html = this._loadText(view.templateAbsUrl);
|
|
|
|
} else {
|
|
|
|
throw new BaseException('View should have either the templateUrl or template property set');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inline the style tags from the html
|
|
|
|
return html.then(html => {
|
|
|
|
var tplEl = DOM.createTemplate(html);
|
|
|
|
let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE');
|
|
|
|
|
|
|
|
let promises: List<Promise<string>> = [];
|
|
|
|
for (let i = 0; i < styleEls.length; i++) {
|
|
|
|
let promise = this._resolveAndInlineElement(styleEls[i], view.templateAbsUrl);
|
|
|
|
if (isPromise(promise)) {
|
|
|
|
promises.push(promise);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return promises.length > 0 ? PromiseWrapper.all(promises).then(_ => tplEl) : tplEl;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inlines a style element.
|
|
|
|
*
|
|
|
|
* @param styleEl The style element
|
|
|
|
* @param baseUrl The base url
|
|
|
|
* @returns {Promise<any>} null when no @import rule exist in the css or a Promise
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private _resolveAndInlineElement(styleEl, baseUrl: string): Promise<any> {
|
|
|
|
let textOrPromise = this._resolveAndInlineCssText(DOM.getText(styleEl), baseUrl);
|
|
|
|
|
|
|
|
if (isPromise(textOrPromise)) {
|
|
|
|
return (<Promise<string>>textOrPromise).then(css => { DOM.setText(styleEl, css); });
|
|
|
|
} else {
|
|
|
|
DOM.setText(styleEl, <string>textOrPromise);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private _resolveAndInlineCssText(cssText: string, baseUrl: string): string | Promise<string> {
|
|
|
|
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
|
|
|
|
return this._styleInliner.inlineImports(cssText, baseUrl);
|
|
|
|
}
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
2015-06-10 14:40:24 +02:00
|
|
|
|
2015-06-15 15:57:42 +02:00
|
|
|
function _insertCssTexts(element, cssTexts: List<string>): void {
|
|
|
|
if (cssTexts.length == 0) return;
|
|
|
|
|
|
|
|
let insertBefore = DOM.firstChild(element);
|
|
|
|
|
|
|
|
for (let i = cssTexts.length - 1; i >= 0; i--) {
|
|
|
|
let styleEl = DOM.createStyleElement(cssTexts[i]);
|
|
|
|
if (isPresent(insertBefore)) {
|
|
|
|
DOM.insertBefore(insertBefore, styleEl);
|
|
|
|
} else {
|
|
|
|
DOM.appendChild(element, styleEl);
|
|
|
|
}
|
|
|
|
insertBefore = styleEl;
|
|
|
|
}
|
2015-06-10 14:40:24 +02:00
|
|
|
}
|