blob: b26913ff52458e673b5b9ddff44f21575287f6df [file] [log] [blame]
/*
* Copyright (c) 2010-2019 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
import {App, Device, ObjectFactory, objects, strings, ValueField, widgets} from './index';
import $ from 'jquery';
let $activeElements = null;
let objectFactories = {};
/**
* Returns the first of the given arguments that is not null or undefined. If no such element
* is present, the last argument is returned. If no arguments are given, undefined is returned.
*/
export function nvl() {
var result;
for (var i = 0; i < arguments.length; i++) {
result = arguments[i];
if (result !== undefined && result !== null) {
break;
}
}
return result;
}
/**
* Use this method in your functions to assert that a mandatory parameter is passed
* to the function. Throws an error when value is not set.
*
* @param {string} parameterName
* @param {object} [value]
* @param {function} [type] if this optional parameter is set, the given value must be of this type (instanceof check)
* @return {object} the value
*/
export function assertParameter(parameterName, value, type) {
if (objects.isNullOrUndefined(value)) {
throw new Error('Missing required parameter \'' + parameterName + '\'');
}
if (type && !(value instanceof type)) {
throw new Error('Parameter \'' + parameterName + '\' has wrong type');
}
return value;
}
/**
* Use this method to assert that a mandatory property is set. Throws an error when value is not set.
*
* @param type (optional) if this parameter is set, the value must be of this type (instanceof check)
* @return the value (for direct assignment)
*/
export function assertProperty(object, propertyName, type) {
var value = object[propertyName];
if (objects.isNullOrUndefined(value)) {
throw new Error('Missing required property \'' + propertyName + '\'');
}
if (type && !(value instanceof type)) {
throw new Error('Property \'' + propertyName + '\' has wrong type');
}
return value;
}
/**
* Checks if one of the arguments from 1-n is equal to the first argument.
* @param value
* @param arguments to check against the value, may be an array or a variable argument list.
*/
export function isOneOf(value) {
if (arguments.length < 2) {
return false;
}
var argsToCheck;
if (arguments.length === 2 && Array.isArray(arguments[1])) {
argsToCheck = arguments[1];
} else {
argsToCheck = Array.prototype.slice.call(arguments, 1);
}
return argsToCheck.indexOf(value) !== -1;
}
/**
* Creates a new object instance.<p> Delegates the create call to scout.ObjectFactory#create.
* @returns {object}
*/
export function create(objectType, model, options) {
return ObjectFactory.get().create(objectType, model, options);
}
/**
* Prepares the DOM for scout in the given document. This should be called once while initializing scout.
* If the target document is not specified, the global "document" variable is used instead.
*
* This is used by apps (App, LoginApp, LogoutApp)
*
* Currently it does the following:
* - Remove the <noscript> tag (obviously there is no need for it).
* - Remove <scout-text> tags (they must have been processed before, see texts.readFromDOM())
* - Remove <scout-version> tag (it must have been processed before, see scout.App._initVersion())
* - Add a device / browser class to the body tag to allow for device specific CSS rules.
* - If the browser is Google Chrome, add a special meta header to prevent automatic translation.
*/
export function prepareDOM(targetDocument) {
targetDocument = targetDocument || document;
// Cleanup DOM
$('noscript', targetDocument).remove();
$('scout-text', targetDocument).remove();
$('scout-version', targetDocument).remove();
$('body', targetDocument).addDeviceClass();
// Prevent "Do you want to translate this page?" in Google Chrome
if (Device.get().browser === Device.Browser.CHROME) {
var metaNoTranslate = '<meta name="google" content="notranslate" />';
var $title = $('head > title', targetDocument);
if ($title.length === 0) {
// Add to end of head
$('head', targetDocument).append(metaNoTranslate);
} else {
$title.after(metaNoTranslate);
}
}
}
/**
* Installs a global 'mousedown' interceptor to invoke 'aboutToBlurByMouseDown' on value field before anything else gets executed.
*/
export function installGlobalMouseDownInterceptor(myDocument) {
myDocument.addEventListener('mousedown', function(event) {
ValueField.invokeValueFieldAboutToBlurByMouseDown(event.target || event.srcElement);
}, true); // true=the event handler is executed in the capturing phase
}
/**
* Because Firefox does not set the active state of a DOM element when the mousedown event
* for that element is prevented, we set an 'active' CSS class instead. This means in the
* CSS we must deal with :active and with .active, where we need same behavior for the
* active state across all browsers.
* <p>
* Typically you'd write something like this in your CSS:
* button:active, button.active { ... }
*/
export function installSyntheticActiveStateHandler(myDocument) {
if (Device.get().requiresSyntheticActiveState()) {
$activeElements = [];
$(myDocument)
.on('mousedown', function(event) {
var $element = $(event.target);
while ($element.length) {
$activeElements.push($element.addClass('active'));
$element = $element.parent();
}
})
.on('mouseup', function() {
$activeElements.forEach(function($element) {
$element.removeClass('active');
});
$activeElements = [];
});
}
}
/**
* Resolves the widget using the given widget id or HTML element.
* <p>
* If the argument is a string or a number, it will search the widget hierarchy for the given id using Widget#widget(id).
* If the argument is a HTML or jQuery element, it will use widgets.get() to get the widget which belongs to the given element.
*
* @param widgetIdOrElement
* a widget ID or a HTML or jQuery element
* @param [partId]
* partId of the session the widget belongs to (optional, only relevant if the
* argument is a widget ID). If omitted, the first session is used.
* @returns
* the widget for the given element or id
*/
export function widget(widgetIdOrElement, partId) {
if (objects.isNullOrUndefined(widgetIdOrElement)) {
return null;
}
var $elem = widgetIdOrElement;
if (typeof widgetIdOrElement === 'string' || typeof widgetIdOrElement === 'number') {
// Find widget for ID
var session = scout.getSession(partId);
if (session) {
widgetIdOrElement = strings.asString(widgetIdOrElement);
return session.root.widget(widgetIdOrElement);
}
}
return widgets.get($elem);
}
/**
* Helper function to get the model adapter for a given adapterId. If there is more than one
* session, e.g. in case of portlets, the second argument specifies the partId of the session
* to be queried. If not specified explicitly, the first session is used. If the session or
* the adapter could not be found, null is returned.
*/
export function adapter(adapterId, partId) {
if (objects.isNullOrUndefined(adapterId)) {
return null;
}
var session = scout.getSession(partId);
if (session && session.modelAdapterRegistry) {
return session.modelAdapterRegistry[adapterId];
}
return null;
}
export function getSession(partId) {
let sessions = App.get().sessions;
if (!sessions) {
return null;
}
if (objects.isNullOrUndefined(partId)) {
return sessions[0];
}
for (var i = 0; i < sessions.length; i++) {
var session = sessions[i];
// noinspection EqualityComparisonWithCoercionJS
// eslint-disable-next-line eqeqeq
if (session.partId == partId) { // <-- compare with '==' is intentional! (NOSONAR)
return session;
}
}
return null;
}
/**
* This method exports the adapter with the given ID as JSON, it returns an plain object containing the
* configuration of the adapter. You can transform that object into JSON by calling <code>JSON.stringify</code>.
* This method can only be called through the browser JavaScript console.
* Here's an example of how to call the method:
*
* JSON.stringify(scout.exportAdapter(4))
*
* @param adapterId
*/
export function exportAdapter(adapterId, partId) {
var session = scout.getSession(partId);
if (session && session.modelAdapterRegistry) {
var adapter = session.getModelAdapter(adapterId);
if (!adapter) {
return null;
}
var adapterData = cloneAdapterData(adapterId);
resolveAdapterReferences(adapter, adapterData);
adapterData.type = 'model'; // property 'type' is required for models.js
return adapterData;
}
// ----- Helper functions -----
function cloneAdapterData(adapterId) {
var adapterData = session.getAdapterData(adapterId);
adapterData = $.extend(true, {}, adapterData);
return adapterData;
}
function resolveAdapterReferences(adapter, adapterData) {
var tmpAdapter, tmpAdapterData;
adapter.widget._widgetProperties.forEach(function(WidgetPropertyName) {
var WidgetPropertyValue = adapterData[WidgetPropertyName];
if (!WidgetPropertyValue) {
return; // nothing to do when property is null
}
if (Array.isArray(WidgetPropertyValue)) {
// value is an array of adapter IDs
var adapterDataArray = [];
WidgetPropertyValue.forEach(function(adapterId) {
tmpAdapter = session.getModelAdapter(adapterId);
tmpAdapterData = cloneAdapterData(adapterId);
resolveAdapterReferences(tmpAdapter, tmpAdapterData);
adapterDataArray.push(tmpAdapterData);
});
adapterData[WidgetPropertyName] = adapterDataArray;
} else {
// value is an adapter ID
tmpAdapter = session.getModelAdapter(WidgetPropertyValue);
tmpAdapterData = cloneAdapterData(WidgetPropertyValue);
resolveAdapterReferences(tmpAdapter, tmpAdapterData);
adapterData[WidgetPropertyName] = tmpAdapterData;
}
});
adapterData = adapter.exportAdapterData(adapterData);
}
}
/**
* Reloads the entire browser window.
*
* Options:
* [schedule]
* If true, the page reload is not executed in the current thread but scheduled using setTimeout().
* This is useful if the caller wants to execute some other code before the reload. The default is false.
* [clearBody]
* If true, the body is cleared first before the reload is performed. This is useful to prevent
* showing "old" content in the browser until the new content arrives. The default is true.
* [redirectUrl]
* The new URL to load. If not specified, the current location is used (window.location).
*/
export function reloadPage(options) {
options = options || {};
if (options.schedule) {
setTimeout(reloadPageImpl);
} else {
reloadPageImpl();
}
// ----- Helper functions -----
function reloadPageImpl() {
// Hide everything (on entire page, not only $entryPoint)
if (nvl(options.clearBody, true)) {
$('body').html('');
}
// Reload window (using setTimeout, to overcome drawing issues in IE)
setTimeout(function() {
if (options.redirectUrl) {
window.location.href = options.redirectUrl;
} else {
window.location.reload();
}
});
}
}
export function addObjectFactories(factories) {
objectFactories = $.extend(objectFactories, factories);
}
export function cloneShallow(template, properties, createUniqueId) {
assertParameter('template', template);
var clone = Object.create(Object.getPrototypeOf(template));
Object.getOwnPropertyNames(template)
.forEach(key => {
clone[key] = template[key];
});
if (properties) {
for (let key in properties) {
clone[key] = properties[key];
}
}
if (nvl(createUniqueId, true)) {
clone.id = ObjectFactory.get().createUniqueId();
}
if (clone.cloneOf === undefined) {
clone.cloneOf = template;
}
return clone;
}
export default {
nvl,
assertParameter,
assertProperty,
isOneOf,
create,
prepareDOM,
installGlobalMouseDownInterceptor,
installSyntheticActiveStateHandler,
widget,
adapter,
getSession,
exportAdapter,
reloadPage,
addObjectFactories,
objectFactories,
cloneShallow
};