Skip to content

Commit

Permalink
amp-date-display: render via __html string (#30226)
Browse files Browse the repository at this point in the history
* amp-date-display: render via __html string

* cleanup defaults

* cleanup RendererFunctionResponseType

* tests

* more tests

* amp-date-display has to use div

* fixed tests
  • Loading branch information
Dima Voytenko committed Sep 16, 2020
1 parent 941616d commit c26ece3
Show file tree
Hide file tree
Showing 23 changed files with 1,107 additions and 490 deletions.
5 changes: 5 additions & 0 deletions build-system/externs/preact.extern.js
Expand Up @@ -55,3 +55,8 @@ PreactDef.SimpleRenderable;
* @typedef {PreactDef.SimpleRenderable|!PreactDef.VNode|!Array<PreactDef.SimpleRenderable|!PreactDef.VNode|!Array<PreactDef.SimpleRenderable|!PreactDef.VNode>>}
*/
PreactDef.Renderable;

/**
* @typedef {{__html: ?string}}
*/
PreactDef.InnerHTML;
26 changes: 13 additions & 13 deletions examples/amp-date-display.amp.html
Expand Up @@ -27,7 +27,7 @@ <h1>Date & time</h1>
<h2>Available variables</h2>
<amp-date-display datetime="2017-08-02T20:05:05.000Z" display-in="utc" layout="fixed" width="360" height="140" >
<template type="amp-mustache">
<div>iso: {{iso}}</div>
<div>iso: <time datetime="{{iso}}">{{iso}}</time></div>
<div>day: {{day}} {{dayTwoDigit}} {{dayName}} {{dayNameShort}}</div>
<div>month: {{month}} {{monthTwoDigit}} {{monthName}} {{monthNameShort}}</div>
<div>year: {{year}} {{yearTwoDigit}}</div>
Expand All @@ -40,68 +40,68 @@ <h2>Available variables</h2>
<h2>UTC vs local</h2>
<amp-date-display datetime="2017-08-02T15:05:05.000Z" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>UTC in local: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}</div>
UTC in local: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}
</template>
</amp-date-display>
<amp-date-display datetime="2017-08-02T15:05:05.000Z" display-in="utc" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>UTC in UTC: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}</div>
UTC in UTC: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}
</template>
</amp-date-display>
<amp-date-display datetime="2017-08-02T15:05:05.000" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>local in local: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}</div>
local in local: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}
</template>
</amp-date-display>
<amp-date-display datetime="2017-08-02T15:05:05.000" display-in="utc" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>local in UTC: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}</div>
local in UTC: {{dayName}} {{day}} {{monthName}} {{year}}, {{hourTwoDigit}}:{{minuteTwoDigit}}:{{secondTwoDigit}}
</template>
</amp-date-display>

<h2>Now & offset</h2>
<amp-date-display datetime="now" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>now: {{iso}}</div>
now: {{iso}}
</template>
</amp-date-display>
<amp-date-display datetime="now" offset-seconds="-3600" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>now - 1h: {{iso}}</div>
now - 1h: {{iso}}
</template>
</amp-date-display>

<h2>Timestamps</h2>
<amp-date-display timestamp-seconds="1501686305" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>from seconds: {{iso}}</div>
from seconds: {{iso}}
</template>
</amp-date-display>
<amp-date-display timestamp-ms="1501686305000" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>from ms: {{iso}}</div>
from ms: {{iso}}
</template>
</amp-date-display>

