jsf.js_next_gen
Version:
A next generation typescript reimplementation of jsf.js
468 lines • 22.7 kB
JavaScript
;
/*! Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResponseProcessor = void 0;
const mona_dish_1 = require("mona-dish");
const AjaxImpl_1 = require("../AjaxImpl");
const Assertions_1 = require("../util/Assertions");
const ErrorData_1 = require("./ErrorData");
const ImplTypes_1 = require("../core/ImplTypes");
const EventData_1 = require("./EventData");
const Const_1 = require("../core/Const");
const ExtDomQuery_1 = require("../util/ExtDomQuery");
const HiddenInputBuilder_1 = require("../util/HiddenInputBuilder");
var trim = mona_dish_1.Lang.trim;
const Lang_1 = require("../util/Lang");
var ofAssoc = Lang_1.ExtLang.ofAssoc;
/**
* Response processor
*
* Each XML tag is either a node or a leaf
* or both
*
* the processor provides a set of operations
* which are executed on a single leaf node per operation
* and present the core functionality of our response
*
* Note the response processor is stateful hence we bundle it in a class
* to reduce code we keep references tot contexts in place
*/
class ResponseProcessor {
constructor(request, externalContext, internalContext) {
this.request = request;
this.externalContext = externalContext;
this.internalContext = internalContext;
}
/**
* head replacement
* @param shadowDocument incoming shadow head data (aka cdata as xml reference or dom element)
* the data incoming must represent the html representation of the head itself one way or the other
*/
replaceHead(shadowDocument) {
const shadowHead = shadowDocument.querySelectorAll(Const_1.HTML_TAG_HEAD);
if (!shadowHead.isPresent()) {
return;
}
const head = ExtDomQuery_1.ExtDomQuery.querySelectorAll(Const_1.HTML_TAG_HEAD);
// full replace we delete everything
head.childNodes.delete();
this.addToHead(shadowHead);
//we copy the attributes as well (just in case myfaces introduces the id in head)
head.copyAttrs(shadowHead);
}
addToHead(shadowHead) {
const mappedHeadData = new ExtDomQuery_1.ExtDomQuery(shadowHead);
const scriptTags = [Const_1.HTML_TAG_SCRIPT];
const nonExecutables = mappedHeadData.filter(item => scriptTags.indexOf(item.tagName.orElse("").value) == -1);
nonExecutables.runHeadInserts(true);
//incoming either the outer head tag or its children
const nodesToAdd = (shadowHead.tagName.value === "HEAD") ? shadowHead.childNodes : shadowHead;
// this is stored for "post" processing
// after the rest of the "physical build up", head before body
const scriptElements = new mona_dish_1.DomQuery(...nodesToAdd.asArray
.filter(item => scriptTags.indexOf(item.tagName.orElse("").value) != -1));
this.addToHeadDeferred(scriptElements);
}
addToHeadDeferred(newElements) {
this.internalContext.assign(Const_1.DEFERRED_HEAD_INSERTS).value.push(newElements);
}
/**
* replaces the body in the expected manner
* which means the entire body content is refreshed
* however also the body attributes must be transferred
* keeping event handlers etc... in place
*
* @param shadowDocument .. an incoming shadow document hosting the new nodes
*/
replaceBody(shadowDocument) {
const shadowBody = shadowDocument.querySelectorAll(Const_1.HTML_TAG_BODY);
if (!shadowBody.isPresent()) {
return;
}
const shadowInnerHTML = shadowBody.innerHTML;
const resultingBody = ExtDomQuery_1.ExtDomQuery.querySelectorAll(Const_1.HTML_TAG_BODY);
const updateForms = resultingBody.querySelectorAll(Const_1.HTML_TAG_FORM);
// main difference, we cannot replace the body itself, but only its content
// we need a separate step for post-processing the incoming
// attributes, like classes, styles etc...
resultingBody.html(shadowInnerHTML).copyAttrs(shadowBody);
this.externalContext.assign((0, Const_1.$nsp)(Const_1.P_RENDER_OVERRIDE)).value = "@all";
this.storeForPostProcessing(updateForms, resultingBody);
}
/**
* Leaf Tag eval... process whatever is in the eval cdata block
*
* @param node the node to eval
*/
eval(node) {
ExtDomQuery_1.ExtDomQuery.globalEval(node.cDATAAsString);
}
/**
* processes an incoming error from the response
* which is hosted under the <error> tag
* @param node the node hosting the error in our response xml
* @param node the node in the xml hosting the error message
*/
error(node) {
/**
* <error>
* <error-name>String</error-name>
* <error-message><![CDATA[message]]></error-message>
* <error>
*/
const mergedErrorData = new ExtDomQuery_1.ExtConfig({});
mergedErrorData.assign(Const_1.SOURCE).value = this.externalContext.getIf(Const_1.P_AJAX_SOURCE).get(0).value;
mergedErrorData.assign(Const_1.ERROR_NAME).value = node.querySelectorAll(Const_1.ERROR_NAME).textContent(Const_1.EMPTY_STR);
mergedErrorData.assign(Const_1.ERROR_MESSAGE).value = node.querySelectorAll(Const_1.ERROR_MESSAGE).cDATAAsString;
const hasResponseXML = this.internalContext.get(Const_1.RESPONSE_XML).isPresent();
//we now store the response xml also in the error data for further details
mergedErrorData.assignIf(hasResponseXML, Const_1.RESPONSE_XML).value = this.internalContext.getIf(Const_1.RESPONSE_XML).value.get(0).value;
// error post-processing and enrichment (standard messages from keys)
const errorData = ErrorData_1.ErrorData.fromServerError(mergedErrorData);
// we now trigger an internally stored onError function which might be an attached to the context
// either we do not have an internal on error, or an on error has been based via params from the outside.
// In both cases they are attached to our contexts
this.triggerOnError(errorData);
AjaxImpl_1.Implementation.sendError(errorData);
}
/**
* process the redirect operation
*
* @param node
*/
redirect(node) {
Assertions_1.Assertions.assertUrlExists(node);
const redirectUrl = trim(node.attr(Const_1.ATTR_URL).value);
if (redirectUrl != Const_1.EMPTY_STR) {
window.location.href = redirectUrl;
}
}
/**
* processes the update operation and updates the node with the cdata block
* @param node the xml response node hosting the update info
* @param cdataBlock the cdata block with the new html code
*/
update(node, cdataBlock) {
const result = ExtDomQuery_1.ExtDomQuery.byId(node.id.value, true).outerHTML(cdataBlock, false, false);
const sourceForm = result === null || result === void 0 ? void 0 : result.firstParent(Const_1.HTML_TAG_FORM).orElseLazy(() => result.byTagName(Const_1.HTML_TAG_FORM, true));
if (sourceForm) {
this.storeForPostProcessing(sourceForm, result);
}
}
/**
* Delete handler, simply deletes the node referenced by the xml data
* @param node
*/
delete(node) {
mona_dish_1.DQ.byId(node.id.value, true).delete();
}
/**
* attributes leaf tag... process the attributes
*
* @param node
*/
attributes(node) {
const elem = mona_dish_1.DQ.byId(node.id.value, true);
node.byTagName(Const_1.XML_TAG_ATTR).each((item) => {
elem.attr(item.attr(Const_1.ATTR_NAME).value).value = item.attr(Const_1.ATTR_VALUE).value;
});
}
/**
* @param shadowDocument a shadow document which is needed for further processing
*/
replaceViewRoot(shadowDocument) {
this.replaceHead(shadowDocument);
this.replaceBody(shadowDocument);
}
/**
* Insert handling, either before or after
*
* @param node
*/
insert(node) {
//let insertId = node.id; //not used atm
const before = node.attr(Const_1.XML_TAG_BEFORE);
const after = node.attr(Const_1.XML_TAG_AFTER);
const insertNodes = mona_dish_1.DQ.fromMarkup(node.cDATAAsString);
if (before.isPresent()) {
mona_dish_1.DQ.byId(before.value, true).insertBefore(insertNodes);
this.internalContext.assign(Const_1.UPDATE_ELEMS).value.push(insertNodes);
}
if (after.isPresent()) {
const domQuery = mona_dish_1.DQ.byId(after.value, true);
domQuery.insertAfter(insertNodes);
this.internalContext.assign(Const_1.UPDATE_ELEMS).value.push(insertNodes);
}
}
/**
* Handler for the case <insert << before id="...
*
* @param node the node hosting the insert data
*/
insertWithSubTags(node) {
const before = node.querySelectorAll(Const_1.XML_TAG_BEFORE);
const after = node.querySelectorAll(Const_1.XML_TAG_AFTER);
before.each(item => {
const insertId = item.attr(Const_1.ATTR_ID);
const insertNodes = mona_dish_1.DQ.fromMarkup(item.cDATAAsString);
if (insertId.isPresent()) {
mona_dish_1.DQ.byId(insertId.value, true).insertBefore(insertNodes);
this.internalContext.assign(Const_1.UPDATE_ELEMS).value.push(insertNodes);
}
});
after.each(item => {
const insertId = item.attr(Const_1.ATTR_ID);
const insertNodes = mona_dish_1.DQ.fromMarkup(item.cDATAAsString);
if (insertId.isPresent()) {
mona_dish_1.DQ.byId(insertId.value, true).insertAfter(insertNodes);
this.internalContext.assign(Const_1.UPDATE_ELEMS).value.push(insertNodes);
}
});
}
/**
* Process the viewState update, update the affected
* forms with their respective new viewState values
*
*/
processViewState(node) {
if (ResponseProcessor.isViewStateNode(node)) {
const state = node.cDATAAsString;
this.internalContext.assign(Const_1.APPLIED_VST, node.id.value).value = new ImplTypes_1.StateHolder((0, Const_1.$nsp)(node.id.value), state);
return true;
}
return false;
}
processClientWindow(node) {
if (ResponseProcessor.isClientWindowNode(node)) {
const state = node.cDATAAsString;
this.internalContext.assign(Const_1.APPLIED_CLIENT_WINDOW, node.id.value).value = new ImplTypes_1.StateHolder((0, Const_1.$nsp)(node.id.value), state);
return true;
}
}
/**
* generic global eval which runs the embedded css and scripts
*/
globalEval() {
// phase one, if we have head inserts, we build up those before going into the script eval phase
let insertHeadElems = new ExtDomQuery_1.ExtDomQuery(...this.internalContext.getIf(Const_1.DEFERRED_HEAD_INSERTS).value);
insertHeadElems.runHeadInserts(true);
// phase 2 we run a script eval on all updated elements in the body
let updateElems = new ExtDomQuery_1.ExtDomQuery(...this.internalContext.getIf(Const_1.UPDATE_ELEMS).value);
updateElems.runCss();
// phase 3, we do the same for the css
updateElems.runScripts();
}
/**
* Postprocessing view state fixing
* this appends basically the incoming view states to the forms.
* It is called from outside after all forms have been processed basically
* as last lifecycle step, before going into the next request.
*/
fixViewStates() {
ofAssoc(this.internalContext.getIf(Const_1.APPLIED_VST).orElse({}).value)
.forEach(([, value]) => {
const namingContainerId = this.internalContext.getIf(Const_1.NAMING_CONTAINER_ID);
const namedViewRoot = !!this.internalContext.getIf(Const_1.NAMED_VIEWROOT).value;
const affectedForms = this.getContainerForms(namingContainerId)
.filter(affectedForm => this.isInExecuteOrRender(affectedForm));
this.appendViewStateToForms(affectedForms, namedViewRoot, value.value, namingContainerId.orElse("").value);
});
}
/**
* same as with view states before applies the incoming client windows as last step after the rest of the processing
* is done.
*/
fixClientWindow() {
ofAssoc(this.internalContext.getIf(Const_1.APPLIED_CLIENT_WINDOW).orElse({}).value)
.forEach(([, value]) => {
const namingContainerId = this.internalContext.getIf(Const_1.NAMING_CONTAINER_ID);
const namedViewRoot = !!this.internalContext.getIf(Const_1.NAMED_VIEWROOT).value;
const affectedForms = this.getContainerForms(namingContainerId)
.filter(affectedForm => this.isInExecuteOrRender(affectedForm));
this.appendClientWindowToForms(affectedForms, namedViewRoot, value.value, namingContainerId.orElse("").value);
});
}
updateNamedViewRootState() {
let partialId = this.internalContext.getIf(Const_1.NAMING_CONTAINER_ID);
let namedViewRoot = this.internalContext.getIf(Const_1.NAMED_VIEWROOT);
if (partialId.isPresent() &&
(namedViewRoot.isAbsent() ||
!namedViewRoot.value)) {
const SEP = (0, Const_1.$faces)().separatorchar;
this.internalContext.assign(Const_1.NAMED_VIEWROOT).value = (!!document.getElementById(partialId.value)) || (0, mona_dish_1.DQ$)(`input[name*='${(0, Const_1.$nsp)(Const_1.P_VIEWSTATE)}']`)
.filter(node => node.attr("name").value.indexOf(partialId.value + SEP) == 0).length > 0;
}
}
/**
* all processing done we can close the request and send the appropriate events
*/
done() {
const eventData = EventData_1.EventData.createFromRequest(this.request.value, this.internalContext, this.externalContext, Const_1.SUCCESS);
//because some frameworks might decorate them over the context in the response
const eventHandler = this.externalContext.getIf(Const_1.ON_EVENT).orElseLazy(() => this.internalContext.getIf(Const_1.ON_EVENT).value).orElse(Const_1.EMPTY_FUNC).value;
AjaxImpl_1.Implementation.sendEvent(eventData, eventHandler);
}
/**
* proper viewState -> form assignment
*
* @param forms the forms to append the viewState to
* @param viewState the final viewState
* @param namingContainerId
*/
appendViewStateToForms(forms, namedViewRoot, viewState, namingContainerId = "") {
this.assignState(forms, (0, Const_1.$nsp)(Const_1.SEL_VIEWSTATE_ELEM), namedViewRoot, viewState, namingContainerId);
}
/**
* proper clientWindow -> form assignment
*
* @param forms the forms to append the viewState to
* @param clientWindow the final viewState
* @param namingContainerId
*/
appendClientWindowToForms(forms, namedViewRoot, clientWindow, namingContainerId = "") {
this.assignState(forms, (0, Const_1.$nsp)(Const_1.SEL_CLIENT_WINDOW_ELEM), namedViewRoot, clientWindow, namingContainerId);
}
/**
* generic append state which appends a certain state as hidden element to an existing set of forms
*
* @param forms the forms to append or change to
* @param selector the selector for the state
* @param namedViewRoot if set to true, the name is also prefixed
* @param state the state itself which needs to be assigned
*
* @param namingContainerId
* @private
*/
assignState(forms, selector, namedViewRoot, state, namingContainerId) {
/**
* creates the viewState or client window id element
* @param form
*/
const createAndAppendHiddenInput = (form) => {
return new HiddenInputBuilder_1.HiddenInputBuilder(selector)
.withNamingContainerId(namingContainerId)
.withParent(form)
.withNamedViewRoot(namedViewRoot)
.build();
};
forms.each(form => {
const hiddenInput = form.querySelectorAll(selector)
.orElseLazy(() => createAndAppendHiddenInput(form));
hiddenInput.val = state;
});
}
/**
* Stores certain aspects of the dom for later post-processing
*
* @param updateForms the update forms which should receive standardized internal jsf data
* @param toBeEvaluated the resulting elements which should be evaluated
*/
storeForPostProcessing(updateForms, toBeEvaluated) {
this.storeForUpdate(updateForms);
this.storeForEval(toBeEvaluated);
}
/**
* helper to store a given form for the update post-processing (viewState)
*
* @param updateForms the dom query object pointing to the forms which need to be updated
*/
storeForUpdate(updateForms) {
this.internalContext.assign(Const_1.UPDATE_FORMS).value.push(updateForms);
}
/**
* same for eval (js and css)
*
* @param toBeEvaluated
*/
storeForEval(toBeEvaluated) {
this.internalContext.assign(Const_1.UPDATE_ELEMS).value.push(toBeEvaluated);
}
/**
* check whether a given XMLQuery node is an explicit viewState node
*
* @param node the node to check
* @returns if it is a viewState node
*/
static isViewStateNode(node) {
var _a, _b, _c, _d, _e, _f;
const SEP = (0, Const_1.$faces)().separatorchar;
return "undefined" != typeof ((_a = node === null || node === void 0 ? void 0 : node.id) === null || _a === void 0 ? void 0 : _a.value) && (((_b = node === null || node === void 0 ? void 0 : node.id) === null || _b === void 0 ? void 0 : _b.value) == (0, Const_1.$nsp)(Const_1.P_VIEWSTATE) ||
((_d = (_c = node === null || node === void 0 ? void 0 : node.id) === null || _c === void 0 ? void 0 : _c.value) === null || _d === void 0 ? void 0 : _d.indexOf([SEP, (0, Const_1.$nsp)(Const_1.P_VIEWSTATE)].join(Const_1.EMPTY_STR))) != -1 ||
((_f = (_e = node === null || node === void 0 ? void 0 : node.id) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.indexOf([(0, Const_1.$nsp)(Const_1.P_VIEWSTATE), SEP].join(Const_1.EMPTY_STR))) != -1);
}
/**
* incoming client window node also needs special processing
*
* @param node the node to check
* @returns true of it ii
*/
static isClientWindowNode(node) {
var _a, _b, _c, _d, _e, _f;
const SEP = (0, Const_1.$faces)().separatorchar;
return "undefined" != typeof ((_a = node === null || node === void 0 ? void 0 : node.id) === null || _a === void 0 ? void 0 : _a.value) && (((_b = node === null || node === void 0 ? void 0 : node.id) === null || _b === void 0 ? void 0 : _b.value) == (0, Const_1.$nsp)(Const_1.P_CLIENT_WINDOW) ||
((_d = (_c = node === null || node === void 0 ? void 0 : node.id) === null || _c === void 0 ? void 0 : _c.value) === null || _d === void 0 ? void 0 : _d.indexOf([SEP, (0, Const_1.$nsp)(Const_1.P_CLIENT_WINDOW)].join(Const_1.EMPTY_STR))) != -1 ||
((_f = (_e = node === null || node === void 0 ? void 0 : node.id) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.indexOf([(0, Const_1.$nsp)(Const_1.P_CLIENT_WINDOW), SEP].join(Const_1.EMPTY_STR))) != -1);
}
triggerOnError(errorData) {
this.externalContext.getIf(Const_1.ON_ERROR).orElseLazy(() => this.internalContext.getIf(Const_1.ON_ERROR).value).orElse(Const_1.EMPTY_FUNC).value(errorData);
}
/**
* filters the forms according to being member of the "execute" or "render" cycle
* @param affectedForm
* @private
*/
isInExecuteOrRender(affectedForm) {
const executes = this.externalContext.getIf((0, Const_1.$nsp)(Const_1.P_EXECUTE)).orElse("@none").value.split(/\s+/gi);
const renders = this.externalContext.getIf(Const_1.P_RENDER_OVERRIDE)
.orElseLazy(() => this.externalContext.getIf((0, Const_1.$nsp)(Const_1.P_RENDER)).value)
.orElse(Const_1.IDENT_NONE).value.split(/\s+/gi);
const executeAndRenders = executes.concat(...renders);
return [...executeAndRenders].filter(nameOrId => {
if ([Const_1.IDENT_ALL, Const_1.IDENT_NONE].indexOf(nameOrId) != -1) {
return true;
}
const NAME_OR_ID = this.getNameOrIdSelector(nameOrId);
//either the form directly is in execute or render or one of its children or one of its parents
return affectedForm.matchesSelector(NAME_OR_ID) ||
affectedForm.querySelectorAll(NAME_OR_ID).isPresent() ||
affectedForm.firstParent(NAME_OR_ID).isPresent();
}).length > 0;
}
/**
* gets all forms under a single naming container id
* @param namingContainerId
* @private
*/
getContainerForms(namingContainerId) {
if (namingContainerId.isPresent()) {
//naming container mode, all forms under naming container id must be processed
return (0, mona_dish_1.DQ$)(this.getNameOrIdSelector(namingContainerId.value))
// missing condition if the naming container is not present we have to
// use the body as fallback
.orElseLazy(() => mona_dish_1.DQ.byTagName(Const_1.HTML_TAG_BODY))
.byTagName(Const_1.HTML_TAG_FORM, true);
}
else {
return mona_dish_1.DQ.byTagName(Const_1.HTML_TAG_FORM);
}
}
getNameOrIdSelector(nameOrId) {
return `[id='${nameOrId}'], [name='${nameOrId}']`;
}
}
exports.ResponseProcessor = ResponseProcessor;
//# sourceMappingURL=ResponseProcessor.js.map