UNPKG

jsf.js_next_gen

Version:

A next generation typescript reimplementation of jsf.js

468 lines 22.7 kB
"use strict"; /*! 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 &lt;error&gt; 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 &lt;insert <&lt; 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