<h2>Locales</h2>
<amp-date-display datetime="now" locale="de" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>de: {{dayName}} {{day}} {{monthName}} {{year}}</div>
de: {{dayName}} {{day}} {{monthName}} {{year}}
</template>
</amp-date-display>
<amp-date-display datetime="now" locale="fr" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>fr: {{dayName}} {{day}} {{monthName}} {{year}}</div>
fr: {{dayName}} {{day}} {{monthName}} {{year}}
</template>
</amp-date-display>
<amp-date-display datetime="now" locale="cs" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>cs: {{dayName}} {{day}} {{monthName}} {{year}}</div>
cs: {{dayName}} {{day}} {{monthName}} {{year}}
</template>
</amp-date-display>
<amp-date-display datetime="now" locale="en-GB" layout="fixed" width="360" height="20" >
<template type="amp-mustache">
<div>en-GB: {{dayName}} {{day}} {{monthName}} {{year}}</div>
en-GB: {{dayName}} {{day}} {{monthName}} {{year}}
</template>
</amp-date-display>
</div>
Expand Down
102 changes: 58 additions & 44 deletions extensions/amp-date-display/1.0/amp-date-display.js
Expand Up @@ -14,59 +14,28 @@
* limitations under the License.
*/

import * as Preact from '../../../src/preact';
import {AsyncRender} from './async-render';
import {DateDisplay} from './date-display';
import {PreactBaseElement} from '../../../src/preact/base-element';
import {RenderDomTree} from './render-dom-tree';
import {Services} from '../../../src/services';
import {dev, userAssert} from '../../../src/log';
import {dict} from '../../../src/utils/object';
import {isExperimentOn} from '../../../src/experiments';
import {isLayoutSizeDefined} from '../../../src/layout';
import {parseDate} from '../../../src/utils/date';
import {userAssert} from '../../../src/log';

/** @const {string} */
const TAG = 'amp-date-display';

class AmpDateDisplay extends PreactBaseElement {
/** @override */
init() {
const templates = Services.templatesFor(this.win);
let rendered = false;

return dict({
/**
* @param {!JsonObject} data
* @param {*} children
* @return {*}
*/
'render': (data, children) => {
// We only render once in AMP mode, but React mode may rerender
// serveral times.
if (rendered) {
return children;
}
rendered = true;

const host = this.element;
const domPromise = templates
.findAndRenderTemplate(host, data)
.then((rendered) => {
const container = document.createElement('div');
container.appendChild(rendered);

return <RenderDomTree dom={container} host={host} />;
});

return (
<>
{children}
<AsyncRender>{domPromise}</AsyncRender>
</>
);
},
});
/** @param {!AmpElement} element */
constructor(element) {
super(element);

/** @private {?../../../src/service/template-impl.Templates} */
this.templates_ = null;

/** @private {?Element} */
this.template_ = null;
}

/** @override */
Expand All @@ -77,14 +46,50 @@ class AmpDateDisplay extends PreactBaseElement {
);
return isLayoutSizeDefined(layout);
}

/** @override */
checkPropsPostMutations() {
const templates =
this.templates_ || (this.templates_ = Services.templatesFor(this.win));
const template = templates.maybeFindTemplate(this.element);
if (template != this.template_) {
this.template_ = template;
if (template) {
// Only overwrite `render` when template is ready to minimize FOUC.
templates.whenReady(template).then(() => {
if (template != this.template_) {
// A new template has been set while the old one was initializing.
return;
}
this.mutateProps(
dict({
'render': (data) => {
return templates
.renderTemplateAsString(dev().assertElement(template), data)
.then((html) => dict({'__html': html}));
},
})
);
});
} else {
this.mutateProps(dict({'render': null}));
}
}
}

/** @override */
isReady(props) {
if (this.template_ && !('render' in props)) {
// The template is specified, but not available yet.
return false;
}
return true;
}
}

/** @override */
AmpDateDisplay['Component'] = DateDisplay;

/** @override */
AmpDateDisplay['passthrough'] = true;

