').append($content).find(options.selector);
if(options.replace) $target.replaceWith($content);
else $target.empty().append($content);
$target.data('zin-target-load', Date.now()).zuiInit();
},
error: (xhr, type, error) =>
{
loadError = error;
if(options.error) options.error(error, options);
if(DEBUG) console.error('[ZIN] ', 'Fetch data failed from ' + url, {xhr, type, error});
zui.Messager.show('ZIN: Fetch data failed from ' + url);
},
complete: () =>
{
toggleLoading(target, false);
if(options.complete) options.complete(remoteData, loadError, options);
}
};
return $.ajax(ajaxOptions);
}
function postAndLoadPage(url, data, selector, options)
{
if(typeof selector === 'object')
{
options = selector;
selector = null;
}
options = $.extend({url: url, selector: selector, method: 'POST', data: data, contentType: false}, options);
if(options.dataMap)
{
options.data = zui.createFormData(zui.mapFormData(options.dataMap), options.data);
delete options.dataMap;
}
if(!options.data || !(options.data instanceof FormData || Object.keys(options.data).length)) return;
if(options.app) openPage(url, options.app, options);
else loadPage(options);
}
function loadCurrentPage(options)
{
if(options instanceof Event || options instanceof HTMLElement)
{
options = {};
if(DEBUG) console.warn('[APP] ', 'loadCurrentPage() should not be called with an event or element.');
}
if(typeof options === 'string') options = {selector: options};
return loadPage($.extend({cache: false}, options));
}
function reloadPage()
{
loadPage({cache: false, url: currentAppUrl, selector: 'body>*,title>*,#configJS'});
}
/**
* Load form with zin request.
*
* @param {{target: string, url?: string, partial?: boolean, keep?: boolean|string, items?: string, updateActionUrl?: string}} options Load form options.
*/
function loadForm(options)
{
options = $.extend({target: 'form', updateActionUrl: true}, options);
const $target = $(options.target);
if(!$target.length) return console.warn('[APP] ', `cannot find target with selector "${options.target}"`);
let $form = $target.closest('form');
if(!$form.length) $form = $target.find('form');
if(!$form.length) return console.warn('[APP] ', `cannot find form from target "${options.target}"`);
if(options.partial === undefined) options.partial = !!$target.closest('.modal').length;
const id = $form.attr('id');
if(!id) return console.warn('[APP] ', `form from "${options.target}" has no id`);
const formData = new FormData($form[0]);
const data = {};
formData.forEach((value, key) =>
{
if(key.includes('[')) key = key.substring(0, key.indexOf('['));
data[key] = value;
});
if(DEBUG) showLog('Load form', id, {options, data});
const $oldItems = $form.children('.form-group[data-name]');
let items = options.items || [];
if(typeof items === 'string') items = items.split(',');
const loadingTarget = items ? $oldItems.filter((_, element) => items.includes($(element).attr('data-name'))) : `#${id}`;
let url = options.url || $form.attr('data-load-url');
url = url ? zui.formatString(url, data).replace(/\{\w+\}/g, '') : currentAppUrl;
loadPage(
{
url: url,
selector: `#${id}`,
partial: options.partial,
loadingTarget: loadingTarget,
loadingClass: 'pointer-events-none',
cache: false,
success: () =>
{
let updateActionUrl = options.updateActionUrl;
if(updateActionUrl === true) updateActionUrl = url;
if(updateActionUrl) $form.attr('action', updateActionUrl);
},
onRender: (info) =>
{
if(info.name !== id) return;
if(options.items)
{
const $data = $(info.data).children('.form-group[data-name]');
items.forEach(name =>
{
if('.[#'.includes(name[0]))
{
const $items = $data.filter(name);
const $oldMatchItems = $oldItems.filter(name);
$oldMatchItems.last().after($items);
$oldMatchItems.remove();
$items.zuiInit();
}
else
{
const $items = $data.filter(`[data-name="${name}"]`);
const $oldMatchItems = $oldItems.filter(`[data-name="${name}"]`);
if($oldMatchItems.length)
{
try {$oldMatchItems.replaceWith($items);} catch (_) {}
}
else
{
$oldItems.last().after($items);
}
$items.zuiInit();
}
});
if(options.updateOrders)
{
const formGrid = $form.zui('formGrid');
if(formGrid)
{
const orders = [];
$data.each((_, element) => orders.push($(element).attr('data-name')));
formGrid.updateOrders(orders, $form.data('fullModeOrders'));
}
}
}
else
{
$form.html(info.data);
const formGrid = $form.zui('formGrid');
if(formGrid) formGrid.toggleMode(formGrid.mode, true);
}
let keep = options.keep;
if(keep === true) keep = Object.keys(data);
else if(typeof keep === 'string') keep = keep.split(',');
if(Array.isArray(keep))
{
keep.forEach((name) =>
{
const $item = $form.find(`[name="${name}"]`);
if(!$item.length) return;
const value = data[name];
if($item.is('.pick-value'))
{
const pick = $item.closest('.pick').zui();
if(pick) pick.$.setValue(value, true);
}
else if($item.is('select'))
{
$item.find(`option[value="${value}"]`).prop('selected', true);
}
else if($item.is(':checkbox'))
{
$item.prop('checked', value);
}
else
{
$item.val(value);
}
});
}
return true;
}
})
}
function applyFormData(data, formSelector)
{
let formHelper;
if(window.getPageFormHelper) formHelper = window.getPageFormHelper(formSelector, data);
if(!formHelper)
{
const $form = $(formSelector || '#mainContainer form').filter(function() {return $(this).closest('#formSettingBtn').length === 0;});
formHelper = zui.zentaoFormHelper($form);
}
formHelper.setFormData(data);
}
function openPage(url, appCode, options)
{
if(DEBUG) showLog('Open page', url, {appCode, options});
if(!window.config.zin)
{
location.href = $.createLink('index', 'app', 'url=' + btoa(url));
return;
}
if(getUrlID(url) === 'index-index') return top.location.href = url;
return $.apps.openApp(url, $.extend({code: appCode, forceReload: true}, options));
}
function autoLoad(id)
{
const parseID = (x) =>
{
const parts = x.split(':');
return {type: parts[0], ids: parts[1] ? parts[1].split(',') : []};
};
id = parseID(id);
const idSet = new Set(id.ids);
const type = id.type;
$('[data-auto-load]').each(function()
{
const $this = $(this);
const targetID = parseID($this.attr('data-auto-load'));
if(type !== targetID.type || (targetID.ids.length && !targetID.ids.some(x => idSet.has(x)))) return;
const modal = zui.Modal.query($this);
if(modal) modal.render({loadingClass: ''});
else loadCurrentPage();
});
}
/**
* Search history and go back to specified path.
*
* @param {string} target Back target, can be app name or module-method path.
* @param {string} url Fallback url.
* @returns {void}
*/
function goBack(target, url)
{
if(!target || target === 'APP' || target === true) target = currentCode;
else if(target === 'GLOBAL') target = '';
$.apps.goBack(target, url, historyState);
}
/**
* Open url in app.
* @param {string} url
* @param {Object} options
* @param {string} options.url
* @param {string} options.app
* @param {string} options.load
* @param {string} options.back
* @param {Event} event
*/
function openUrl(url, options, event)
{
if(typeof url === 'object')
{
options = url;
url = options.url;
}
else
{
options = options || {};
options.url = url;
}
if(typeof url === 'string' && url.includes('#'))
{
const hash = url.split('#')[1];
if(hash === 'open-modal' || hash.startsWith('open-modal?'))
{
const openModalParams = hash.includes('?') ? hash.split('?')[1] : '';
const openModalOptions = $.extend({}, {url: url, type: 'ajax'}, options);
if(openModalParams)
{
const searchParams = new URLSearchParams(openModalParams);
for(const key of searchParams.keys()) openModalOptions[key] = searchParams.getAll(key).join(',');
}
zui.Modal.open(openModalOptions);
return;
}
}
if(DEBUG) showLog('Open url', url, options);
if(options.app === 'current') options.app = currentCode;
if(options.confirm)
{
return zui.Modal.confirm(options.confirm).then(confirmed =>
{
if(confirmed) openUrl(url, $.extend({}, options, {confirm: false}), event);
else if(options.canceled) openUrl(options.canceled, $.extend({}, options, {confirm: false}), event);
});
}
const load = options.load;
if(typeof load === 'string' || load)
{
if(!options.target) options.target = options.loadId;
if(url) options.url = url;
if(load)
{
if(load === 'post') return postAndLoadPage(options.url, options.data, options);
if(load === 'table')
{
if(!options.target && event) options.target = $(event.target).closest('.dtable').attr('id');
return loadTable(options.url, options.target, options);
}
if(load === 'modal')
{
if(!options.target && event) options.target = $(event.target).closest('.modal').attr('id');
return loadModal(options);
}
if(load === 'target') return loadTarget(options);
if(load !== 'APP' && typeof load === 'string') options.selector = load;
delete options.load;
}
return loadPage(options);
}
if(typeof options.back === 'string') return goBack(options.back, url);
return openPage(url, options.app);
}
/**
* Parse wg selector.
* @param {string} selector
* @return {object|null}
*/
function parseSelector(selector)
{
selector = selector.trim();
let len = selector.length;
if(len < 1) return null;
const result = {class: [], id: '', tag: '', inner: false, name: '', first: false, selector: selector};
if(selector.includes('/'))
{
const parts = selector.split('/', 2);
result.name = parts[0];
selector = parts[1];
len = selector.length;
}
selector = selector.replace('> *', '>*');
if(selector.endsWith('>*'))
{
result.inner = true;
selector = selector.substring(0, selector.length - 2);
len = selector.length;
}
let type = 'tag';
let current = '';
let updateResult = function(result, current, type)
{
if(!current.length) return;
if(type === 'class')
{
result[type].push(current);
}
else if(type === 'option')
{
current.split('&').forEach(function(option)
{
const parts = option.split('=');
result[parts[0]] = parts[1] || true;
});
}
else
{
result[type] = current;
}
};
for(let i = 0; i < len; i++)
{
let c = selector[i];
let t = '';
if(c === '#' && type !== 'option')
{
t = 'id';
}
else if(c === '.' && type !== 'option')
{
t = 'class';
}
else if(c === '(' && type !== 'option' && selector.endsWith(')'))
{
let command = selector.substring(i + 1, selector.length - 1);
if(!command) command = current;
result.command = command;
break;
}
else if(c === ':')
{
t = 'option';
}
if(!t)
{
current += c;
}
else
{
updateResult(result, current, type);
current = '';
type = t;
}
}
updateResult(result, current, type);
if(!result.name.length)
{
if(result.id.length) result.name = result.id;
else if(result.tag) result.name = result.tag;
else result.name = selector;
}
result.select = [result.tag, result.id.length ? '#' + result.id : '', result.class.length ? '.' + result.class.join('.') : ''].join('');
return result;
}
/**
* Ajax send score to server.
* @param {string} method
*/
function ajaxSendScore(method)
{
$.get($.createLink('score', 'ajax', 'method=' + method));
}
/**
* Change current app language.
* @param {string} lang
*/
function changeAppLang(lang)
{
if($('html').attr('lang') === lang) return;
zui.i18n.setCode(lang);
reloadPage();
$('html').attr('lang', lang);
}
/**
* Change current app theme.
* @param {string} lang
*/
function changeAppTheme(theme)
{
const classList = $('html').attr('class').split(' ').filter(x => x.length && !x.startsWith('theme-'));
classList.push('theme-' + theme);
$('html').attr('class', classList.join(' '));
const $theme = $('#zuiTheme');
const oldPath = $theme.attr('href').split('/');
oldPath.pop();
oldPath.push(theme + '.css');
$theme.attr('href', oldPath.join('/'));
const userMenu = $('#userMenu-toggle').zui();
if(!userMenu) return;
const themeItem = userMenu.options.menu.items.find(x => x.key === 'theme');
themeItem.items.forEach(item => {item.active = item['data-value'] === theme;});
userMenu.render({menu: userMenu.options.menu});
}
/**
* Select UI language.
* @param {string} lang
*/
function selectLang(lang)
{
$.cookie.set('lang', lang, {expires: config.cookieLife, path: config.webRoot});
ajaxSendScore('selectLang');
$.apps.changeAppsLang(lang);
}
/**
* Select UI theme.
* @param {string} lang
*/
function selectTheme(theme)
{
$.cookie.set('theme', theme, {expires: config.cookieLife, path: config.webRoot});
$.ajaxSendScore('selectTheme');
$.apps.changeAppsTheme(theme);
}
/**
* Select vision.
* @param {string} vision
*/
function selectVision(vision)
{
$.get($.createLink('my', 'ajaxSwitchVision', 'vision=' + vision), function(result)
{
const response = JSON.parse(result);
if(response.result == 'fail')
{
return zui.Messager.error(response.message);
}
if(response.result == 'success' && response.load)
{
window.top.location.href = response.load;
}
});
}
/**
* 获取所有界面列表。
* @param {string} vision
* @returns {Array<{id: string, label: string, active: boolean}>
*/
function getVisions(vision)
{
return window.userVisions.map(key => ({id: key, label: window.config.visions[key], active: key === window.config.vision}));
}
function fetchMessage(force, fetchUrl)
{
let $this = $('#messageBar');
let $dropmenu = $("#dropdownMessageMenu");
if(typeof(force) === 'undefined' || typeof(force) === 'object') force = false;
if(typeof(fetchUrl) === 'undefined') fetchUrl = $this.attr('data-fetcher');
setTimeout(function()
{
let dropdown = $this.zui('Dropdown');
let isOpen = true;
if(dropdown) isOpen = dropdown.shown;
if(!isOpen && !force) return;
$dropmenu.load(fetchUrl);
}, 100);
}
function waitDom(selector, func, times, interval)
{
var _times = times || 100; //100次
var _interval = interval || 50; //50毫秒每次
var _self = $(selector);
var _iIntervalID;
if(_self.length)
{
func && func.call(_self);
return;
}
_iIntervalID = setInterval(function()
{
if(!_times) clearInterval(_iIntervalID);
_times <= 0 || _times--;
_self = $(selector);
if(_self.length)
{
func && func.call(_self);
clearInterval(_iIntervalID);
}
}, _interval);
}
function setImageSize(image, maxWidth, maxHeight)
{
var $image = $(image);
if($image.parent().is('a')) return;
/* If not set maxWidth, set it auto. */
if(!maxWidth)
{
bodyWidth = $('body').width();
maxWidth = bodyWidth - 470; // The side bar's width is 336, and add some margins.
}
if(!maxHeight) maxHeight = top.window.innerHeight;
setTimeout(function()
{
let maxHeightStyle = $image.height() > 0 ? 'max-height:' + maxHeight + 'px' : '';
if(!document.getElementsByClassName('xxc-embed').length && $image.width() > 0 && $image.width() > maxWidth) $image.attr('width', maxWidth);
$image.wrap('
');
if($image.height() > 0 && $image.height() > maxHeight) $image.closest('a').append("
" + window.config.expand + " ");
}, 50);
}
function showMoreImage(obj)
{
$(obj).parents('a').css('max-height', 'none');
$(obj).remove();
}
function handleGlobalClick(e)
{
if(e.defaultPrevented) return;
if(isInAppTab) window.parent.$('body').trigger('click');
const $target = $(e.target);
if($target.closest('.not-open-url').length) return;
const $link = $target.closest('a,.open-url');
if(!$link.length || $link.hasClass('ajax-submit') || $link.attr('download') || $link.attr('data-on') || $link.attr('zui-on') || $link.attr('zui-toggle') || $link.attr('zui-command') || $link.hasClass('show-in-app') || $link.hasClass('not-open-url') || ($link.attr('target') || '')[0] === '_') return;
const href = $link.attr('href');
if($link.hasClass('disabled') || $link.prop('disabled'))
{
e.preventDefault();
return;
}
if(href && $link.is('a'))
{
if(href.startsWith('javascript:')) return;
if(/^https?:/.test(href) && !$link.data('app'))
{
e.preventDefault();
window.open(href, '_blank', 'noopener,noreferrer');
return;
}
}
const options = $link.dataset();
if(options.toggle && options.toggle !== 'tooltip' && !$link.hasClass('open-url')) return;
const url = options.url || href;
const $modal = $link.closest('.modal');
if(options.loadId)
{
options.target = options.loadId;
delete options.loadId;
}
if(url)
{
if(/^(https?|javascript):/.test(url) && !options.app) return;
if(url[0] === '#')
{
if(/firefox/i.test(navigator.userAgent)) e.preventDefault();
return;
}
}
if(!url && $link.is('a') && !options.back && !options.load) return;
if($modal.length)
{
if(!options.load && !url) return;
if(options.load === 'modal' && !options.target) options.target = $modal.attr('id');
if(options.load === 'table')
{
options.partial = true;
if(!options.url) options.url = $modal.data('zui.Modal').options.url;
}
if(options.closeModal) zui.Modal.hide(typeof options.closeModal === 'string' ? options.closeModal : $modal);
let app = options.app;
if(!options.app) options.app = $.apps.getAppCode(url, currentCode);
if(app === currentCode && options.load !== 'modal' && options.load !== 'table')
{
options.load === 'modal';
zui.Modal.query($modal).hide();
}
}
else
{
if(options.load === 'modal' && !options.target) delete options.load;
}
openUrl(url, options, e);
e.preventDefault();
}
function createSelector(name)
{
return name.replace(/([[\]])/g, '\\$1');
};
function showValidateMessage(message)
{
let $firstControl = null;
Object.entries(message).forEach(([name, msg]) => {
if (Array.isArray(msg)) {
msg = msg.join('');
}
const nameSelector = createSelector(name);
const $element = $('.form');
let $control = $element.find(`#${nameSelector}`);
if(!$control.length) $control = $element.find(`[name="${nameSelector}"]`);
if(!$control.length && !name.includes('[')) $control = $element.find(`[name="${nameSelector}[]"]`);
if($control.hasClass('pick-value')) $control = $control.closest('.pick');
$control.addClass('has-error');
const $group = $control.closest('.form-group,.form-batch-control');
if(!$group.length) return zui.Messager.show({content: msg, type: 'danger', className: 'bg-danger text-canvas gap-2 messager-success'});
if($group.length)
{
let $tip = $group.find(`#${nameSelector}Tip`);
if(!$tip.length)
{
$tip = $(`
`).appendTo($group);
}
$tip.empty().text(msg);
}
if(!$firstControl) $firstControl = $control;
});
if($firstControl) $firstControl[0]?.focus();
}
function enterWorkspace(code, url)
{
workspaceType = code;
$.cookie.set('workspace', code || currentCode, {expires: config.cookieLife, path: config.webRoot});
if(url) openUrl(url);
else setTimeout(reloadPage, 500);
}
function exitWorkspace()
{
workspaceType = '';
$.cookie.remove('workspace', {path: config.webRoot});
setTimeout(reloadPage, 500);
}
function updateWorkspaceUI(info)
{
$('body').toggleClass('in-workspace', !!info);
if(info)
{
const $dropmenu = $('#dropmenu').first();
info.name = $dropmenu.attr('data-text');
info.dropmenu = $dropmenu.attr('zui-create-dropmenu');
info.icon = $('#heading>.toolbar>.btn>.icon').attr('class').replace('icon icon-', '');
info.menuGroup = $('#navbar>.nav').attr('data-navbar-group');
}
workspaceType = info ? info.type : null;
$.apps.updateSpaceMenu && $.apps.updateSpaceMenu(info);
}
$.extend(window, {registerRender: registerRender, fetchContent: fetchContent, loadTable: loadTable, loadPage: loadPage, postAndLoadPage: postAndLoadPage, loadCurrentPage: loadCurrentPage, parseSelector: parseSelector, toggleLoading: toggleLoading, openUrl: openUrl, openPage: openPage, goBack: goBack, registerTimer: registerTimer, loadModal: loadModal, loadTarget: loadTarget, loadComponent: loadComponent, loadPartial: loadPartial, reloadPage: reloadPage, selectLang: selectLang, selectTheme: selectTheme, selectVision: selectVision, changeAppLang, changeAppTheme: changeAppTheme, waitDom: waitDom, fetchMessage: fetchMessage, setImageSize: setImageSize, showMoreImage: showMoreImage, autoLoad: autoLoad, loadForm: loadForm, showValidateMessage: showValidateMessage, getPageInfo: getPageInfo, getPerfData: getPerfData, applyFormData: applyFormData, zinCallbacks: zinCallbacks, registerZinCallback: registerZinCallback, getVisions: getVisions, enterWorkspace: enterWorkspace, exitWorkspace: exitWorkspace, updateWorkspaceUI: updateWorkspaceUI});
$.extend($.apps, {openUrl: openUrl, getAppUrl: () => currentAppUrl});
$.extend($, {ajaxSendScore: ajaxSendScore, selectLang: selectLang});
/* Transfer click event to parent */
$(document).on('click', handleGlobalClick).on('locate.zt', (_e, data) =>
{
if(!data) return;
if(data === true) return loadCurrentPage();
if(data === 'reload') return reloadPage();
if(typeof data === 'string')
{
if(data === 'table') return loadTable();
if(data === 'modal') return loadModal();
if(data === 'login')
{
window.top.location.href = $.createLink('user', 'login', 'referer=' + btoa(currentAppUrl));
return;
}
data = {url: data};
}
if(data.autoLoad) autoLoad(data.autoLoad);
if(data.confirm)
{
return zui.Modal.confirm(data.confirm).then(confirmed =>
{
if(confirmed) $(document).trigger('locate.zt', data.confirmed);
else $(document).trigger('locate.zt', data.cancelled || data.canceled);
});
}
if(data.alert)
{
return zui.Modal.alert(data.alert).then(function()
{
if(data.modal) loadModal(data.modal);
if(data.locate) openUrl(data.locate);
});
}
if(data.modal) return loadModal(data.modal);
if(data.load) return openUrl(data);
if(data.app) return openPage(data.url + (data.selector ? (' ' + data.selector) : ''), data.app);
loadPage(data);
});
if(!isInAppTab && !isIndexPage)
{
const initialState = {url: currentAppUrl, title: document.title};
window.history.pushState(initialState, initialState.title, initialState.url);
$(window).on('popstate', function(event)
{
const state = event.state;
if(DEBUG) showLog(state ? state.code : null, 'Popstate', state ? state.url : null, state);
if(state && state.url) openPage(state.url);
});
}
$(() =>
{
if($.apps.theme) changeAppTheme($.apps.theme);
if(isIndexPage) return;
initZinbar();
if(window.defaultAppUrl) loadPage(window.defaultAppUrl);
if(isInAppTab)
{
const frameElement = window.frameElement;
if(frameElement && parent.window.$) parent.window.$(frameElement).trigger('ready.app');
}
if(DEBUG)
{
if(!isInAppTab && !zui.store.get('Zinbar:hidden') && zui.dom.isVisible($('#navbar'))) loadCurrentPage();
if(zinbar)
{
$(document).on('inited.zt', (e) =>
{
if(e.target.parentElement.closest('[z-use]')) return;
const now = performance.now();
if(zinbar.waitZUI && (now - zinbar.waitZUI.time) <= 100)
{
zinbar.waitZUI.time = now;
updatePerfInfo(zinbar.waitZUI, 'renderEnd');
}
else
{
zinbar.waitZUI = null;
}
});
}
}
});
}());