自定义网格列
{align="center"}
可以使用contentFragmentGrid
扩展点将自定义网格列添加到内容片段控制台。 此示例说明如何添加自定义列,该列以人类可读的格式显示基于上次修改日期的内容片段时限。
扩展点
此示例扩展到扩展点contentFragmentGrid
以向内容片段控制台添加自定义列。
扩展示例
以下示例创建一个自定义列Age
,该列以人类可读的格式显示内容片段的年龄。 期限是从内容片段的上次修改日期开始计算的。
该代码显示了如何在扩展的注册文件中获取内容片段的元数据,以及如何导出内容片段的JSON内容。
此示例使用Luxon库计算通过npm i luxon
安装的内容片段的存留期。
延期注册
ExtensionRegistration.js
映射到index.html路由,是AEM扩展的入口点,并定义:
- 扩展的位置将在AEM创作体验中注入自身(
contentFragmentGrid
) getColumns()
函数中自定义列的定义- 每个自定义列的值(按行)
import React from "react";
import { generatePath } from "react-router";
import { Text } from "@adobe/react-spectrum";
import { register } from "@adobe/uix-guest";
import { extensionId } from "./Constants";
import { Duration } from "luxon";
/**
* Set up a in-memory cache of the custom column data.
* This is important if the work to contain column data is expensive, such as making HTTP requests to obtain the value.
*
* The caching of computed value is optional, but recommended if the work to compute the column data is expensive.
*/
const COLUMN_AGE_ID = "age";
const cache = {
[COLUMN_AGE_ID]: {},
};
function ExtensionRegistration() {
const init = async () => {
const guestConnection = await register({
id: extensionId,
methods: {
contentFragmentGrid: {
getColumns() {
return [
{
id: COLUMN_AGE_ID, // Give the column a unique ID.
label: "Age", // The label to display
render: async function (fragments) {
// This function is run for each row in the grid.
// The fragments parameter is an array of all the fragments (aka each row) in the grid.
const context = await guestConnection.hostConnection.getRemoteApi().getSharedContext();
// Iterate over each fragment in the grid
for (const fragment of fragments) {
// Check if a previous pass has computed the value for this fragment.id. If it has, we can skip it.
if (!cache[COLUMN_AGE_ID][fragment.id]) {
// If the fragment.id has not been computed and cached, then compute the value and cache it.
cache[COLUMN_AGE_ID][fragment.id] = await computeAgeColumnValue(fragment, context);
}
}
// Return the populated cache of the custom column data.
return cache[COLUMN_AGE_ID];
}
},
// Add other custom columns here...
];
},
},
},
});
};
init().catch(console.error);
return <Text>IFrame for integration with Host (AEM)...</Text>;
}
async function computeAgeColumnValue(fragment, context) {
// Various data is available in the sharedContext, such as the AEM host, the IMS token, and the fragment itself.
// Accessing AEM APIs requires the IMS token, which is available in the sharedContext, and also supporting CORS configurations deployed to AEM Author.
//const aemHost = context.aemHost;
//const accessToken = context.auth.imsToken;
// Get the user's locale to format the value of the column.
const locale = context.locale;
// Compute the value of the column, in this case we are computing the age of the fragment from its last modified date.
const duration = Duration.fromMillis(Date.now() - fragment.modifiedDate).rescale();
let largestUnit = {};
if (duration.years > 0) {
largestUnit = {years: duration.years};
} else if (duration.months > 0) {
largestUnit = {months: duration.months};
} else if (duration.days > 0) {
largestUnit = {days: duration.days};
} else if (duration.hours > 0) {
largestUnit = {hours: duration.hours};
} else if (duration.minutes > 0) {
largestUnit = {minutes: duration.minutes};
} else if (duration.seconds > 0) {
largestUnit = {seconds: duration.seconds};
}
// Convert the largest unit of the age to human readable format.
const columnValue = Duration.fromObject(largestUnit, {locale: locale}).toHuman();
// Return the value of the column.
return columnValue;
}
export default ExtensionRegistration;
内容片段数据
向getColumns()
中的render(..)
方法传递了一个片段数组。 数组中的每个对象表示网格中的一行,并包含以下有关内容片段的元数据。 此元数据可用于网格中的常用自定义列。
render: async function (fragments) {
for (const fragment of fragments) {
// An example value from this console log is displayed below.
console.log(fragment);
}
}
示例内容片段JSON,可用作render(..)
方法中fragments
参数的元素。
{
"id": "/content/dam/wknd-shared/en/magazine/alaska-adventure/alaskan-adventures",
"name": "alaskan-adventures",
"title": "Alaskan Adventures",
"status": "draft",
"statusPreview": null,
"model": {
"name": "Article",
"id": "/conf/wknd-shared/settings/dam/cfm/models/article"
},
"folderId": "/content/dam/wknd-shared/en/magazine/alaska-adventure",
"folderName": "Alaska Adventure",
"createdBy": "admin",
"createdDate": 1684875665786,
"modifiedBy": "admin",
"modifiedDate": 1654104774889,
"publishedBy": "",
"publishedDate": 0,
"locale": "",
"main": true,
"translations": {
"locale": "en",
"languageCopies": [
{
"path": "/content/dam/wknd-shared/en/magazine/alaska-adventure/alaskan-adventures",
"title": "Alaskan Adventures",
"locale": "en",
"model": "Article",
"status": "DRAFT",
"id": "/content/dam/wknd-shared/en/magazine/alaska-adventure/alaskan-adventures"
}
]
},
"transientStatus": {},
"extensions": {
"age": "1 year old"
}
}
如果需要其他数据来填充自定义列,可以向AEM Author发出HTTP请求以检索数据。
IMPORTANT
确保将AEM创作实例配置为允许来自运行AppBuilder应用程序的源的跨源请求。 允许的源包括
https://localhost:9080
、AppBuilder暂存源和AppBuilder生产源。或者,扩展也可以调用自定义AppBuilder操作,以代表扩展向AEM Author发出请求。
const context = await guestConnection.hostConnection.getRemoteApi().getSharedContext();
...
// Fetch the Content Fragment JSON from AEM Author using the AEM Assets HTTP API
const response = await fetch(`${context.aemHost}${fragment.id.slice('/content/dam'.length)}.json`, {
headers: {
'Authorization': `Bearer ${context.auth.imsToken}`
}
})
...
列定义
渲染方法的结果是一个JavaScript对象,其键是内容片段的路径(或fragment.id
),值为要在列中显示的值。
例如,此扩展针对age
列的结果为:
{
"/content/dam/wknd-shared/en/magazine/la-skateparks/ultimate-guide-to-la-skateparks": "22 minutes",
"/content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp": "1 hour",
"/content/dam/wknd-shared/en/magazine/western-australia/western-australia-by-camper-van": "1 hour",
"/content/dam/wknd-shared/en/adventures/climbing-new-zealand/climbing-new-zealand": "10 months",
"/content/dam/wknd-shared/en/magazine/skitouring/skitouring": "1 year",
"/content/dam/wknd-shared/en/adventures/beervana-portland/beervana-in-portland": "1 year",
"/content/dam/wknd-shared/en/magazine/alaska-adventure/alaskan-adventures": "1 year",
"/content/dam/wknd-shared/en/magazine/arctic-surfing/aloha-spirits-in-northern-norway": "1 year",
"/content/dam/wknd-shared/en/magazine/san-diego-surf-spots/san-diego-surfspots": "1 year",
"/content/dam/wknd-shared/en/magazine/fly-fishing-amazon/fly-fishing": "1 year",
"/content/dam/wknd-shared/en/adventures/napa-wine-tasting/napa-wine-tasting": "1 year",
"/content/dam/wknd-shared/en/adventures/cycling-southern-utah/cycling-southern-utah": "1 year",
"/content/dam/wknd-shared/en/adventures/gastronomic-marais-tour/gastronomic-marais-tour": "1 year",
"/content/dam/wknd-shared/en/adventures/tahoe-skiing/tahoe-skiing": "1 year",
"/content/dam/wknd-shared/en/adventures/surf-camp-in-costa-rica/surf-camp-costa-rica": "1 year",
"/content/dam/wknd-shared/en/adventures/yosemite-backpacking/yosemite-backpacking": "1 year",
"/content/dam/wknd-shared/en/adventures/whistler-mountain-biking/whistler-mountain-biking": "1 year",
"/content/dam/wknd-shared/en/adventures/colorado-rock-climbing/colorado-rock-climbing": "1 year",
"/content/dam/wknd-shared/en/adventures/ski-touring-mont-blanc/ski-touring-mont-blanc": "1 year",
"/content/dam/wknd-shared/en/adventures/cycling-tuscany/cycling-tuscany": "1 year",
"/content/dam/wknd-shared/en/adventures/west-coast-cycling/west-coast-cycling": "1 year",
"/content/dam/wknd-shared/en/adventures/downhill-skiing-wyoming/downhill-skiing-wyoming": "1 year",
"/content/dam/wknd-shared/en/adventures/riverside-camping-australia/riverside-camping-australia": "1 year",
"/content/dam/wknd-shared/en/contributors/ian-provo": "1 year",
"/content/dam/wknd-shared/en/contributors/sofia-sj-berg": "1 year",
"/content/dam/wknd-shared/en/contributors/justin-barr": "1 year",
"/content/dam/wknd-shared/en/contributors/jake-hammer": "1 year",
"/content/dam/wknd-shared/en/contributors/jacob-wester": "1 year",
"/content/dam/wknd-shared/en/contributors/stacey-roswells": "1 year",
"/content/dam/wknd-shared/en/contributors/kumar-selveraj": "1 year"
}
recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69