/** @override */
AmpDateDisplay['props'] = {
'datetime': {
Expand All @@ -95,6 +100,15 @@ AmpDateDisplay['props'] = {
'locale': {attr: 'locale'},
};

/** @override */
AmpDateDisplay['layoutSizeDefined'] = true;

/** @override */
AmpDateDisplay['lightDomTag'] = 'div';

/** @override */
AmpDateDisplay['usesTemplate'] = true;

/**
* @param {!Element} element
* @return {?number}
Expand Down
36 changes: 0 additions & 36 deletions extensions/amp-date-display/1.0/async-render.js

This file was deleted.

71 changes: 61 additions & 10 deletions extensions/amp-date-display/1.0/date-display.js
Expand Up @@ -14,12 +14,39 @@
* limitations under the License.
*/

import * as Preact from '../../../src/preact';
import {Wrapper, useRenderer} from '../../../src/preact/component';
import {getDate} from '../../../src/utils/date';
import {useMemo} from '../../../src/preact';
import {useResourcesNotify} from '../../../src/preact/utils';

/** @const {string} */
const DEFAULT_DISPLAY_IN = 'local';

/** @const {string} */
const DEFAULT_LOCALE = 'en';

/** @const {!Object<string, *>} */
const DEFAULT_DATETIME_OPTIONS = {
'year': 'numeric',
'month': 'short',
'day': 'numeric',
'hour': 'numeric',
'minute': 'numeric',
};

/** @const {!Object<string, *>} */
const DEFAULT_DATETIME_OPTIONS_UTC = {
...DEFAULT_DATETIME_OPTIONS,
timeZone: 'UTC',
};

/**
* @param {!JsonObject} data
* @return {string}
*/
const DEFAULT_RENDER = (data) => /** @type {string} */ (data['localeString']);

/** @typedef {{
year: number,
month: number,
Expand All @@ -32,6 +59,7 @@ const DEFAULT_LOCALE = 'en';
minute: number,
second: number,
iso: string,
localeString: string,
}} */
let VariablesV2Def;

Expand Down Expand Up @@ -63,23 +91,44 @@ let EnhancedVariablesV2Def;
* @param {!DateDisplayDef.Props} props
* @return {PreactDef.Renderable}
*/
export function DateDisplay(props) {
const {datetime, render, children} = props;
const date = new Date(getDate(datetime));
const data = /** @type {!JsonObject} */ (getDataForTemplate(date, props));
export function DateDisplay({
datetime,
displayIn = DEFAULT_DISPLAY_IN,
locale = DEFAULT_LOCALE,
render = DEFAULT_RENDER,
...rest
}) {
const date = getDate(datetime);
const data = useMemo(
() => getDataForTemplate(new Date(date), displayIn, locale),
[date, displayIn, locale]
);

const rendered = useRenderer(render, data);
const isHtml =
rendered && typeof rendered == 'object' && '__html' in rendered;

useResourcesNotify();

return render(data, children);
return (
<Wrapper
{...rest}
as="div"
datetime={data['iso']}
dangerouslySetInnerHTML={isHtml ? rendered : null}
>
{isHtml ? null : rendered}
</Wrapper>
);
}

/**
* @param {!Date} date
* @param {!DateDisplayDef.Props} props
* @return {?EnhancedVariablesV2Def}
* @param {string} displayIn
* @param {string} locale
* @return {!EnhancedVariablesV2Def}
*/
function getDataForTemplate(date, props) {
const {displayIn = '', locale = DEFAULT_LOCALE} = props;

function getDataForTemplate(date, displayIn, locale) {
const basicData =
displayIn.toLowerCase() === 'utc'
? getVariablesInUTC(date, locale)
Expand Down Expand Up @@ -144,6 +193,7 @@ function getVariablesInLocal(date, locale) {
'minute': date.getMinutes(),
'second': date.getSeconds(),
'iso': date.toISOString(),
'localeString': date.toLocaleString(locale, DEFAULT_DATETIME_OPTIONS),
};
}

Expand Down Expand Up @@ -177,5 +227,6 @@ function getVariablesInUTC(date, locale) {
'minute': date.getUTCMinutes(),
'second': date.getUTCSeconds(),
'iso': date.toISOString(),
'localeString': date.toLocaleString(locale, DEFAULT_DATETIME_OPTIONS_UTC),
};
}

0 comments on commit c26ece3

Please sign in to comment.