1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2008-2016 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @namespace ActionURL static class to supply the current context path, container and action.
 22  *            Additionally, builds a URL from a controller and an action.
 23  *            <p>Additional Documentation:
 24  *              <ul>
 25  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=url">LabKey URLs</a></li>
 26  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=tutorialActionURL">Tutorial: Basics: Building URLs and Filters</a></li>
 27  *              </ul>
 28  *           </p>
 29  */
 30 LABKEY.ActionURL = new function()
 31 {
 32     // private member variables
 33     var _parsedPathName = parsePathName(window.location.pathname);
 34 
 35     // private functions
 36     function buildParameterMap(paramString)
 37     {
 38         if (!paramString && LABKEY.postParameters)
 39         {
 40             // The caller hasn't requested us to parse a specific URL, and we have POST parameters that were written
 41             // back into the page by the server
 42             return LABKEY.postParameters;
 43         }
 44         if (!paramString)
 45         {
 46             paramString = window.location.search;
 47         }
 48         if (paramString.charAt(0) == '?')
 49             paramString = paramString.substring(1, paramString.length);
 50         var paramArray = paramString.split('&');
 51         var parameters = {};
 52         for (var i = 0; i < paramArray.length; i++)
 53         {
 54             var nameValue = paramArray[i].split('=', 2);
 55             if (nameValue.length == 1 && nameValue[0] != '')
 56             {
 57                 // Handle URL parameters with a name but no value or =
 58                 nameValue[1] = '';
 59             }
 60 
 61             if (nameValue.length == 2)
 62             {
 63                 var name = decodeURIComponent(nameValue[0]);
 64                 if (undefined == parameters[name])
 65                     parameters[name] = decodeURIComponent(nameValue[1]);
 66                 else
 67                 {
 68                     var curValue = parameters[name];
 69                     if (LABKEY.Utils.isArray(curValue))
 70                         curValue.push(decodeURIComponent(nameValue[1]));
 71                     else
 72                         parameters[name] = [curValue, decodeURIComponent(nameValue[1])];
 73                 }
 74             }
 75         }
 76         return parameters;
 77     }
 78 
 79     function codePath(path, method)
 80     {
 81         var a = path.split('/');
 82         for (var i=0 ; i<a.length ; i++)
 83             a[i] = method(a[i]);
 84         return a.join('/');
 85     }
 86 
 87     function parsePathName(path)
 88     {
 89         var start = LABKEY.contextPath.length;
 90         var end = path.lastIndexOf("/");
 91         var action = path.substring(end+1);
 92         path = path.substring(start,end);
 93         var controller = null;
 94         var dash = action.indexOf('-');
 95         if (0 < dash)
 96         {
 97             controller = action.substring(0,dash);
 98             action = action.substring(dash+1);
 99         }
100         else
101         {
102             var slash = path.indexOf('/',1);
103             if (slash < 0) // 21945: e.g. '/admin'
104                 controller = path.substring(1);
105             else
106                 controller = path.substring(1, slash);
107             path = path.substring(slash);
108         }
109         var dot = action.indexOf('.');
110         if (0 < dot)
111             action = action.substring(0,dot);
112         return {
113             controller: decodeURIComponent(controller),
114             action: decodeURIComponent(action),
115             containerPath: decodeURI(path)
116         };
117     }
118 
119 
120     /** @scope LABKEY.ActionURL */
121     return {
122         // public functions
123 
124         /**
125         * Gets the current context path.  The default context path for LabKey Server is '/labkey'.
126 		* @return {String} Current context path.
127 		*/
128         getContextPath : function()
129         {
130             return LABKEY.contextPath;
131         },
132 
133 		/**
134 		* Gets the current action
135 		* @return {String} Current action.
136 		*/
137         getAction : function()
138         {
139             return _parsedPathName.action;
140         },
141 
142 		/**
143 		* Gets the current (unencoded) container path.
144 		* @return {String} Current container path.
145 		*/
146         getContainer : function()
147         {
148             if (LABKEY.container && LABKEY.container.path)
149                 return LABKEY.container.path;
150             return _parsedPathName.containerPath;
151         },
152 
153         /**
154          * Gets the current container's name. For example, if you are in the
155          * /Project/SubFolder/MyFolder container, this method would return 'MyFolder'
156          * while getContainer() would return the entire path.
157          * @return {String} Current container name.
158          */
159         getContainerName : function()
160         {
161             var containerPath = LABKEY.ActionURL.getContainer();
162             var start = containerPath.lastIndexOf("/");
163             return containerPath.substring(start + 1);
164         },
165 
166         /**
167          * Get the current controller name
168          * @return {String} Current controller.
169          */
170         getController : function()
171         {
172             return _parsedPathName.controller;
173         },
174 
175         /**
176         * Gets a URL parameter by name. Note that if the given parameter name is present more than once
177         * in the query string, the returned value will be the first occurance of that parameter name. To get all
178         * instances of the parameter, use getParameterArray().
179         * @param {String} parameterName The name of the URL parameter.
180         * @return {String} The value of the named parameter, or undefined of the parameter is not present.
181         */
182         getParameter : function(parameterName)
183         {
184             var val = buildParameterMap()[parameterName];
185             return (val && LABKEY.Utils.isArray(val) && val.length > 0) ? val[0] : val;
186         },
187 
188         /**
189          * Gets a URL parameter by name. This method will always return an array of values, one for
190          * each instance of the parameter name in the query string. If the parameter name appears only once
191          * this method will return a one-element array.
192          * @param {String} parameterName The name of the URL parameter.
193          */
194         getParameterArray : function(parameterName)
195         {
196             var val = buildParameterMap()[parameterName];
197             return (val && !LABKEY.Utils.isArray(val)) ? [val] : val;
198         },
199 
200         /**
201         * Returns an object mapping URL parameter names to parameter values. If a given parameter
202         * appears more than once on the query string, the value in the map will be an array instead
203         * of a single value. Use LABKEY.Utils.isArray() to determine if the value is an array or not, or use
204         * getParameter() or getParameterArray() to retrieve a specific parameter name as a single value
205         * or array respectively.
206         * @param {String} [url] The URL to parse. If not specified, the browser's current location will be used.
207         * @return {Object} Map of parameter names to values.
208         */
209         getParameters : function(url)
210         {
211             var paramString;
212 
213             if (!url)
214             {
215                 return buildParameterMap(url);
216             }
217             if (url.indexOf('?') != -1)
218                 paramString = url.substring(url.indexOf('?') + 1, url.length);
219             else
220                 paramString = url;
221             return buildParameterMap(paramString);
222         },
223 
224         /**
225 		* Builds a URL from a controller and an action.  Uses the current container and context path.
226 		* @param {String} controller The controller to use in building the URL
227 		* @param {String} action The action to use in building the URL
228 		* @param {String} [containerPath] The container path to use (defaults to the current container)
229 		* @param {Object} [parameters] An object with properties corresponding to GET parameters to append to the URL.
230 		* Parameters will be encoded automatically. Parameter values that are arrays will be appended as multiple parameters
231          * with the same name. (Defaults to no parameters)
232 		* @example Examples:
233 
234 1. Build the URL for the 'plotChartAPI' action in the 'reports' controller within 
235 the current container:
236 
237 	var url = LABKEY.ActionURL.buildURL("reports", "plotChartApi");
238 
239 2.  Build the URL for the 'getWebPart' action in the 'reports' controller within 
240 the current container:
241 
242 	var url = LABKEY.ActionURL.buildURL("project", "getWebPart");
243 
244 3.  Build the URL for the 'updateRows' action in the 'query' controller within
245 the container "My Project/My Folder":
246 
247 	var url = LABKEY.ActionURL.buildURL("query", "updateRows",
248 	    "My Project/My Folder");
249 
250 4.  Navigate the browser to the study controller's begin action in the current
251 container:
252 
253     window.location = LABKEY.ActionURL.buildURL("study", "begin");
254 
255 5.  Navigate the browser to the study controller's begin action in the folder
256 "/myproject/mystudyfolder":
257          
258     window.location = LABKEY.ActionURL.buildURL("study", "begin",
259         "/myproject/mystudyfolder");
260 
261 6.  Navigate to the list controller's insert action, passing a returnUrl parameter
262 that points back to the current page:
263          
264     window.location = LABKEY.ActionURL.buildURL("list", "insert",
265          LABKEY.ActionURL.getContainer(), {listId: 50, returnUrl: window.location});
266 		* @return {String} URL constructed from the current container and context path,
267 					plus the specified controller and action.
268 		*/
269         buildURL : function(controller, action, containerPath, parameters)
270         {
271             if(!containerPath)
272                 containerPath = this.getContainer();
273             containerPath = LABKEY.ActionURL.encodePath(containerPath);
274 
275             //ensure that container path begins and ends with a /
276             if(containerPath.charAt(0) != "/")
277                 containerPath = "/" + containerPath;
278             if(containerPath.charAt(containerPath.length - 1) != "/")
279                 containerPath = containerPath + "/";
280             if (-1 == action.indexOf('.'))
281                 action += '.view';
282             var query = LABKEY.ActionURL.queryString(parameters);
283 
284             var newUrl;
285             if (LABKEY.experimental && LABKEY.experimental.containerRelativeURL)
286                 newUrl = LABKEY.contextPath + containerPath + controller + "-" + action;
287             else
288                 newUrl = LABKEY.contextPath + "/" + controller + containerPath + action;
289             if (query)
290                 newUrl += '?' + query;
291             return newUrl;
292         },
293 
294         /**
295          * @private
296          * Encoder for LabKey container paths that accounts for / to only encode the proper names. NOTE: This method is
297          * marked as private and could change at any time.
298          * @param {String} decodedPath An unencoded container path.
299          * @returns {String} An URI encoded container path.
300          */
301         encodePath : function(decodedPath)
302         {
303             return codePath(decodedPath, encodeURIComponent);
304         },
305 
306         /**
307          * @private
308          * Decoder for LabKey container paths that accounts for / to only decode the proper names. NOTE: This method is
309          * marked as private and could change at any time.
310          * @param {String} encodedPath An encoded container path.
311          * @returns {String} An URI decoded container path.
312          */
313         decodePath : function(encodedPath)
314         {
315             return codePath(encodedPath, decodeURIComponent);
316         },
317 
318         /**
319          * Turn the parameter object into a query string (e.g. {x:'fred'} -> "x=fred").
320          * The returned query string is not prepended by a question mark ('?').
321          * 
322          * @param {Object} [parameters] An object with properties corresponding to GET parameters to append to the URL.
323          * Parameters will be encoded automatically. Parameter values that are arrays will be appended as multiple parameters
324           * with the same name. (Defaults to no parameters.)
325          */
326         queryString : function(parameters)
327         {
328             if (!parameters)
329                 return '';
330             var query = '', and = '', pval, parameter, aval;
331 
332             for (parameter in parameters)
333             {
334                 if (parameters.hasOwnProperty(parameter))
335                 {
336                     pval = parameters[parameter];
337 
338                     if (pval === null || pval === undefined)
339                         pval = '';
340 
341                     if (LABKEY.Utils.isArray(pval))
342                     {
343                         for (var idx = 0; idx < pval.length; ++idx)
344                         {
345                             aval = pval[idx];
346                             query += and + encodeURIComponent(parameter) + '=' + encodeURIComponent(pval[idx]);
347                             and = '&';
348                         }
349                     }
350                     else
351                     {
352                         query += and + encodeURIComponent(parameter) + '=' + encodeURIComponent(pval);
353                         and = '&';
354                     }
355                 }
356             }
357             return query;
358         },
359 
360 
361         /**
362          * Get the current base URL, which includes context path by default
363          * for example: http://labkey.org/labkey/
364          * @param {boolean} [noContextPath] Set true to omit the context path.  Defaults to false.
365          * @return {String} Current base URL.
366          */
367         getBaseURL : function(noContextPath)
368         {
369             return window.location.protocol + '//' + window.location.host + (noContextPath ? '' : LABKEY.ActionURL.getContextPath() + '/');
370         }
371     };
372 };
373 
374 
375