1(function($, undefined) { 2 /* TODO FIXME fix concept of URI to support things like mailto */ 3 "use strict"; 4 var defaults = { 5 url: window.location.href, 6 isFile: false, 7 checkIfFileFunction: null, 8 element: null, 9 schemeOnly: false, 10 hostOnly: false, 11 pathOnly: false, 12 fileOnly: false, 13 queryOnly: false, 14 hashOnly: false, 15 excludeScheme: false, 16 excludeHost: false, 17 excludePath: false, 18 excludeUsername: false, 19 excludePassword: false, 20 excludeFile: false, 21 excludeQuery: false, 22 excludeHash: false, 23 inferConfigFromElement: false, 24 bindToCurrentAddress: false, 25 pathPrefix: "//", 26 autocommit: false 27 }; 28 29 if (typeof Object.create !== 'function') { 30 Object.create = function (o) { 31 function F() { } 32 F.prototype = o; 33 return new F(); 34 }; 35 } 36 37 UrlArray.prototype = Object.create(Array.prototype); 38 39 function UrlArray() { 40 if ($.undefined(UrlArray.prototype[UrlPlugin.prototype[0]])) { 41 var names = UrlPlugin.prototype; 42 for (var name in names) { 43 (function(localName) { 44 UrlArray.prototype[localName] = function() { 45 var ret = (function(arr, funcName, args) { 46 var ret = new UrlArray(); 47 for (var i = 0; i < arr.length; i++) { 48 (function(urlElement, funcName, args) { 49 /* Call the appropriate function on all URL objects */ 50 ret.push(urlElement[funcName].apply(urlElement, args)); 51 })(arr[i], funcName, args); 52 } 53 return ret; 54 })(this, localName, arguments); 55 56 return ret; 57 }; 58 })(name); 59 } 60 } 61 } 62 63 function AddressPlugin(options) { 64 this.options = options; 65 this.init(); 66 } 67 68 AddressPlugin.prototype = { 69 init: function() { }, 70 71 redirect: function(url) { 72 if (url instanceof UrlPlugin) url = url.toString(); 73 window.location.href = url; 74 }, 75 76 hash: function(hash) { 77 if (hash instanceof UrlPlugin) hash = hash.hash(); 78 window.location.hash = hash; 79 }, 80 81 update: function(url) { 82 if (url instanceof UrlPlugin) url = url.toString(); 83 window.history.replaceState(null, document.title, url); 84 }, 85 86 current: function(autocommit) { 87 if ($.undefined(autocommit) || autocommit !== true || autocommit !== false) autocommit = false; 88 return $.url({bindToCurrentAddress: true, autocommit: autocommit}); 89 } 90 }; 91 92 function UrlPlugin(options, elem) { 93 if (typeof options === "string") { 94 options = {url: options}; 95 } 96 97 var queriedElementAndFailed = false; 98 if ($.defined(elem)) { 99 if ($.undefined(options)) options = {}; 100 101 options.element = elem; 102 if ($.undefined(options.inferConfigFromElement)) { 103 options.inferConfigFromElement = true; 104 } 105 if ($.undefined(options.url) || options.url == null || options.url === "") { 106 var queryElement = function(elem) { 107 var tagName = $.prop(elem, "tagName").toLowerCase(); 108 var url = null; 109 if (["a", "link", "area"].indexOf(tagName) != -1) url = $.attr(elem, "href"); 110 else if (["img", "script", "frame", "iframe", "input"].indexOf(tagName) != -1) url = $.attr(elem, "src"); 111 else if (["form"].indexOf(tagName) != -1) url = $.attr(elem, "action"); 112 return url; 113 }; 114 options.url = queryElement(elem); 115 queriedElementAndFailed = options.url == null; 116 } 117 } 118 119 this._options = $.extend({}, defaults, options); 120 if (queriedElementAndFailed) this._options.url = null; 121 122 this._query = {}; 123 this._hash = null; 124 this._host = null; 125 this._port = null; 126 this._path = null; 127 this._scheme = null; 128 this._file = null; 129 this._extension = null; 130 this._relativeScheme = false; 131 this.init(); 132 } 133 134 UrlPlugin.prototype = (function() { 135 var getScheme = function() { return window.location.protocol.substring(0, window.location.protocol.length - 1); }; 136 var pruneUrl = function(url) { return url.split("#").splice(0, 1).join("").split("?").splice(0, 1).join(""); }; 137 var surroundUrl = function(url, endsWithSlash) { 138 if ($.undefined(endsWithSlash)) endsWithSlash = false; 139 if (url.charAt(0) !== "/") url = "/" + url; 140 if (!endsWithSlash && url.length > 1 && url.charAt(url.length - 1) !== "/") url += "/"; 141 return url; 142 }; 143 var parseUser = function(username) { 144 var ret = [null, null]; 145 var idx = username.indexOf(":"); 146 if (idx != -1) { 147 ret[0] = username.substring(0, idx); 148 ret[1] = username.substring(idx + 1, username.length); 149 } 150 else ret[0] = username; 151 152 return ret; 153 }; 154 var parseHost = function(fullHost) { 155 var ret = [null, null, null, null]; 156 var idx = null; 157 var usernameIdx = fullHost.indexOf("@"); 158 if (usernameIdx != -1) { 159 var userpass = parseUser(fullHost.substring(0, usernameIdx)); 160 fullHost = fullHost.substring(usernameIdx + 1, fullHost.length); 161 ret[2] = userpass[0]; 162 ret[3] = userpass[1]; 163 } 164 idx = fullHost.indexOf(":"); 165 if (idx != -1) { 166 ret[0] = fullHost.substring(0, idx); 167 ret[1] = fullHost.substring(idx + 1, fullHost.length); 168 } 169 else ret[0] = fullHost; 170 171 return ret; 172 }; 173 var parseFile = function(file) { 174 var ret = [null, null]; 175 176 var idx = file.indexOf("."); 177 if (idx != -1) { 178 ret[0] = file.substring(0, idx); 179 ret[1] = file.substring(idx + 1, file.length); 180 } 181 else ret[0] = file; 182 183 return ret; 184 }; 185 var parsePath = function(fullPath) { 186 var ret = [null, null, null]; 187 188 var parts = fullPath.split("/"); 189 var lastPart = parts[parts.length - 1]; 190 191 /* Trailing slashes have no file/extension */ 192 if (lastPart === "" || lastPart === "/") ret[0] = fullPath; 193 else { 194 var fileParsed = parseFile(lastPart); 195 196 ret[0] = surroundUrl(parts.slice(0, -1).join("/")); 197 ret[1] = fileParsed[0]; 198 ret[2] = fileParsed[1]; 199 } 200 201 return ret; 202 }; 203 var updateElement = function(elem, url) { 204 if ($.undefined(elem) || elem == null) return; 205 206 var tagName = $.prop(elem, "tagName").toLowerCase(); 207 if (["a", "link", "area"].indexOf(tagName) != -1) $.attr(elem, "href", url.toString()); 208 else if (["img", "script", "frame", "iframe", "input"].indexOf(tagName) != -1) $.attr(elem, "src", url.toString()); 209 else if (["form"].indexOf(tagName) != -1) $.attr(elem, "action", url.toString()); 210 }; 211 var updateAddress = function(url, bindToCurrentAddress) { 212 if (!bindToCurrentAddress) return; 213 $.address.update(url.toString()); 214 } 215 216 return { 217 init: function() { 218 this._firstLoad = true; 219 this.reset(); 220 this._firstLoad = false; 221 }, 222 223 query: function(arg, val, keepBlank) { 224 if ($.undefined(arg)) return this._query; 225 if ($.undefined(val)) { 226 if (arg instanceof Array) { 227 var ret = []; 228 for (var key in arg) {
229 ret.push(this.query(arg[key])); 230 } 231 return ret; 232 } 233 234 if (arg == null) { 235 this._query = {}; 236 if (this._options.autocommit) this.commit(); 237 return this; 238 } 239 240 return this._query[arg]; 241 } 242 243 if (arg instanceof Array && val instanceof Array) { 244 for (var i = 0; i < arg.length; i++) { 245 this.query(arg[i], val[i], keepBlank); 246 } 247 } 248 else if (arg instanceof Array) { 249 for (var key in arg) { 250 this.query(arg[key], val, keepBlank); 251 } 252 } 253 else if (val instanceof Array) this._query[arg] = val.join(); 254 else if (val == null && !keepBlank) delete this._query[arg]; 255 else this._query[arg] = val; 256 257 if (this._options.autocommit) this.commit(); 258 return this; 259 }, 260 261 username: function(val) { 262 if ($.undefined(val)) return this._username; 263 var parsed = parseUser(val); 264 this._username = parsed[0]; 265 this._password = parsed[1]; 266 if (this._options.autocommit) this.commit(); 267 return this; 268 }, 269 270 password: function(val) { 271 if ($.undefined(val)) return this._password; 272 this._password = val; 273 if (this._options.autocommit) this.commit(); 274 return this; 275 }, 276 277 host: function(val) { 278 if ($.undefined(val)) return this._host; 279 var parsed = parseHost(val); 280 this._host = parsed[0]; 281 this._port = parsed[1]; 282 this._username = parsed[2]; 283 this._password = parsed[3]; 284 if (this._options.autocommit) this.commit(); 285 return this; 286 }, 287 288 hash: function(val) { 289 if ($.undefined(val)) return this._hash; 290 this._hash = val; 291 if (this._options.autocommit) this.commit(); 292 return this; 293 }, 294 295 fragment: function(val) { 296 return this.hash(val); 297 }, 298 299 path: function(val) { 300 if ($.undefined(val)) return this._path; 301 if (val == null) val = "/"; 302 303 var pathParsed = parsePath(val); 304 this._path = pathParsed[0]; 305 if (pathParsed[1] != null) { 306 this._file = pathParsed[1]; 307 this._extension = pathParsed[2]; 308 } 309 310 if (this._options.autocommit) this.commit(); 311 return this; 312 }, 313 314 segments: function(arg) { 315 if ($.undefined(arg)) { 316 if (this._path == null) return []; 317 return this._path.split("/").slice(1, -1); 318 } 319 this.path("/"); 320 this.appendSegment(arg); 321 return this; 322 }, 323 324 removeSegment: function(idx) { 325 if ($.undefined(idx)) return this; 326 327 var segments = this.segments(); 328 if (idx instanceof Array) { 329 for (var i = idx.length - 1; i > -1; i--) { 330 segments.splice(idx[i], 1); 331 } 332 } 333 else { 334 segments.splice(idx, 1); 335 } 336 337 this.path("/"); 338 this.appendSegment(segments); 339 return this; 340 }, 341 342 insertSegment: function(val, idx) { 343 if ($.undefined(val)) return this; 344 if ($.undefined(idx)) idx = 0; 345 var segments = this.segments(); 346 347 if (typeof val === "string") { 348 if (idx instanceof Array) { 349 for (var i = idx.length - 1; i > -1; i--) { 350 segments.splice(idx[i], 0, val); 351 } 352 } 353 else segments.splice(idx, 0, val); 354 } 355 else if (val instanceof Array) { 356 for (var i = val.length - 1; i > -1; i--) { 357 var insertIndex = (idx instanceof Array ? idx[i] : idx); 358 segments.splice(insertIndex, 0, val[i]); 359 } 360 } 361 362 this.path("/"); 363 this.appendSegment(segments); 364 return this; 365 }, 366 367 appendSegment: function(val) { 368 if ($.undefined(val) || val == null) return this; 369 370 if (typeof val === "string") { 371 if (val.charAt(0) === "/") val = val.substring(1); 372 373 val = this.path() + val; 374 375 var pathParsed = parsePath(val); 376 this._path = pathParsed[0]; 377 if (pathParsed[1] != null) { 378 this._file = pathParsed[1]; 379 this._extension = pathParsed[2]; 380 } 381 } 382 else if (val instanceof Array) { 383 for (var key in val) { 384 var pathSegment = val[key]; 385 if (pathSegment.indexOf(".") == -1) pathSegment += "/" 386 this.appendSegment(pathSegment); 387 } 388 } 389 390 if (this._options.autocommit) this.commit(); 391 return this; 392 }, 393 394 port: function(val) { 395 if ($.undefined(val)) return this._port; 396 this._port = val; 397 if (this._options.autocommit) this.commit(); 398 return this; 399 }, 400 401 file: function(val) { 402 if ($.undefined(val)) return this._file; 403 var fileParsed = parseFile(val); 404 this._file = fileParsed[0]; 405 this._extension = fileParsed[1]; 406 if (this._options.autocommit) this.commit(); 407 return this; 408 }, 409 410 extension: function(val) { 411 if ($.undefined(val)) return this._extension; 412 this._extension = val; 413 if (this._options.autocommit) this.commit(); 414 return this; 415 }, 416 417 scheme: function(val) { 418 if ($.undefined(val)) return this._scheme; 419 this._scheme = val; 420 if (this._options.autocommit) this.commit(); 421 return this; 422 }, 423 424 protocol: function(val) { 425 return this.scheme(val); 426 }, 427 428 toString: function() { 429 var url = ""; 430 var anyOnly = this._options.schemeOnly || this._options.hostOnly || this._options.pathOnly || this._options.fileOnly || this._options.queryOnly || this._options.hashOnly; 431
432 if (!this._options.excludeScheme && this._scheme != null && (!anyOnly || this._options.schemeOnly)) url += this._scheme + ":" + this._options.pathPrefix; 433 if (!this._options.excludeHost && (!anyOnly || this._options.hostOnly)) { 434 if (this._host != null) { 435 if (!this._options.excludeUsername) { 436 if (this._username != null) url += this._username; 437 if (!this._options.excludePassword) { 438 if (this._password != null) url += ":" + this._password; 439 } 440 if (this._username != null) url += "@"; 441 } 442 url += this._host; 443 } 444 if (this._port != null) url += ":" + this._port; 445 } 446 if (!this._options.excludePath && this._path != null && (!anyOnly || this._options.pathOnly)) url += this._path; 447 if (!this._options.excludeFile && (!anyOnly || this._options.fileOnly)) { 448 if (this._file != null) url += this._file; 449 if (this._extension != null) url += "." + this._extension; 450 } 451 if (!this._options.excludeQuery && (!anyOnly || this._options.queryOnly)) { 452 var newQs = ""; 453 var encode = function (s) { return encodeURIComponent(s); }; 454 455 for (var key in this._query) { 456 if (newQs !== "") newQs += "&"; 457 else newQs += "?"; 458 459 var val = this._query[key]; 460 newQs += encode(key); 461 if ($.defined(val) && val != null) newQs += "=" + encode(val); 462 } 463 url += newQs; 464 } 465 if (!this._options.excludeHash && this._hash != null && (!anyOnly || this._options.hashOnly)) url += "#" + this._hash; 466 return url; 467 }, 468 469 originalUrl: function() { 470 return this._options.url; 471 }, 472 473 commit: function() { 474 updateElement(this._options.element, this); 475 updateAddress(this, this._options.bindToCurrentAddress); 476 return this; 477 }, 478 479 reset: function(newOriginalUrl) { 480 if ($.defined(newOriginalUrl)) this._options.url = newOriginalUrl; 481 var url = this._options.url; 482 var qs = null; 483 var infer = this._options.inferConfigFromElement && this._options.element != null; 484 485 if (url == null) return; 486 487 /* Not a URL at all */ 488 if (url.indexOf("javascript:") != -1) { 489 if (infer) { 490 this._file = url; 491 this._options.fileOnly = true; 492 } 493 return; 494 } 495 496 /* Special case... */ 497 if (url.indexOf("#") === 0) { 498 this._host = window.location.host; 499 this._scheme = getScheme(); 500 this._path = window.location.pathname; 501 this._hash = url.substring(1); 502 qs = window.location.search; 503 if (infer) this._options.hashOnly = true; 504 } 505 else { 506 var pattern = RegExp("^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); 507 var matches = url.match(pattern); 508 509 if ($.defined(matches[5])) this._hash = matches[5].substring(1); 510 if ($.defined(matches[1]) && matches[1].match(/\d+/) == null) this._scheme = matches[1]; 511 if ($.defined(matches[4])) qs = matches[4]; 512 if ($.defined(matches[3])) this._path = matches[3]; 513 else this._path = "/"; 514 515 if ($.defined(matches[2])) { 516 this._host = matches[2]; 517 if (url.indexOf("//") == 0) { 518 this._scheme = getScheme(); 519 this._relativeScheme = true; 520 } 521 } 522 else if ($.defined(matches[1])) { 523 /* We have no path and we do have a specified scheme, meaning it's probably a simpler URI such as mailto */ 524 this._host = this._path; 525 this._path = null; 526 } 527 else { 528 /* If the URL starts with a slash, it may be a relative URL or a protocol-specific absolute URL */ 529 var parts = pruneUrl(url).split("/"); 530 var endsWithSlash = url.charAt(url.length - 1) !== "/"; 531 var firstPart = parts[0]; 532 533 if (firstPart === "" || firstPart === "/") { 534 firstPart = parts[1]; 535 if (firstPart == "" || firstPart === "/") { 536 /* We have a relative scheme absolute URL */ 537 this._scheme = getScheme(); 538 this._host = parts.splice(2, 3).join(""); 539 this._path = parts.splice(3).join("/"); 540 this._relativeScheme = true; 541 } 542 else { 543 /* Just a relative URL */ 544 this._host = window.location.host; 545 this._scheme = getScheme(); 546 this._path = "/" + parts.splice(1).join("/");
547 if (infer) this._options.excludeScheme = true; 548 if (infer) this._options.excludeHost = true; 549 } 550 } 551 else { 552 /* 553 Might be a file in the current directory (e.g. a href="default.htm") or a host (e.g. google.com) 554 By default, we assume it"s a host because it"s really hard to disambiguate between files and hosts 555 The user is provided two ways to define that it is a file, one is direct 556 The other is defining a function to check and see if the last segment is a file 557 e.g. $("a").url({checkIfFileFunction: function(f) { return f.IndexOf(".com") == -1; }); 558 */ 559 560 var isFile = this._options.isFile || (this._options.checkIfFileFunction != null && this._options.checkIfFileFunction(firstPart)); 561 this._host = (isFile ? window.location.host : firstPart); 562 this._file = (isFile ? firstPart : null); 563 this._path = parts.slice(1).join("/"); 564 this._scheme = getScheme(); 565 this._path = surroundUrl(this._path, endsWithSlash); 566 if (infer) this._options.excludeScheme = true; 567 } 568 } 569 } 570 571 if (this._host !== null) { 572 var hostParsed = parseHost(this._host); 573 if (hostParsed[1] != null) { 574 this._host = hostParsed[0]; 575 this._port = hostParsed[1]; 576 this._username = hostParsed[2]; 577 this._password = hostParsed[3]; 578 } 579 } 580 else if (infer) this._options.excludeHost = true; 581 582 if (this._path !== null && this._path !== "/" && this._path !== "") { 583 var pathParsed = parsePath(this._path); 584 this._path = pathParsed[0]; 585 this._file = pathParsed[1]; 586 this._extension = pathParsed[2]; 587 } 588 else { 589 if (infer) this._options.excludePath = true; 590 this._path = "/"; 591 } 592 593 if (qs != null) { 594 var match; 595 var pl = /\+/g; 596 var search = /([^&=]+)=?([^&]*)/g; 597 var decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }; 598 var query = qs.substring(1); 599 while (match = search.exec(query)) { 600 if (match != null) { 601 this._query[decode(match[1])] = decode(match[2]); 602 } 603 } 604 } 605 606 if (!this._firstLoad) this.commit(); 607 return this; 608 } 609 }; 610 })(); 611 612 $.extend({ 613 defined: function(obj) { return typeof obj !== "undefined"; }, 614 undefined: function(obj) { return typeof obj === "undefined"; }, 615 url: function(url) { return new UrlPlugin(url); }, 616 buildUrl: function() { return new UrlPlugin({url: null}); }, 617 address: new AddressPlugin(), 618 }); 619 620 $.fn.url = function(options) { 621 var collection = this; 622 var ret = new UrlArray(); 623 collection.each(function() { 624 if (!$.data(this, "url-plugin") || $.defined(options)) { 625 (function(elem, localOptions) { 626 $.data(elem, "url-plugin", new UrlPlugin(localOptions, elem)); 627 })(this, options); 628 } 629 ret.push($.data(this, "url-plugin")); 630 }); 631 632 return ret; 633 }; 634})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.