Index: branches/wmf/1.18wmf1/resources/mediawiki/mediawiki.Uri.js |
— | — | @@ -56,7 +56,7 @@ |
57 | 57 | * |
58 | 58 | */ |
59 | 59 | |
60 | | -( function( $ ) { |
| 60 | +( function( $, mw ) { |
61 | 61 | |
62 | 62 | /** |
63 | 63 | * Function that's useful when constructing the URI string -- we frequently encounter the pattern of |
— | — | @@ -89,172 +89,213 @@ |
90 | 90 | 'host', // www.test.com |
91 | 91 | 'port', // 81 |
92 | 92 | 'path', // /dir/dir.2/index.htm |
93 | | - 'query', // q1=0&&test1&test2=value (will become { q1: 0, test1: '', test2: 'value' } ) |
| 93 | + 'query', // q1=0&&test1&test2=value (will become { q1: '0', test1: '', test2: 'value' } ) |
94 | 94 | 'fragment' // top |
95 | 95 | ]; |
96 | 96 | |
97 | | - /** |
98 | | - * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse. |
99 | | - * @constructor |
100 | | - * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties. |
101 | | - * @param {Boolean} strict mode (when parsing a string) |
102 | | - */ |
103 | | - mw.Uri = function( uri, strictMode ) { |
104 | | - strictMode = !!strictMode; |
105 | | - if ( uri !== undefined && uri !== null || uri !== '' ) { |
106 | | - if ( typeof uri === 'string' ) { |
107 | | - this._parse( uri, strictMode ); |
108 | | - } else if ( typeof uri === 'object' ) { |
109 | | - var _this = this; |
110 | | - $.each( properties, function( i, property ) { |
111 | | - _this[property] = uri[property]; |
112 | | - } ); |
113 | | - if ( this.query === undefined ) { |
114 | | - this.query = {}; |
115 | | - } |
116 | | - } |
117 | | - } |
118 | | - if ( !( this.protocol && this.host && this.path ) ) { |
119 | | - throw new Error( 'Bad constructor arguments' ); |
120 | | - } |
121 | | - }; |
122 | 97 | |
123 | 98 | /** |
124 | | - * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986 |
125 | | - * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a + |
126 | | - * @param {String} string |
127 | | - * @return {String} encoded for URI |
| 99 | + * We use a factory to inject a document location, for relative URLs, including protocol-relative URLs. |
| 100 | + * so the library is still testable & purely functional. |
128 | 101 | */ |
129 | | - mw.Uri.encode = function( s ) { |
130 | | - return encodeURIComponent( s ) |
131 | | - .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28') |
132 | | - .replace( /\)/g, '%29').replace( /\*/g, '%2A') |
133 | | - .replace( /%20/g, '+' ); |
134 | | - }; |
| 102 | + mw.UriRelative = function( documentLocation ) { |
135 | 103 | |
136 | | - /** |
137 | | - * Standard decodeURIComponent, with '+' to space |
138 | | - * @param {String} string encoded for URI |
139 | | - * @return {String} decoded string |
140 | | - */ |
141 | | - mw.Uri.decode = function( s ) { |
142 | | - return decodeURIComponent( s ).replace( /\+/g, ' ' ); |
143 | | - }; |
144 | | - |
145 | | - mw.Uri.prototype = { |
146 | | - |
147 | 104 | /** |
148 | | - * Parse a string and set our properties accordingly. |
149 | | - * @param {String} URI |
150 | | - * @param {Boolean} strictness |
151 | | - * @return {Boolean} success |
| 105 | + * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse. |
| 106 | + * @constructor |
| 107 | + * @param {Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). |
| 108 | + * Object must have non-blank 'protocol', 'host', and 'path' properties. |
| 109 | + * @param {Object|Boolean} Object with options, or (backwards compatibility) a boolean for strictMode |
| 110 | + * - strictMode {Boolean} Trigger strict mode parsing of the url. Default: false |
| 111 | + * - overrideKeys {Boolean} Wether to let duplicate query parameters override eachother (true) or automagically |
| 112 | + * convert to an array (false, default). |
152 | 113 | */ |
153 | | - _parse: function( str, strictMode ) { |
154 | | - var matches = parser[ strictMode ? 'strict' : 'loose' ].exec( str ); |
155 | | - var uri = this; |
156 | | - $.each( properties, function( i, property ) { |
157 | | - uri[ property ] = matches[ i+1 ]; |
158 | | - } ); |
| 114 | + function Uri( uri, options ) { |
| 115 | + options = typeof options === 'object' ? options : { strictMode: !!options }; |
| 116 | + options = $.extend( { |
| 117 | + strictMode: false, |
| 118 | + overrideKeys: false |
| 119 | + }, options ); |
159 | 120 | |
160 | | - // uri.query starts out as the query string; we will parse it into key-val pairs then make |
161 | | - // that object the "query" property. |
162 | | - // we overwrite query in uri way to make cloning easier, it can use the same list of properties. |
163 | | - var q = {}; |
164 | | - // using replace to iterate over a string |
165 | | - if ( uri.query ) { |
166 | | - uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { |
167 | | - if ( $1 ) { |
168 | | - var k = mw.Uri.decode( $1 ); |
169 | | - var v = ( $2 === '' || $2 === undefined ) ? null : mw.Uri.decode( $3 ); |
170 | | - if ( typeof q[ k ] === 'string' ) { |
171 | | - q[ k ] = [ q[ k ] ]; |
172 | | - } |
173 | | - if ( typeof q[ k ] === 'object' ) { |
174 | | - q[ k ].push( v ); |
175 | | - } else { |
176 | | - q[ k ] = v; |
177 | | - } |
| 121 | + if ( uri !== undefined && uri !== null || uri !== '' ) { |
| 122 | + if ( typeof uri === 'string' ) { |
| 123 | + this._parse( uri, options ); |
| 124 | + } else if ( typeof uri === 'object' ) { |
| 125 | + var _this = this; |
| 126 | + $.each( properties, function( i, property ) { |
| 127 | + _this[property] = uri[property]; |
| 128 | + } ); |
| 129 | + if ( this.query === undefined ) { |
| 130 | + this.query = {}; |
178 | 131 | } |
179 | | - } ); |
| 132 | + } |
180 | 133 | } |
181 | | - this.query = q; |
182 | | - }, |
183 | 134 | |
184 | | - /** |
185 | | - * Returns user and password portion of a URI. |
186 | | - * @return {String} |
187 | | - */ |
188 | | - getUserInfo: function() { |
189 | | - return cat( '', this.user, cat( ':', this.password, '' ) ); |
190 | | - }, |
| 135 | + // protocol-relative URLs |
| 136 | + if ( !this.protocol ) { |
| 137 | + this.protocol = defaultProtocol; |
| 138 | + } |
191 | 139 | |
| 140 | + if ( !( this.protocol && this.host && this.path ) ) { |
| 141 | + throw new Error( 'Bad constructor arguments' ); |
| 142 | + } |
| 143 | + } |
| 144 | + |
192 | 145 | /** |
193 | | - * Gets host and port portion of a URI. |
194 | | - * @return {String} |
| 146 | + * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986 |
| 147 | + * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a + |
| 148 | + * @param {String} string |
| 149 | + * @return {String} encoded for URI |
195 | 150 | */ |
196 | | - getHostPort: function() { |
197 | | - return this.host + cat( ':', this.port, '' ); |
198 | | - }, |
| 151 | + Uri.encode = function( s ) { |
| 152 | + return encodeURIComponent( s ) |
| 153 | + .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28') |
| 154 | + .replace( /\)/g, '%29').replace( /\*/g, '%2A') |
| 155 | + .replace( /%20/g, '+' ); |
| 156 | + }; |
199 | 157 | |
200 | 158 | /** |
201 | | - * Returns the userInfo and host and port portion of the URI. |
202 | | - * In most real-world URLs, this is simply the hostname, but it is more general. |
203 | | - * @return {String} |
| 159 | + * Standard decodeURIComponent, with '+' to space |
| 160 | + * @param {String} string encoded for URI |
| 161 | + * @return {String} decoded string |
204 | 162 | */ |
205 | | - getAuthority: function() { |
206 | | - return cat( '', this.getUserInfo(), '@' ) + this.getHostPort(); |
207 | | - }, |
| 163 | + Uri.decode = function( s ) { |
| 164 | + return decodeURIComponent( s ).replace( /\+/g, ' ' ); |
| 165 | + }; |
208 | 166 | |
209 | | - /** |
210 | | - * Returns the query arguments of the URL, encoded into a string |
211 | | - * Does not preserve the order of arguments passed into the URI. Does handle escaping. |
212 | | - * @return {String} |
213 | | - */ |
214 | | - getQueryString: function() { |
215 | | - var args = []; |
216 | | - $.each( this.query, function( key, val ) { |
217 | | - var k = mw.Uri.encode( key ); |
218 | | - var vals = val === null ? [ null ] : $.makeArray( val ); |
219 | | - $.each( vals, function( i, v ) { |
220 | | - args.push( k + ( v === null ? '' : '=' + mw.Uri.encode( v ) ) ); |
| 167 | + Uri.prototype = { |
| 168 | + |
| 169 | + /** |
| 170 | + * Parse a string and set our properties accordingly. |
| 171 | + * @param {String} URI |
| 172 | + * @param {Object} options |
| 173 | + * @return {Boolean} success |
| 174 | + */ |
| 175 | + _parse: function( str, options ) { |
| 176 | + var matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str ); |
| 177 | + var uri = this; |
| 178 | + $.each( properties, function( i, property ) { |
| 179 | + uri[ property ] = matches[ i+1 ]; |
221 | 180 | } ); |
222 | | - } ); |
223 | | - return args.join( '&' ); |
224 | | - }, |
225 | 181 | |
226 | | - /** |
227 | | - * Returns everything after the authority section of the URI |
228 | | - * @return {String} |
229 | | - */ |
230 | | - getRelativePath: function() { |
231 | | - return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' ); |
232 | | - }, |
| 182 | + // uri.query starts out as the query string; we will parse it into key-val pairs then make |
| 183 | + // that object the "query" property. |
| 184 | + // we overwrite query in uri way to make cloning easier, it can use the same list of properties. |
| 185 | + var q = {}; |
| 186 | + // using replace to iterate over a string |
| 187 | + if ( uri.query ) { |
| 188 | + uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { |
| 189 | + if ( $1 ) { |
| 190 | + var k = Uri.decode( $1 ); |
| 191 | + var v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 ); |
233 | 192 | |
234 | | - /** |
235 | | - * Gets the entire URI string. May not be precisely the same as input due to order of query arguments. |
236 | | - * @return {String} the URI string |
237 | | - */ |
238 | | - toString: function() { |
239 | | - return this.protocol + '://' + this.getAuthority() + this.getRelativePath(); |
240 | | - }, |
| 193 | + // If overrideKeys, always (re)set top level value. |
| 194 | + // If not overrideKeys but this key wasn't set before, then we set it as well. |
| 195 | + if ( options.overrideKeys || q[ k ] === undefined ) { |
| 196 | + q[ k ] = v; |
241 | 197 | |
242 | | - /** |
243 | | - * Clone this URI |
244 | | - * @return {Object} new URI object with same properties |
245 | | - */ |
246 | | - clone: function() { |
247 | | - return new mw.Uri( this ); |
248 | | - }, |
| 198 | + // Use arrays if overrideKeys is false and key was already seen before |
| 199 | + } else { |
| 200 | + // Once before, still a string, turn into an array |
| 201 | + if ( typeof q[ k ] === 'string' ) { |
| 202 | + q[ k ] = [ q[ k ] ]; |
| 203 | + } |
| 204 | + // Add to the array |
| 205 | + if ( $.isArray( q[ k ] ) ) { |
| 206 | + q[ k ].push( v ); |
| 207 | + } |
| 208 | + } |
| 209 | + } |
| 210 | + } ); |
| 211 | + } |
| 212 | + this.query = q; |
| 213 | + }, |
249 | 214 | |
250 | | - /** |
251 | | - * Extend the query -- supply query parameters to override or add to ours |
252 | | - * @param {Object} query parameters in key-val form to override or add |
253 | | - * @return {Object} this URI object |
254 | | - */ |
255 | | - extend: function( parameters ) { |
256 | | - $.extend( this.query, parameters ); |
257 | | - return this; |
258 | | - } |
| 215 | + /** |
| 216 | + * Returns user and password portion of a URI. |
| 217 | + * @return {String} |
| 218 | + */ |
| 219 | + getUserInfo: function() { |
| 220 | + return cat( '', this.user, cat( ':', this.password, '' ) ); |
| 221 | + }, |
| 222 | + |
| 223 | + /** |
| 224 | + * Gets host and port portion of a URI. |
| 225 | + * @return {String} |
| 226 | + */ |
| 227 | + getHostPort: function() { |
| 228 | + return this.host + cat( ':', this.port, '' ); |
| 229 | + }, |
| 230 | + |
| 231 | + /** |
| 232 | + * Returns the userInfo and host and port portion of the URI. |
| 233 | + * In most real-world URLs, this is simply the hostname, but it is more general. |
| 234 | + * @return {String} |
| 235 | + */ |
| 236 | + getAuthority: function() { |
| 237 | + return cat( '', this.getUserInfo(), '@' ) + this.getHostPort(); |
| 238 | + }, |
| 239 | + |
| 240 | + /** |
| 241 | + * Returns the query arguments of the URL, encoded into a string |
| 242 | + * Does not preserve the order of arguments passed into the URI. Does handle escaping. |
| 243 | + * @return {String} |
| 244 | + */ |
| 245 | + getQueryString: function() { |
| 246 | + var args = []; |
| 247 | + $.each( this.query, function( key, val ) { |
| 248 | + var k = Uri.encode( key ); |
| 249 | + var vals = val === null ? [ null ] : $.makeArray( val ); |
| 250 | + $.each( vals, function( i, v ) { |
| 251 | + args.push( k + ( v === null ? '' : '=' + Uri.encode( v ) ) ); |
| 252 | + } ); |
| 253 | + } ); |
| 254 | + return args.join( '&' ); |
| 255 | + }, |
| 256 | + |
| 257 | + /** |
| 258 | + * Returns everything after the authority section of the URI |
| 259 | + * @return {String} |
| 260 | + */ |
| 261 | + getRelativePath: function() { |
| 262 | + return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' ); |
| 263 | + }, |
| 264 | + |
| 265 | + /** |
| 266 | + * Gets the entire URI string. May not be precisely the same as input due to order of query arguments. |
| 267 | + * @return {String} the URI string |
| 268 | + */ |
| 269 | + toString: function() { |
| 270 | + return this.protocol + '://' + this.getAuthority() + this.getRelativePath(); |
| 271 | + }, |
| 272 | + |
| 273 | + /** |
| 274 | + * Clone this URI |
| 275 | + * @return {Object} new URI object with same properties |
| 276 | + */ |
| 277 | + clone: function() { |
| 278 | + return new Uri( this ); |
| 279 | + }, |
| 280 | + |
| 281 | + /** |
| 282 | + * Extend the query -- supply query parameters to override or add to ours |
| 283 | + * @param {Object} query parameters in key-val form to override or add |
| 284 | + * @return {Object} this URI object |
| 285 | + */ |
| 286 | + extend: function( parameters ) { |
| 287 | + $.extend( this.query, parameters ); |
| 288 | + return this; |
| 289 | + } |
| 290 | + }; |
| 291 | + |
| 292 | + var defaultProtocol = ( new Uri( documentLocation ) ).protocol; |
| 293 | + |
| 294 | + return Uri; |
259 | 295 | }; |
260 | 296 | |
261 | | -} )( jQuery ); |
| 297 | + // if we are running in a browser, inject the current document location, for relative URLs |
| 298 | + if ( document && document.location && document.location.href ) { |
| 299 | + mw.Uri = mw.UriRelative( document.location.href ); |
| 300 | + } |
| 301 | + |
| 302 | +} )( jQuery, mediaWiki ); |
Property changes on: branches/wmf/1.18wmf1/resources/mediawiki/mediawiki.Uri.js |
___________________________________________________________________ |
Modified: svn:mergeinfo |
262 | 303 | Merged /trunk/phase3/resources/mediawiki/mediawiki.Uri.js:r99444,99446,102616 |