You are viewing a plain text version of this content. The canonical link for it is here.
Posted to pluto-scm@portals.apache.org by ms...@apache.org on 2014/11/19 17:42:18 UTC
[02/15] portals-pluto git commit: Started adding portlet hub
prototype to pluto
http://git-wip-us.apache.org/repos/asf/portals-pluto/blob/1eeac9c3/pluto-portal/src/main/webapp/portletHubImpl.js
----------------------------------------------------------------------
diff --git a/pluto-portal/src/main/webapp/portletHubImpl.js b/pluto-portal/src/main/webapp/portletHubImpl.js
new file mode 100644
index 0000000..befa84e
--- /dev/null
+++ b/pluto-portal/src/main/webapp/portletHubImpl.js
@@ -0,0 +1,645 @@
+/* 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.
+ */
+
+/*
+ * This source code implements specifications defined by the Java
+ * Community Process. In order to remain compliant with the specification
+ * DO NOT add / change / or delete method signatures!
+ */
+
+/**
+ * @fileOverview
+ * This module provides mock data & functions for the mock portlet hub.
+ * <p>
+ * The functions encapsulate all mockup-specific implementation details.
+ * The intention is that support for a different portal can added by
+ * reimplementing these functions.
+ * <p>
+ * To implement the portlet hub for your portal, implement the methods
+ * described under "portlet.impl".
+ * In order to make the Jasmine tests work with your implementation, the
+ * test functions will need to be modified appropiately as well.
+ * <p>
+ * Sets up data that is used by the Jasmine unit tests as well as by the
+ * portlet hub implementation. In particular, the Jasmine tests need initialization
+ * data for the portlets on the page. The portlet hub requires this
+ * data as well, so it is being provided through the global name space to be used both
+ * by the hub and by the test code.
+ * <p>
+ * Later, it should hopefully be possible to use the Jasmine tests with a "live"
+ * portlet hub implementation by making the portlet info on the page
+ * available to Jasmine through this mechanism.
+ * <p>
+ * A "real" portlet hub implementation would likely obtain this information in a
+ * different manner.
+ *
+ * @author Scott Nicklous
+ * @copyright IBM Corp., 2014
+ */
+var portlet = portlet || {};
+
+(function () {
+ 'use strict';
+
+ var isInitialized = false,
+
+ /**
+ * The object holding the portlet data for each portlet on the page.
+ * This an object indexed by portlet ID.
+ * @property {PortletData} {string}
+ * The object holds one portlet data object for each portlet. The string
+ * property name is the portlet ID.
+ * @private
+ */
+ pageState,
+
+
+ /**
+ * Determines if the specified portlet ID is present.
+ * @param {string} pid The portlet ID
+ * @returns {boolean} true if the portlet ID can be found
+ * @function
+ * @private
+ */
+ isValidId = function (pid) {
+ var id;
+ for (id in pageState) {
+ if (!pageState.hasOwnProperty(id)) {
+ continue;
+ }
+ if (pid === id) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * get the available portlet IDs in an array
+ */
+ getIds = function () {
+ var id, ids = [];
+ for (id in pageState) {
+ if (pageState.hasOwnProperty(id)) {
+ ids.push(id);
+ }
+ }
+ return ids;
+ },
+
+ /**
+ * gets parameter value
+ */
+ getParmVal = function (pid, name) {
+ return pageState[pid].state.parameters[name];
+ },
+
+ /**
+ * gets parameter value
+ */
+ setParmVal = function (pid, name, val) {
+ if (val === undefined) {
+ delete pageState[pid].state.parameters[name];
+ } else {
+ pageState[pid].state.parameters[name] = val.slice(0);
+ }
+ },
+
+ /**
+ * Compares the values of two parameters and returns true if they are equal
+ *
+ * @param {string[]} parm1 First parameter
+ * @param {string[]} parm2 2nd parameter
+ * @returns {boolean} true if the new parm value is equal to the current value
+ * @private
+ */
+ _isParmEqual = function(parm1, parm2) {
+ var ii;
+
+ // The values are either string arrays or undefined.
+
+ if ((parm1 === undefined) && (parm2 === undefined)) {
+ return true;
+ }
+
+ if ((parm1 === undefined) || (parm2 === undefined)) {
+ return false;
+ }
+
+ if (parm1.length !== parm2.length) {
+ return false;
+ }
+
+
+ for (ii = parm1.length - 1; ii >= 0; ii--) {
+ if (parm1[ii] !== parm2[ii]) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Compares the values of the named parameter in the new portlet state
+ * with the values of that parameter in the current state.
+ *
+ * @param {string} pid The portlet ID
+ * @param {PortletState} state The new portlet state
+ * @param {string} name The parameter name to check
+ * @returns {boolean} true if the new parm value is different
+ * from the current value
+ * @private
+ */
+ isParmInStateEqual = function (pid, state, name) {
+ var newVal = state.parameters[name], oldVal = getParmVal(pid, name);
+
+ return _isParmEqual(newVal, oldVal);
+ },
+
+ /**
+ * gets defeined PRPs for the portlet
+ */
+ getPRPNames = function (pid) {
+ return pageState[pid].pubParms;
+ },
+
+ /**
+ * Gets the updated public parameters for the given portlet
+ * ID and new portlet state.
+ * Returns an object whose properties are the names of the
+ * updated public parameters. The values are the new public
+ * parameter values.
+ *
+ * @param {string} pid The portlet ID
+ * @param {PortletState} state The new portlet state
+ * @returns {object} object containing the updated PRPs
+ * @private
+ */
+ getUpdatedPRPs = function (pid, state) {
+ var prps = {}, ii = 0, prpNames = getPRPNames(pid), name;
+
+ for (ii = 0; ii < prpNames.length; ii++) {
+ name = prpNames[ii];
+ if (isParmInStateEqual(pid, state, name) === false) {
+ prps[name] = state.parameters[name];
+ }
+ }
+
+ return prps;
+ },
+
+
+ /**
+ * Returns a deep-copy clone of the input portlet state object.
+ * Used to provide the portlet client with a copy of the current
+ * state data rather than a reference to the live state itself.
+ *
+ * @param {PortletState} state The portlet state object to check
+ * @returns {PortletState} Clone of the input portlet state
+ * @private
+ */
+ cloneState = function (aState) {
+ var newParams = {},
+ newState = {
+ portletMode : aState.portletMode,
+ windowState : aState.windowState,
+ parameters : newParams
+ }, key, oldParams = aState.parameters;
+
+ for (key in oldParams) {
+ newParams[key] = oldParams[key].slice(0);
+ }
+
+ return newState;
+ },
+
+ /**
+ * Get allowed window states for portlet
+ */
+ getAllowedWS = function (pid) {
+ return pageState[pid].allowedWS.slice(0);
+ },
+
+ /**
+ * Get allowed portlet modes for portlet
+ */
+ getAllowedPM = function (pid) {
+ return pageState[pid].allowedPM.slice(0);
+ },
+
+
+ /**
+ * gets render data for the portlet
+ */
+ getRenderData = function (pid) {
+ return pageState[pid].renderData;
+ },
+
+ /**
+ * gets state for the portlet
+ */
+ getState = function (pid) {
+ return pageState[pid].state;
+ },
+
+ /**
+ * sets state for the portlet. returns
+ * array of IDs for portlets that were affected by the change,
+ * taking into account the public render parameters.
+ */
+ setState = function (pid, state) {
+ var pids, prps = getUpdatedPRPs(pid, state), prp, ii, tpid, upids = [], newVal, oldVal, prpNames;
+
+ // For each updated PRP for the
+ // initiating portlet, update that PRP in the other portlets.
+ for (prp in prps) {
+ if (prps.hasOwnProperty(prp)) {
+
+ newVal = prps[prp];
+
+ // process each portlet ID
+ pids = getIds();
+ for (ii = 0; ii < pids.length; ii++) {
+ tpid = pids[ii];
+
+ // don't update for initiating portlet. that's done after the loop
+ if (tpid !== pid) {
+
+ oldVal = getParmVal(tpid, prp);
+ prpNames = getPRPNames(tpid);
+
+ // check for public parameter and if the value has changed
+ if ((prpNames.indexOf(prp) >= 0) &&
+ (_isParmEqual(oldVal, newVal) === false)) {
+
+ if (newVal === undefined) {
+ delete pageState[tpid].state.parameters[prp];
+ } else {
+ pageState[tpid].state.parameters[prp] = newVal.slice(0);
+ }
+ upids.push(tpid);
+
+ }
+ }
+ }
+ }
+ }
+
+ // update state for the initiating portlet
+ pageState[pid].state = state;
+ upids.push(pid);
+
+
+ // Use Promise to allow for potential server communication -
+ return new Promise(function (resolve, reject) {
+ var simval = '';
+ if (pid === 'SimulateCommError' && state.parameters.SimulateError !== undefined) {
+ simval = state.parameters.SimulateError[0];
+ }
+
+ // reject promise of an error is to be simulated
+ if (simval === 'reject') {
+ reject(new Error("Simulated error occurred when setting state!"));
+ } else {
+ resolve(upids);
+ }
+ });
+ },
+
+
+ /**
+ * updates page state from string and returns array of portlet IDs
+ * to be updated.
+ *
+ * @param {string} ustr The
+ * @param {string} pid The portlet ID
+ * @private
+ */
+ updatePageStateFromString = function (ustr, pid) {
+ var states, tpid, state, upids = [];
+
+ states = decodeUpdateString(ustr, pid);
+
+ // Update states and collect IDs of affected portlets.
+ for (tpid in states) {
+ if (states.hasOwnProperty(tpid)) {
+ state = states[tpid];
+ pageState[tpid].state = state;
+ upids.push(tpid);
+ }
+ }
+
+ return upids;
+ },
+
+
+ /**
+ * performs the actual action.
+ *
+ * @param {string} type The URL type
+ * @param {string} pid The portlet ID
+ * @param {PortletParameters} parms
+ * Additional parameters. May be <code>null</code>
+ * @param {HTMLFormElement} Form to be submitted
+ * May be <code>null</code>
+ * @private
+ */
+ executeAction = function (pid, parms, element) {
+ var states, ustr, tpid, state, upids = [];
+
+ // pretend to create a url, etc. ... for the mockup
+ // we don't need the parms or element
+
+ // get the mockup data update string and make it into an object.
+ // update each affected portlet client. Makes use of a
+ // test function for decoding.
+
+ ustr = portlet.test.data.updateStrings[pid];
+ upids = updatePageStateFromString(ustr, pid);
+
+ // Use Promise to allow for potential server communication -
+ return new Promise(function (resolve, reject) {
+ var simval = '';
+ if (pid === 'SimulateCommError' && (parms)) {
+ simval = parms.SimulateError;
+ if (simval) {
+ simval = simval[0];
+ }
+ }
+
+ // reject promise of an error is to be simulated
+ if (simval === 'reject') {
+ reject(new Error("Simulated error occurred during action!"));
+ } else {
+ resolve(upids);
+ }
+ });
+
+ },
+
+
+ /**
+ * Returns a URL of the specified type.
+ *
+ * @param {string} type The URL type
+ * @param {string} pid The portlet ID
+ * @param {PortletParameters} parms
+ * Additional parameters. May be <code>null</code>
+ * @param {string} cache Cacheability. Must be present if
+ * type = "RESOURCE". May be <code>null</code>
+ * @private
+ */
+ getUrl = function (type, pid, parms, cache) {
+
+ var url = "http://www.dummyportal.com/some/context/";
+ var qparms = [], rparms, qp, state, tpid, val, pids, ii, jj;
+
+ // for a mockup, should be good enough ...
+ // optimized for easy parsing by the test code.
+ // see "portlet.test" below.
+
+ // This is how it should look:
+ // http://www.dummyportal.com/some/context/PortletA/ACTION/PAGE/?&rp1=resVal&RENDERPARMS
+ // &~~~&PortletA&mode=VIEW&ws=NORMAL&parm1=Fred&parm2=Wilma&parm2=Pebbles&parm3=Barney&parm3=Betty&parm3=Bam%20Bam
+ // &~~~&PortletB&mode=VIEW&ws=NORMAL&parm1=val1&pubparm1=pubval1&parm2=val2&parm2=val3
+ // &~~~&PortletC&mode=VIEW&ws=NORMAL&parm1=val1&pubparm1=pubval1&pubparm2=pubval2&parm2=val2&parm2=val3
+ // &~~~&PortletD&mode=VIEW&ws=NORMAL&pubparm1=private_val1&pubparm2=pubval2&parm2=val2&parm2=val3
+ // &~~~&PortletE&mode=VIEW&ws=NORMAL&parm1=val1&parm2=val2&parm2=val3&pubparm1=pubval1&pubparm2=pubval2
+ // &~~~&PortletF&mode=VIEW&ws=NORMAL&~~~
+
+ url += pid + "/" + type + "/";
+ url += (((cache===undefined)||(cache===null))?"cacheLevelPage":cache) + "/?";
+
+ // put the additional parameters on the URL
+ if (parms !== null) {
+ for (qp in parms) {
+ for (var ii=0; ii<parms[qp].length; ii++) {
+ val = (parms[qp][ii] === null) ? "" : "=" + encodeURIComponent(parms[qp][ii]);
+ qparms.push(encodeURIComponent(qp) + val);
+ }
+ }
+ }
+
+ qparms.push("RENDERPARMS")
+ qparms.push("~~~")
+
+ // Don't put any render parameters on it cache=cacheLevelFull
+ if ((type !== "RESOURCE") ||
+ ((type === "RESOURCE") && (cache !== "cacheLevelFull"))) {
+
+ pids = getIds();
+ jj = pids.length;
+ while ((jj = jj - 1) >= 0) {
+ tpid = pids[jj];
+
+ // If cache=cacheLevelPortlet, only put on the parms for that portlet
+ if ((type === "RESOURCE") && (cache === "cacheLevelPortlet") && (pid !== tpid)) {
+ continue;
+ }
+
+ // put the portlet state parameters on the URL
+ state = getState(tpid);
+
+ qparms.push(encodeURIComponent(tpid));
+ qparms.push("mode=" + state.portletMode);
+ qparms.push("ws=" + state.windowState);
+
+ rparms = state.parameters;
+ for (qp in rparms) {
+ for (ii=0; ii<rparms[qp].length; ii++) {
+ val = (rparms[qp][ii] === null) ? "" : "=" + encodeURIComponent(rparms[qp][ii]);
+ qparms.push(encodeURIComponent(qp) + val);
+ }
+ }
+ qparms.push("~~~");
+
+ }
+ }
+
+ // put on the query parms
+ while (qparms.length > 0) {
+ url += "&" + qparms.shift();
+ }
+
+ // Use Promise to allow for potential server communication -
+ return new Promise(function (resolve, reject) {
+ var simval = '';
+ if (pid === 'SimulateCommError' && (parms)) {
+ simval = parms.SimulateError;
+ if (simval) {
+ simval = simval[0];
+ }
+ }
+
+ // reject promise of an error is to be simulated
+ if (simval === 'reject') {
+ reject(new Error("Simulated error occurred when getting a URL!"));
+ } else {
+ resolve(url);
+ }
+ });
+ },
+
+
+ /**
+ * Exception thrown when a portlet hub method is provided with an invalid argument.
+ * @typedef IllegalArgumentException
+ * @property {string} name The exception name, equal to "IllegalArgumentException"
+ * @property {string} message An optional message that provides more detail about the exception
+ */
+ throwIllegalArgumentException = function (msg) {
+ throw {
+ name : "IllegalArgumentException",
+ message : msg
+ };
+ },
+
+
+ // decodes the update strings
+ decodeUpdateString = function (ustr) {
+ var states = {}, state, pid, ii, ind,
+ pids = ustr.match(/~~&.*?&/g); // reluctant match
+
+ // If there is no match, bad input data
+ if (pids === null) {
+ throwIllegalArgumentException("Invalid update string.");
+ }
+
+ // For each portlet being updated, get the new data
+ ii = pids.length;
+ while ((ii = ii -1) >= 0) {
+ if (pids[ii].length < 5) {
+ // the portlet ID must be at least 1 character long
+ throwIllegalArgumentException("Invalid portlet ID in update string.");
+ }
+
+ // trim extra stuff off of the portlet id
+ ind = pids[ii].length - 1;
+ pid = pids[ii].substring(3, ind);
+
+ state = portlet.test.action.getState(ustr, pid);
+ states[pid] = state;
+
+ }
+
+ return states;
+ };
+
+
+ /**
+ * Register a portlet. The impl is passed the portlet ID for the portlet.
+ * The impl must retrieve the information for the portlet in an appropriate
+ * manner. It must return a Promise that is fulfilled when data for the
+ * portlet becomes available and is rejected if an error occurs or if the
+ * portlet ID is invalid.
+ *
+ * @param {string} pid Portlet ID
+ *
+ * @returns {Promise} fulfilled when data is available
+ *
+ * @function
+ * @private
+ */
+ portlet.impl = portlet.impl || {};
+ portlet.impl.register = function (pid) {
+
+ // take care of moc data initialization
+ if (!isInitialized) {
+ pageState = portlet.impl.getInitData();
+ isInitialized = true;
+ }
+
+ // stubs for accessing data for this portlet
+ var stubs = {
+
+ /**
+ * Get allowed window states for portlet
+ */
+ getAllowedWS : function () {return getAllowedWS(pid);},
+
+ /**
+ * Get allowed portlet modes for portlet
+ */
+ getAllowedPM : function () {return getAllowedPM(pid);},
+
+ /**
+ * Get render data for portlet, if any
+ */
+ getRenderData : function () {return getRenderData(pid);},
+
+ /**
+ * Get current portlet state
+ */
+ getState : function () {return getState(pid);},
+
+ /**
+ * Set new portlet state. Returns promise fullfilled with an array of
+ * IDs of portlets whose state have been modified.
+ */
+ setState : function (state) {return setState(pid, state);},
+
+ /**
+ * Perform the Ajax action request
+ */
+ executeAction : function (parms, element, callback, onError) {return executeAction(pid, parms, element, callback, onError);},
+
+ /**
+ * Get a URL of the specified type - resource or partial action
+ */
+ getUrl : function (type, parms, cache) {return getUrl(type, pid, parms, cache);},
+
+ /**
+ * Decode the update string returned by the partial action request.
+ * Returns array of IDs of portlets to be updated.
+ */
+ decodeUpdateString : function (ustr) {return updatePageStateFromString(ustr, pid);},
+
+ };
+
+ return new Promise(
+ function(resolve, reject) {
+
+ // verify the input pid
+ if (isValidId(pid)) {
+
+ switch(pid) {
+ case 'SimulateLongWait':
+ window.setTimeout(function () {resolve(stubs);}, 500);
+ break;
+ case 'SimulateError':
+ window.setTimeout(function () {reject(new Error("Bad error happened!"));}, 100);
+ break;
+ default:
+ window.setTimeout(function () {resolve(stubs);}, 10);
+ }
+
+ } else {
+ reject(new Error("Invalid portlet ID: " + pid));
+ }
+ }
+ );
+ };
+
+ // Expose some internal functions for test purposes -
+
+ portlet.test = {
+ getIds : getIds
+ };
+
+})();