1 /* Licensed to the Apache Software Foundation (ASF) under one or more
  2  * contributor license agreements.  See the NOTICE file distributed with
  3  * this work for additional information regarding copyright ownership.
  4  * The ASF licenses this file to you under the Apache License, Version 2.0
  5  * (the "License"); you may not use this file except in compliance with
  6  * the License.  You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 /**
 17  * @class
 18  * @name _AjaxUtils
 19  * @memberOf myfaces._impl.xhrCore
 20  * @description
 21  *
 22  * A set of helper routines which are utilized within our Ajax subsystem and nowhere else
 23  *
 24  * TODO move this into a singleton, the current structure is
 25  * still a j4fry legacy we need to get rid of it in the long run
 26  */
 27 _MF_SINGLTN(_PFX_XHR+"_AjaxUtils", _MF_OBJECT,
 28 /** @lends myfaces._impl.xhrCore._AjaxUtils.prototype */
 29 {
 30 
 31     NAMED_VIEWROOT: "namedViewRoot",
 32     NAMING_CONTAINER_ID: "myfaces.partialId",
 33 
 34 
 35     /**
 36      * determines fields to submit
 37      * @param {Object} targetBuf - the target form buffer receiving the data
 38      * @param {Node} parentItem - form element item is nested in
 39      * @param {Array} partialIds - ids fo PPS
 40      */
 41     encodeSubmittableFields : function(targetBuf,
 42                                        parentItem, partialIds) {
 43             if (!parentItem) throw "NO_PARITEM";
 44             if (partialIds ) {
 45                 this.encodePartialSubmit(parentItem, false, partialIds, targetBuf);
 46             } else {
 47                 // add all nodes
 48                 var eLen = parentItem.elements.length;
 49                 for (var e = 0; e < eLen; e++) {
 50                     this.encodeElement(parentItem.elements[e], targetBuf);
 51                 } // end of for (formElements)
 52             }
 53 
 54     },
 55 
 56      /**
 57      * appends the issuing item if not given already
 58      * @param item
 59      * @param targetBuf
 60      */
 61     appendIssuingItem: function (item, targetBuf) {
 62         // if triggered by a Button send it along
 63         var identifier = item.id || item.name;
 64         var type = ((item && item.type) || "").toLowerCase();
 65 
 66         if(targetBuf.hasKey(identifier)) { //already processed within the values
 67             return;
 68         }
 69 
 70         //MYFACES-4606 we cannot send a value on an unchecked box as issuing element
 71         var isCheckboxRadio = "checkbox" == type || "radio" == type;
 72         if(isCheckboxRadio && !item.checked) {
 73             return;
 74         } else if (isCheckboxRadio) {
 75             var value = ("undefined" == typeof item.value || null == item.value) ? true : item.value;
 76             targetBuf.append(identifier, value);
 77         //item must have a valid value to be able to be appended, without it no dice!
 78         } else if(!(("undefined" == typeof item.value) || (null == item.value))) {
 79             var itemValue = item.value;
 80             targetBuf.append(identifier, itemValue);
 81         }
 82     },
 83 
 84 
 85     /**
 86      * encodes a single input element for submission
 87      *
 88      * @param {Node} element - to be encoded
 89      * @param {} targetBuf - a target array buffer receiving the encoded strings
 90      */
 91     encodeElement : function(element, targetBuf) {
 92 
 93         //browser behavior no element name no encoding (normal submit fails in that case)
 94         //https://issues.apache.org/jira/browse/MYFACES-2847
 95         if (!element.name) {
 96             return;
 97         }
 98 
 99         var _RT = this._RT;
100         var name = element.name;
101         var tagName = element.tagName.toLowerCase();
102         var elemType = element.type;
103         if (elemType != null) {
104             elemType = elemType.toLowerCase();
105         }
106 
107         // routine for all elements
108         // rules:
109         // - process only inputs, textareas and selects
110         // - elements muest have attribute "name"
111         // - elements must not be disabled
112         if (((tagName == "input" || tagName == "textarea" || tagName == "select") &&
113                 (name != null && name != "")) && !element.disabled) {
114 
115             // routine for select elements
116             // rules:
117             // - if select-one and value-Attribute exist => "name=value"
118             // (also if value empty => "name=")
119             // - if select-one and value-Attribute don't exist =>
120             // "name=DisplayValue"
121             // - if select multi and multple selected => "name=value1&name=value2"
122             // - if select and selectedIndex=-1 don't submit
123             if (tagName == "select") {
124                 // selectedIndex must be >= 0 sein to be submittet
125                 if (element.selectedIndex >= 0) {
126                     var uLen = element.options.length;
127                     for (var u = 0; u < uLen; u++) {
128                         // find all selected options
129                         //var subBuf = [];
130                         if (element.options[u].selected) {
131                             var elementOption = element.options[u];
132                             targetBuf.append(name, (elementOption.getAttribute("value") != null) ?
133                                     elementOption.value : elementOption.text);
134                         }
135                     }
136                 }
137             }
138 
139             // routine for remaining elements
140             // rules:
141             // - don't submit no selects (processed above), buttons, reset buttons, submit buttons,
142             // - submit checkboxes and radio inputs only if checked
143             if ((tagName != "select" && elemType != "button"
144                     && elemType != "reset" && elemType != "submit" && elemType != "image")
145                     && ((elemType != "checkbox" && elemType != "radio") || element.checked)) {
146                 if ('undefined' != typeof element.files && element.files != null && _RT.getXHRLvl() >= 2 && element.files.length) {
147                     //xhr level2
148                     targetBuf.append(name, element.files[0]);
149                 } else {
150                     targetBuf.append(name, element.value);
151                 }
152             }
153 
154         }
155     },
156 
157     _$ncRemap: function(internalContext, containerId) {
158         var namedVieRoot = internalContext[this.NAMED_VIEWROOT];
159         var namingContainerId = internalContext[this.NAMING_CONTAINER_ID];
160         if(!namedVieRoot || !namingContainerId) {
161             return containerId
162         }
163         if(containerId.indexOf(namingContainerId) == 0) {
164             return containerId;
165         }
166         return [namingContainerId, containerId].join("");
167     },
168 
169     /**
170      * determines the current naming container
171      * and assigns it internally
172      *
173      * @param internalContext
174      * @param formElement
175      * @private
176      */
177     _assignNamingContainerData: function(internalContext, formElement, separatorChar) {
178         const viewRootId = this._resolveViewRootId(formElement, separatorChar);
179 
180         if(!!viewRootId) {
181             internalContext[this.NAMED_VIEWROOT] = true;
182             internalContext[this.NAMING_CONTAINER_ID] = viewRootId;
183         }
184     },
185 
186     /**
187      * resolve the viewRoot id in a naming container situation
188      * (aka ViewState element name is prefixed)
189      * @param form
190      * @return a string (never null) which is either emtpy or contains the prefix for the ViewState
191      * (including the separator)
192      */
193     _resolveViewRootId: function(form, separatorChar) /*string*/ {
194         form = this._Dom.byId(form);
195         var _t = this;
196         var foundNames = this._Dom.findAll(form, function(node) {
197             var name = null;
198             if(node.getAttribute && node.getAttribute("name")) {
199                 name = node.getAttribute("name");
200             }
201             if(!name || name.indexOf(_t.P_VIEWSTATE)) {
202                 return false;
203             }
204             return node;
205         }, true);
206         if(!foundNames.length) {
207             return "";
208         }
209         return foundNames[0].name.split(separatorChar, 2)[0];
210     },
211 
212     /**
213      * as per jsdoc before the request it must be ensured that every post argument
214      * is prefixed with the naming container id (there is an exception in mojarra with
215      * the element=element param, which we have to follow here as well.
216      * (inputs are prefixed by name anyway normally this only affects our standard parameters)
217      * @private
218      */
219     _resoveConfigNamingContainerMapper: function(myfacesOptions, separatorChar) {
220         var isNamedViewRoot = !!myfacesOptions[this.NAMED_VIEWROOT];
221         if(!isNamedViewRoot) {
222             return;
223         }
224 
225         var partialId = myfacesOptions[this.NAMING_CONTAINER_ID];
226         var prefix = partialId + this.getSeparatorChar();
227         return function (data /*assoc array of key value pairs*/) {
228             var ret = {};
229             for(var key in data) {
230                 if(!data.hasOwnProperty(key)) {
231                     continue;
232                 }
233                 ret[prefix + key] = data[key]
234             }
235             return ret;
236         }
237     }
238 });