SUI 页面自动插入的内容
自动注入的脚本
在 sui 页面编译过程中,会自动的在页面中插入一些内容。
全局数据。
组件关连的脚本。
全局 js 对象。 为了方便用户操作页面,sui 页面会自动的注入一些全局对象。
Yao 对象
用于操作页面的一些 API。比如获取 Token,获取 Cookie,设置 Cookie,删除 Cookie,序列化数据等。
ts
/**
* Yao Object
* @param {*} host
*/
function Yao(host) {}
/**
* Get API
* @param {*} path
* @param {*} params
*/
Yao.prototype.Get = async function (path, params, headers) {};
/**
* Post API
* @param {*} path
* @param {*} data
* @param {*} params
* @param {*} headers
*/
Yao.prototype.Post = async function (path, data, params, headers) {};
/**
* Download API
* @param {*} path
* @param {*} params
*/
Yao.prototype.Download = async function (path, params, savefile, headers) {};
/**
*
* Fetch API
* @param {*} method
* @param {*} path
* @param {*} params
* @param {*} data
* @param {*} headers
*/
Yao.prototype.Fetch = async function (
method,
path,
params,
data,
headers,
isblob
) {};
/** 获取Token */
Yao.prototype.Token = function () {};
/** 获取Cookie */
Yao.prototype.Cookie = function (cookieName) {};
/** 设置Cookie */
Yao.prototype.SetCookie = function (cookieName, cookieValue, expireDays) {};
/** 删除Cookie */
Yao.prototype.DeleteCookie = function (cookieName) {};
/**
* Serialize To Query String
* @param {*} obj
* @returns
*/
Yao.prototype.Serialize = function (obj) {};
使用方法:
js
const yao = new Yao();
const token = yao.Token();
const cookie = yao.Cookie('cookieName');
yao.SetCookie('cookieName', 'cookieValue', 7);
yao.DeleteCookie('cookieName');
const data = yao.Serialize({ key: 'value' });
const rest = await yao.Get('/api/path', params, headers);
const rest = await yao.Post('/api/path', { key: 'value' }, params, headers);
// savefile filename to save
await yao.Download('/api/path', params, savefile, headers);
Dom 操作
Query SUI 组件选择器
ts
function $Query(selector: string | Element): __Query {
return new __Query(selector);
}
用于操作页面 DOM 的对象。
ts
/**
* Class for DOM manipulation and traversal.
*/
class __Query {
/**
* The selector or element used for querying.
*/
selector: string | Element | NodeListOf<Element> | undefined = '';
/**
* All matched elements or null.
*/
elements: NodeListOf<Element> | null = null;
/**
* Create a query instance.
*/
element: Element | null = null;
/**
* Create a query instance.
*/
constructor(selector: string | Element | NodeListOf<Element>) {
if (typeof selector === 'string') {
this.selector = selector;
this.elements = document.querySelectorAll(selector);
if (this.elements.length > 0) {
this.element = this.elements[0];
}
} else if (selector instanceof NodeList) {
this.elements = selector;
if (this.elements.length > 0) {
this.element = this.elements[0];
}
} else {
this.element = selector;
}
this.selector = selector;
}
/**
* Get the current element.
*/
elm(): Element | null {
return this.element;
}
/**
* Get all matched elements.
*/
elms(): NodeListOf<Element> | null {
return this.elements;
}
/**
* Find child elements by selector.
*/
find(selector: string): __Query | null {
const elm = this.element?.querySelector(selector);
if (elm) {
return new __Query(elm);
}
return null;
}
findAll(selector: string): __Query | null {
const elms = this.element?.querySelectorAll(selector);
if (elms) {
return new __Query(elms);
}
return null;
}
/**
* Find the closest matching ancestor.
*/
closest(selector: string): __Query | null {
const elm = this.element?.closest(selector);
if (elm) {
return new __Query(elm);
}
return null;
}
/**
* Add event listener.
*/
on(event: string, callback: (event: Event) => void): __Query {
if (!this.element) {
return this;
}
this.element.addEventListener(event, callback);
return this;
}
//根组件
$$() {
if (!this.element) {
return null;
}
const root = this.element.closest('[s\\:cn]');
if (!root) {
return null;
}
// @ts-ignore
return $$(root);
}
/**
* Iterate over elements.
*/
each(callback: (element: __Query, index: number) => void) {
if (!this.elements) {
return;
}
this.elements.forEach((element, index) => {
callback(new __Query(element), index);
});
return;
}
/**
* Get the associated store.
*/
store() {
if (!this.element || typeof this.element.getAttribute !== 'function') {
return null;
}
// @ts-ignore
return new __sui_store(this.element);
}
/**
* Get an attribute value.
*/
attr(key) {
if (!this.element || typeof this.element.getAttribute !== 'function') {
return null;
}
return this.element.getAttribute(key);
}
/**
* Get data-attribute value.
*/
data(key) {
if (!this.element || typeof this.element.getAttribute !== 'function') {
return null;
}
return this.element.getAttribute('data:' + key);
}
/**
* Get JSON data.
*/
json(key) {
if (!this.element || typeof this.element.getAttribute !== 'function') {
return null;
}
const v = this.element.getAttribute('json:' + key);
if (!v) {
return null;
}
try {
return JSON.parse(v);
} catch (e) {
console.error(`Error parsing JSON for key ${key}: ${e}`);
return null;
}
}
/**
* Get a property value.
*/
prop(key) {
if (!this.element || typeof this.element.getAttribute !== 'function') {
return null;
}
const k = 'prop:' + key;
const v = this.element.getAttribute(k);
const json = this.element.getAttribute('json-attr-prop:' + key) === 'true';
if (json && v) {
try {
return JSON.parse(v);
} catch (e) {
console.error(`Error parsing JSON for prop ${key}: ${e}`);
return null;
}
}
return v;
}
/**
* Check if the element has a class.
*/
hasClass(className) {
return this.element?.classList.contains(className);
}
/**
* Toggle classes.
*/
toggleClass(className) {
const classes = Array.isArray(className)
? className
: className?.split(' ');
classes?.forEach((c) => {
const v = c.replace(/[\n\r\s]/g, '');
if (v === '') return;
this.element?.classList.toggle(v);
});
return this;
}
/**
* Remove classes.
*/
removeClass(className) {
const classes = Array.isArray(className)
? className
: className?.split(' ');
classes?.forEach((c) => {
const v = c.replace(/[\n\r\s]/g, '');
if (v === '') return;
this.element?.classList.remove(v);
});
return this;
}
/**
* Add classes.
*/
addClass(className) {
const classes = Array.isArray(className)
? className
: className?.split(' ');
classes?.forEach((c) => {
const v = c.replace(/[\n\r\s]/g, '');
if (v === '') return;
this.element?.classList.add(v);
});
return this;
}
/**
* Get or set inner HTML.
*/
html(html?: string): __Query | string {
if (html === undefined) {
return this.element?.innerHTML || '';
}
if (this.element) {
this.element.innerHTML = html;
}
return this;
}
}
SUI 组件选择器
ts
/**
* Dom Object
* @param {*} selector 选择器,html元素或是组件,
* @returns Sui 组件
*/
function $$(selector) {
let elm: HTMLElement | null = null;
if (typeof selector === 'string') {
elm = document.querySelector(selector);
}
if (selector instanceof HTMLElement) {
elm = selector;
}
if (elm) {
const cn = elm.getAttribute('s:cn');
if (cn && cn != '' && typeof window[cn] === 'function') {
const component = new window[cn](elm);
//elm ,html元素,
//yao component组件对象,每一个组件都会有对应的js对象,会在编译的过程中自动生成。
return new __sui_component(elm, component);
}
}
return null;
}
这里会有点复杂,组件中包含了不同的对象,比如状态,事件处理,属性,方法等。
ts
/**
* 创建一个sui组件对象
* @param {*} elm 组件的html元素
* @param {*} component 组件对象,用户通常需要自定义组件对象的处理函数,与组件同名的js或是ts文件。
* @returns sui组件对象
*/
function __sui_component(elm, component) {
this.root = elm;
// 读取与保存组件的状态数据
// $$(dropdown).store.Get("selected")
this.store = new __sui_store(elm); //保存组件的数据
// 父组件传入的属性
this.props = new __sui_props(elm); //保存组件的属性
//组件的事件处理,使用Set方法来触发事件处理函数。
this.state = component ? new __sui_state(component) : {}; //保存组件的状态
const __self = this;
//封装 html 对象
this.$root = new __Query(this.root);
this.find = function (selector) {
// @ts-ignore
return new __Query(__self.root).find(selector);
};
// 查找组件的子组件
// 示例: $$(input).query("[input-element]").setAttribute("placeholder", placeholder);
this.query = function (selector) {
return __self.root.querySelector(selector);
};
// 查找组件的子组件列表
this.queryAll = function (selector) {
return __self.root.querySelectorAll(selector);
};
// 触发组件事件
this.emit = function (name, data) {
const event = new CustomEvent(name, { detail: data });
__self.root.dispatchEvent(event);
};
// 渲染组件
this.render = function (name, data, option) {
// @ts-ignore
const r = new __Render(__self, option);
return r.Exec(name, data);
};
}
/**
* 组件的状态处理,组件通过回调函数进行响应处理,如果在组件中定义了watch对象,则会自动调用watch中的函数。
*
* 把事件向传到父组件中,父组件可以通过watch对象来监听组件的状态变化。
*
* @param {*} component yao sui组件对象,用户通常需要自定义组件对象的处理函数,与组件同名的js或是ts文件。
*/
function __sui_state(component) {
this.handlers = component.watch || {}; //组件需要在组件对象中定义watch对象,用于监听组件状态的变化。
//当组件状态发生变化时,会自动调用组件对象中的watch对象中的函数。
this.Set = async function (key, value, target) {
const handler = this.handlers[key];
//target对象默认是当前组件对象,如果target对象是组件对象,则使用组件对象中的root对象【html对象】。
target = target || component.root;
if (handler && typeof handler === 'function') {
const stateObj = {
target: target,
stopPropagation: function () {
target.setAttribute('state-propagation', 'true');
}
};
// Call the state change handler,watch中的函数会有两个参数,第一个参数是新的值,第二个参数是状态对象,可以理解成事件对象。
await handler(value, stateObj);
// 默认情况下会向上传播状态变化,除非在watch中的函数中调用了stateObj.stopPropagation()函数。
const isStopPropagation = target
? target.getAttribute('state-propagation') === 'true'
: false;
if (isStopPropagation) {
return;
}
let parent = component.root.parentElement?.closest(`[s\\:cn]`);
if (parent == null) {
return;
}
// Dispatch the state change custom event to parent component
const event = new CustomEvent('state:change', {
detail: { key: key, value: value, target: component.root }
});
parent.dispatchEvent(event);
}
};
}
/**
* 组件的读取,只读取props:开头的属性。当一个组件被其它组件使用时,
* 在编译过程中,会把上级的属性并且以`props`为前缀的复制到子组件中。
* 所以这些属性都是只读的,不能修改。
*
* @param {*} elm 组件的html元素
* @returns
*/
function __sui_props(elm) {
this.Get = function (key) {
if (!elm || typeof elm.getAttribute !== 'function') {
return null;
}
const k = 'prop:' + key;
const v = elm.getAttribute(k);
const json = elm.getAttribute('json-attr-prop:' + key) === 'true';
if (json) {
try {
return JSON.parse(v);
} catch (e) {
return null;
}
}
return v;
};
this.List = function () {
const props = {};
if (!elm || typeof elm.getAttribute !== 'function') {
return props;
}
const attrs = elm.attributes;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name.startsWith('prop:')) {
const k = attr.name.replace('prop:', '');
const json = elm.getAttribute('json-attr-prop:' + k) === 'true';
if (json) {
try {
props[k] = JSON.parse(attr.value);
} catch (e) {
props[k] = null;
}
continue;
}
props[k] = attr.value;
}
}
return props;
};
}
Store 对象
用于存储页面数据的对象。
ts
/**
* 组件的状态处理,利用组件的属性来保存组件的数据
*
* @param {*} elm 组件的html元素
* @returns
*
*/
function __sui_store(elm) {
elm = elm || document.body;
this.Get = function (key) {
return elm.getAttribute('data:' + key);
};
//把数据保存在组件的html元素中
this.Set = function (key, value) {
elm.setAttribute('data:' + key, value);
};
this.GetJSON = function (key) {
const value = elm.getAttribute('json:' + key);
if (value && value != '') {
try {
const res = JSON.parse(value);
return res;
} catch (e) {
const message = e.message || e || 'An error occurred';
console.error(`[SUI] Event Handler Error: ${message}`, elm);
return null;
}
}
return null;
};
this.SetJSON = function (key, value) {
elm.setAttribute('json:' + key, JSON.stringify(value));
};
//后端的数据会通过这个属性传输到前端。
//组件需要在后端脚本【组件同名的.backend.ts】中BeforeRender方法中设置组件的数据。
this.GetData = function () {
return this.GetJSON('__component_data') || {};
};
}
Store 选择器
ts
function $Store(elm) {
if (!elm) {
return null;
}
if (typeof elm === 'string') {
elm = document.querySelectorAll(elm);
if (elm.length == 0) {
return null;
}
elm = elm[0];
}
// @ts-ignore
return new __sui_store(elm);
}
sui lib 内容,主要是 js 脚本。
语言翻译,多语言,在 js 脚本中可以使用__m
进行翻译。
html
<script type="text/javascript">
let __sui_locale = {};
try {
__sui_locale = %s;
} catch (e) { __sui_locale = {} }
function __m(message, fmt) {
if (fmt && typeof fmt === "function") {
return fmt(message, __sui_locale);
}
return __sui_locale[message] || message;
}
</script>
全局对象:__sui_data 保存了页面的全局数据。包含请求的对象,cookie,主题,语言,时区,payload,参数,url 等。
加载事件处理,处理s:ready
事件。
html
<script name="imports" type="json">
[]
</script>
<script type="text/javascript">
try {
var __sui_data = {
$query: { x: ['1223'] },
$cookie: {
TOKEN:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwic2lkIjoiMWYzZTMxNjQtYjM1ZS00MWU4LTkwZGUtNjZkOWM1ZjgxOTMxIiwiZGF0YSI6e30sImF1ZCI6IllhbyBQcm9jZXNzIHV0aWxzLmp3dC5NYWtlIiwiZXhwIjoxNzEwOTU3OTUwLCJqdGkiOiIxIiwiaWF0IjoxNzEwOTI5MTUwLCJpc3MiOiJ4aWFuZzoxIiwibmJmIjoxNzEwOTI5MTUwLCJzdWIiOiJVc2VyIFRva2VuIn0.P3pTPWn9jF__3vjo9-u7m66j9H_1IcC2zLNMCi4V8BE'
},
$theme: null,
$locale: null,
$timezone: '+08:00',
$payload: null,
$param: {},
$url: {
host: 'localhost:5099',
domain: 'localhost',
path: '/demo/index',
scheme: 'http',
url: 'http://localhost:5099/demo/index'
},
$direction: 'ltr'
};
} catch (e) {
console.log('init data error:', e);
}
document.addEventListener('DOMContentLoaded', function () {
try {
document.querySelectorAll('[s\\:ready]').forEach(function (element) {
const method = element.getAttribute('s:ready');
const cn = element.getAttribute('s:cn');
if (method && typeof window[cn] === 'function') {
try {
window[cn](element);
} catch (e) {
const message = e.message || e || 'An error occurred';
console.error(`[SUI] ${cn} Error: ${message}`);
}
}
});
} catch (e) {}
});
</script>
事件处理
函数__sui_event_init
会在页面加载完成后调用,初始化绑定页面及组件的事件。
在组件中,使用s:event-xxxx
属性来绑定事件。
在组件中使用s:data-xxxx
属性,s:json-xxxx
属性来设置事件的数据。
也可以在组件中使用data:xxx
属性,json:xxxx
属性来设置事件的数据。
html
<li
s:for="categories"
class="
px-1 py-2.5 text-sm font-medium text-gray-500 dark:text-gray-200
hover:text-primary-500 dark:hover:text-primary-400 cursor-pointer
border-b-2
{{ item.name == articles.category ? 'text-primary-500 dark:text-primary-400 border-primary-500 dark:border-primary-400' : 'border-transparent ' }}
"
s:data-category="{{ item.name }}"
s:on-click="LoadCategory"
category
>
{{ item.name }}
</li>
在.backend.ts
中,编写事件响应处理函数。
ts
/**
* When category is changed, load articles
* @param event 浏览器事件
* @param data 组件的关联数据
* @prram detail 事件关联的数据
*/
self.LoadCategory = async function (
event: MouseEvent,
data: EventData,
detail: {
rootElement: HtmlElement; //即是本组件
targetElement: HtmlElement; // 点击的元素,事件源
}
) {
// Active and inactive class
const active =
'text-primary-500 dark:text-primary-400 border-primary-500 dark:border-primary-400';
const inactive = 'border-transparent';
// Prevent default behavior ( href redirect )
event.preventDefault();
// Get category and store it
const category = data.category || null;
self.store.Set('category', category);
// Change item style
$Query('[category]').each((el) => {
el.removeClass(active).addClass(inactive);
// Current category
if ($Store(el.element as HTMLElement).Get('category') === category) {
el.removeClass(inactive).addClass(active);
}
});
// Load articles
const articles = await $Backend('/blog').Call('GetArticles', category, 1);
// Render articles
await self.render('articles', { articles });
};
示例 2:
ts
/**
* When the item is clicked, raise the item-click event and toggle the selected state
* @param event
* @param data
* @param detail
* @returns
*/
self.onItemClick = (
event: Event,
data: Record<string, any>,
detail: Record<string, any>
) => {
debugger;
event.stopPropagation();
const item = data.item || {};
const props = self.store.GetData(); //这里是取的`.backend.ts`中函数`BeforeRender`返回对象。
const mode = props.mode || 'single';
// Single mode select the item
if (mode == 'single') {
unselectAllItems();
selectItem(detail.targetElement);
self.root.dispatchEvent(new CustomEvent('item-click', { detail: item }));
return;
}
// Multiple mode item has been selected
if (item.selected) {
unselectItem(detail.targetElement);
self.root.dispatchEvent(
new CustomEvent('item-click', { detail: { ...item, selected: false } })
);
return;
}
// Multiple mode item unselected
selectItem(detail.targetElement);
self.root.dispatchEvent(
new CustomEvent('item-click', { detail: { ...item, select: true } })
);
return;
};
ts
/**
* 事件处理
* @param event 浏览器原始事件
* @param dataKeys 在组件中使用data:xxxx属性定义的属性,使用self.store.Get()/Set()可以访问或是设置组件的data数据。
* @param jsonKeys 在组件中使用json:xxxx属性定义的属性, 使用self.store.GetJSON()/SetJSON()可以访问或是设置组件的json数据。
* @param target 事件源,HTML元素,比如组件中的某一个按钮或是html元素
* @param root SUI组件所在的根元素,HTML元素
* @param handler 在`.backend.ts`中定义的事件响应处理函数
* @returns
*/
function __sui_event_handler(
event,
dataKeys,
jsonKeys,
target,
root,
handler: Function
) {
const data = {};
target = target || null;
if (target) {
dataKeys.forEach(function (key) {
const value = target.getAttribute('data:' + key);
data[key] = value;
});
jsonKeys.forEach(function (key) {
const value = target.getAttribute('json:' + key);
data[key] = null;
if (value && value != '') {
try {
data[key] = JSON.parse(value);
} catch (e) {
const message = e.message || e || 'An error occurred';
console.error(`[SUI] Event Handler Error: ${message} `, target);
}
}
});
}
// 用户定义的事件处理函数
handler &&
handler(event, data, {
rootElement: root,
targetElement: target
});
}
/**
* 事件初始化
* @param elm 页面的根元素
* @returns
*/
function __sui_event_init(elm: Element) {
/**
* 绑定事件
* @param eventElm 事件源
* @returns
*/
const bindEvent = (eventElm) => {
const cn = eventElm.getAttribute('s:event-cn') || '';
if (cn == '') {
console.error('[SUI] Component name is required for event binding', elm);
return;
}
// Data keys
// 收集用户定义的事件
const events: Record<string, string> = {};
//收集data:xxxx属性
//收集json:xxxx属性
const dataKeys: string[] = [];
const jsonKeys: string[] = [];
for (let i = 0; i < eventElm.attributes.length; i++) {
if (eventElm.attributes[i].name.startsWith('data:')) {
dataKeys.push(eventElm.attributes[i].name.replace('data:', ''));
}
if (eventElm.attributes[i].name.startsWith('json:')) {
jsonKeys.push(eventElm.attributes[i].name.replace('json:', ''));
}
if (eventElm.attributes[i].name.startsWith('s:on-')) {
const key = eventElm.attributes[i].name.replace('s:on-', '');
events[key] = eventElm.attributes[i].value;
}
}
// Bind the event
for (const name in events) {
const bind = events[name];
if (cn == '__page') {
const handler = window[bind];
const root = document.body;
const target = eventElm;
eventElm.addEventListener(name, (event) => {
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
});
continue;
}
//如果是子组件,需要找到它的父组件
const component = eventElm.closest(`[s\\:cn=${cn}]`);
if (typeof window[cn] !== 'function') {
console.error(`[SUI] Component ${cn} not found`, eventElm);
return;
}
// @ts-ignore
const comp = new window[cn](component);
const handler = comp[bind];
const root = comp.root;
const target = eventElm;
eventElm.addEventListener(name, (event) => {
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
});
}
};
//查询所有包含s:event属性的元素
const eventElms = elm.querySelectorAll('[s\\:event]');
//查询所有包含s:event-jit属性的元素
const jitEventElms = elm.querySelectorAll('[s\\:event-jit]');
eventElms.forEach((eventElm) => bindEvent(eventElm));
jitEventElms.forEach((eventElm) => bindEvent(eventElm));
}
浏览器渲染
在浏览器中,无刷新更新的方法。
通过调用 api,传入上下文数据,返回页面的 html 代码。
组件需要设置属性:s:render="name"
。
ts
/**
* SUI Render
* @param component
* @param name
*/
async function __sui_render(
component: Component | string,
name: string,
data: Record<string, any>,
option?: RenderOption
): Promise<string> {
const comp = // 获取组件,可以传入组件名称
// sui_component
(typeof component === 'object' ? component : $$(component)) as Component;
if (comp == null) {
console.error(`[SUI] Component not found: ${component}`);
return Promise.reject('Component not found');
}
const elms = comp.root.querySelectorAll(`[s\\:render=${name}]`);
if (!elms.length) {
console.error(`[SUI] No element found with s:render=${name}`);
return Promise.reject('No element found');
}
// Set default options
option = option || {};
option.replace = option.replace === undefined ? true : option.replace;
option.showLoader =
option.showLoader === undefined ? false : option.showLoader;
option.withPageData =
option.withPageData === undefined ? false : option.withPageData;
// Prepare loader
let loader = `<span class="sui-render-loading">Loading...</span>`;
if (option.showLoader && option.replace) {
if (typeof option.showLoader === 'string') {
loader = option.showLoader;
} else if (option.showLoader instanceof HTMLElement) {
loader = option.showLoader.outerHTML;
}
elms.forEach((elm) => (elm.innerHTML = loader));
}
// Prepare data
let _data = comp.store.GetData() || {};
if (option.withPageData) {
// @ts-ignore
_data = { ..._data, ...__sui_data };
}
// get s:route attribute
const elm = comp.root.closest('[s\\:route]');
const routeAttr = elm ? elm.getAttribute('s:route') : false;
const root = document.body.getAttribute('s:public') || '';
const route = routeAttr ? `${root}${routeAttr}` : window.location.pathname;
option.component = (routeAttr && comp.root.getAttribute('s:cn')) || '';
const url = `/api/__yao/sui/v1/render${route}`;
const payload = { name, data: _data, option };
// merge the user data
if (data) {
for (const key in data) {
payload.data[key] = data[key];
}
}
const headers = {
'Content-Type': 'application/json',
Cookie: document.cookie
};
// Native post request to the server
try {
const body = JSON.stringify(payload);
const response = await fetch(url, { method: 'POST', headers, body: body });
const text = await response.text();
if (!option.replace) {
return Promise.resolve(text);
}
// Set the response text to the elements
elms.forEach((elm) => {
elm.innerHTML = text;
try {
// 重新初始化新生成的组件的事件
__sui_event_init(elm);
} catch (e) {
const message = e.message || 'Failed to init events';
Promise.reject(message);
}
});
return Promise.resolve(text);
} catch (e) {
//Set the error message
elms.forEach((elm) => {
elm.innerHTML = `<span class="sui-render-error">Failed to render</span>`;
console.error('Failed to render', e);
});
return Promise.reject('Failed to render');
}
}
class __Render {
comp = null;
option = null;
constructor(comp, option) {
this.comp = comp;
this.option = option;
}
async Exec(name, data): Promise<string> {
// @ts-ignore
return __sui_render(this.comp, name, data, this.option);
}
}
使用示例:
用法选择不同的分类,会重新加载文章列表。此时界面不需要更新。
ts
/**
* When category is changed, load articles
* @param event
* @param data
*/
self.LoadCategory = async function (event: MouseEvent, data: EventData) {
// Active and inactive class
const active =
'text-primary-500 dark:text-primary-400 border-primary-500 dark:border-primary-400';
const inactive = 'border-transparent';
// Prevent default behavior ( href redirect )
event.preventDefault();
// Get category and store it
const category = data.category || null;
self.store.Set('category', category);
// Change item style
$Query('[category]').each((el) => {
el.removeClass(active).addClass(inactive);
// Current category
if ($Store(el.element as HTMLElement).Get('category') === category) {
el.removeClass(inactive).addClass(active);
}
});
// Load articles
const articles = await $Backend('/blog').Call('GetArticles', category, 1);
// Render articles
await self.render('articles', { articles });
};