1 /* 2 Watermark plugin for jQuery 3 Version: 3.0.6 4 http://jquery-watermark.googlecode.com/ 5 6 Copyright (c) 2009-2010 Todd Northrop 7 http://www.speednet.biz/ 8 9 June 21, 2010 10 11 Requires: jQuery 1.2.3+ 12 13 Dual licensed under the MIT or GPL Version 2 licenses. 14 See mit-license.txt and gpl2-license.txt in the project root for details. 15 ------------------------------------------------------*/ 16 17 (function ($) { 18 19 var 20 // Will speed up references to undefined 21 undefined, 22 23 // String constants for data names 24 dataFlag = "watermark", 25 dataClass = "watermarkClass", 26 dataFocus = "watermarkFocus", 27 dataFormSubmit = "watermarkSubmit", 28 dataMaxLen = "watermarkMaxLength", 29 dataPassword = "watermarkPassword", 30 dataText = "watermarkText", 31 32 // Includes only elements with watermark defined 33 selWatermarkDefined = ":data(" + dataFlag + ")", 34 35 // Includes only elements capable of having watermark 36 selWatermarkAble = ":text,:password,:search,textarea", 37 38 // triggerFns: 39 // Array of function names to look for in the global namespace. 40 // Any such functions found will be hijacked to trigger a call to 41 // hideAll() any time they are called. The default value is the 42 // ASP.NET function that validates the controls on the page 43 // prior to a postback. 44 // 45 // Am I missing other important trigger function(s) to look for? 46 // Please leave me feedback: 47 // http://code.google.com/p/jquery-watermark/issues/list 48 triggerFns = [ 49 "Page_ClientValidate" 50 ], 51 52 // Holds a value of true if a watermark was displayed since the last 53 // hideAll() was executed. Avoids repeatedly calling hideAll(). 54 pageDirty = false; 55 56 // Extends jQuery with a custom selector - ":data(...)" 57 // :data(<name>) Includes elements that have a specific name defined in the jQuery data collection. (Only the existence of the name is checked; the value is ignored.) 58 // :data(<name>=<value>) Includes elements that have a specific jQuery data name defined, with a specific value associated with it. 59 // :data(<name>!=<value>) Includes elements that have a specific jQuery data name defined, with a value that is not equal to the value specified. 60 // :data(<name>^=<value>) Includes elements that have a specific jQuery data name defined, with a value that starts with the value specified. 61 // :data(<name>$=<value>) Includes elements that have a specific jQuery data name defined, with a value that ends with the value specified. 62 // :data(<name>*=<value>) Includes elements that have a specific jQuery data name defined, with a value that contains the value specified. 63 $.extend($.expr[":"], { 64 "search":function (elem) { 65 return "search" === (elem.type || ""); 66 }, 67 68 "data":function (element, index, matches, set) { 69 var data, parts = /^((?:[^=!^$*]|[!^$*](?!=))+)(?:([!^$*]?=)(.*))?$/.exec(matches[3]); 70 71 if (parts) { 72 data = $(element).data(parts[1]); 73 74 if (data !== undefined) { 75 76 if (parts[2]) { 77 data = "" + data; 78 79 switch (parts[2]) { 80 case "=": 81 return (data == parts[3]); 82 case "!=": 83 return (data != parts[3]); 84 case "^=": 85 return (data.slice(0, parts[3].length) == parts[3]); 86 case "$=": 87 return (data.slice(-parts[3].length) == parts[3]); 88 case "*=": 89 return (data.indexOf(parts[3]) !== -1); 90 } 91 } 92 93 return true; 94 } 95 } 96 97 return false; 98 } 99 }); 100 101 $.watermark = { 102 103 // Current version number of the plugin 104 version:"3.0.6", 105 106 // Default options used when watermarks are instantiated. 107 // Can be changed to affect the default behavior for all 108 // new or updated watermarks. 109 // BREAKING CHANGE: The $.watermark.className 110 // property that was present prior to version 3.0.2 must 111 // be changed to $.watermark.options.className 112 options:{ 113 114 // Default class name for all watermarks 115 className:"watermark", 116 117 // If true, plugin will detect and use native browser support for 118 // watermarks, if available. (e.g., WebKit's placeholder attribute.) 119 useNative:true 120 }, 121 122 // Hide one or more watermarks by specifying any selector type 123 // i.e., DOM element, string selector, jQuery matched set, etc. 124 hide:function (selector) { 125 $(selector).filter(selWatermarkDefined).each( 126 function () { 127 $.watermark._hide($(this)); 128 } 129 ); 130 }, 131 132 // Internal use only. 133 _hide:function ($input, focus) { 134 var inputVal = $input.val() || "", 135 inputWm = $input.data(dataText) || "", 136 maxLen = $input.data(dataMaxLen) || 0, 137 className = $input.data(dataClass); 138 139 if ((inputWm.length) && (inputVal == inputWm)) { 140 $input.val(""); 141 142 // Password type? 143 if ($input.data(dataPassword)) { 144 145 if (($input.attr("type") || "") === "text") { 146 var $pwd = $input.data(dataPassword) || [], 147 $wrap = $input.parent() || []; 148 149 if (($pwd.length) && ($wrap.length)) { 150 $wrap[0].removeChild($input[0]); // Can't use jQuery methods, because they destroy data 151 $wrap[0].appendChild($pwd[0]); 152 $input = $pwd; 153 } 154 } 155 } 156 157 if (maxLen) { 158 $input.attr("maxLength", maxLen); 159 $input.removeData(dataMaxLen); 160 } 161 162 if (focus) { 163 $input.attr("autocomplete", "off"); // Avoid NS_ERROR_XPC_JS_THREW_STRING error in Firefox 164 165 window.setTimeout( 166 function () { 167 $input.select(); // Fix missing cursor in IE 168 } 169 , 1); 170 } 171 } 172 173 className && $input.removeClass(className); 174 }, 175 176 // Display one or more watermarks by specifying any selector type 177 // i.e., DOM element, string selector, jQuery matched set, etc. 178 // If conditions are not right for displaying a watermark, ensures that watermark is not shown. 179 show:function (selector) { 180 $(selector).filter(selWatermarkDefined).each( 181 function () { 182 $.watermark._show($(this)); 183 } 184 ); 185 }, 186 187 // Internal use only. 188 _show:function ($input) { 189 var val = $input.val() || "", 190 text = $input.data(dataText) || "", 191 type = $input.attr("type") || "", 192 className = $input.data(dataClass); 193 194 if (((val.length == 0) || (val == text)) && (!$input.data(dataFocus))) { 195 pageDirty = true; 196 197 // Password type? 198 if ($input.data(dataPassword)) { 199 200 if (type === "password") { 201 var $pwd = $input.data(dataPassword) || [], 202 $wrap = $input.parent() || []; 203 204 if (($pwd.length) && ($wrap.length)) { 205 $wrap[0].removeChild($input[0]); // Can't use jQuery methods, because they destroy data 206 $wrap[0].appendChild($pwd[0]); 207 $input = $pwd; 208 $input.attr("maxLength", text.length); 209 } 210 } 211 } 212 213 // Ensure maxLength big enough to hold watermark (input of type="text" or type="search" only) 214 if ((type === "text") || (type === "search")) { 215 var maxLen = $input.attr("maxLength") || 0; 216 217 if ((maxLen > 0) && (text.length > maxLen)) { 218 $input.data(dataMaxLen, maxLen); 219 $input.attr("maxLength", text.length); 220 } 221 } 222 223 className && $input.addClass(className); 224 $input.val(text); 225 } 226 else { 227 $.watermark._hide($input); 228 } 229 }, 230 231 // Hides all watermarks on the current page. 232 hideAll:function () { 233 if (pageDirty) { 234 $.watermark.hide(selWatermarkAble); 235 pageDirty = false; 236 } 237 }, 238 239 // Displays all watermarks on the current page. 240 showAll:function () { 241 $.watermark.show(selWatermarkAble); 242 } 243 }; 244 245 $.fn.watermark = function (text, options) { 246 /// <summary> 247 /// Set watermark text and class name on all input elements of type="text/password/search" and 248 /// textareas within the matched set. If className is not specified in options, the default is 249 /// "watermark". Within the matched set, only input elements with type="text/password/search" 250 /// and textareas are affected; all other elements are ignored. 251 /// </summary> 252 /// <returns type="jQuery"> 253 /// Returns the original jQuery matched set (not just the input and texarea elements). 254 /// </returns> 255 /// <param name="text" type="String"> 256 /// Text to display as a watermark when the input or textarea element has an empty value and does not 257 /// have focus. The first time watermark() is called on an element, if this argument is empty (or not 258 /// a String type), then the watermark will have the net effect of only changing the class name when 259 /// the input or textarea element's value is empty and it does not have focus. 260 /// </param> 261 /// <param name="options" type="Object" optional="true"> 262 /// Provides the ability to override the default watermark options ($.watermark.options). For backward 263 /// compatibility, if a string value is supplied, it is used as the class name that overrides the class 264 /// name in $.watermark.options.className. Properties include: 265 /// className: When the watermark is visible, the element will be styled using this class name. 266 /// useNative (Boolean or Function): Specifies if native browser support for watermarks will supersede 267 /// plugin functionality. If useNative is a function, the return value from the function will 268 /// determine if native support is used. The function is passed one argument -- a jQuery object 269 /// containing the element being tested as the only element in its matched set -- and the DOM 270 /// element being tested is the object on which the function is invoked (the value of "this"). 271 /// </param> 272 /// <remarks> 273 /// The effect of changing the text and class name on an input element is called a watermark because 274 /// typically light gray text is used to provide a hint as to what type of input is required. However, 275 /// the appearance of the watermark can be something completely different: simply change the CSS style 276 /// pertaining to the supplied class name. 277 /// 278 /// The first time watermark() is called on an element, the watermark text and class name are initialized, 279 /// and the focus and blur events are hooked in order to control the display of the watermark. Also, as 280 /// of version 3.0, drag and drop events are hooked to guard against dropped text being appended to the 281 /// watermark. If native watermark support is provided by the browser, it is detected and used, unless 282 /// the useNative option is set to false. 283 /// 284 /// Subsequently, watermark() can be called again on an element in order to change the watermark text 285 /// and/or class name, and it can also be called without any arguments in order to refresh the display. 286 /// 287 /// For example, after changing the value of the input or textarea element programmatically, watermark() 288 /// should be called without any arguments to refresh the display, because the change event is only 289 /// triggered by user actions, not by programmatic changes to an input or textarea element's value. 290 /// 291 /// The one exception to programmatic updates is for password input elements: you are strongly cautioned 292 /// against changing the value of a password input element programmatically (after the page loads). 293 /// The reason is that some fairly hairy code is required behind the scenes to make the watermarks bypass 294 /// IE security and switch back and forth between clear text (for watermarks) and obscured text (for 295 /// passwords). It is *possible* to make programmatic changes, but it must be done in a certain way, and 296 /// overall it is not recommended. 297 /// </remarks> 298 299 if (!this.length) { 300 return this; 301 } 302 303 var hasClass = false, 304 hasText = (typeof(text) === "string"); 305 306 if (typeof(options) === "object") { 307 hasClass = (typeof(options.className) === "string"); 308 options = $.extend({}, $.watermark.options, options); 309 } 310 else if (typeof(options) === "string") { 311 hasClass = true; 312 options = $.extend({}, $.watermark.options, {className:options}); 313 } 314 else { 315 options = $.watermark.options; 316 } 317 318 if (typeof(options.useNative) !== "function") { 319 options.useNative = options.useNative ? function () { 320 return true; 321 } : function () { 322 return false; 323 }; 324 } 325 326 return this.each( 327 function () { 328 var $input = $(this); 329 330 if (!$input.is(selWatermarkAble)) { 331 return; 332 } 333 334 // Watermark already initialized? 335 if ($input.data(dataFlag)) { 336 337 // If re-defining text or class, first remove existing watermark, then make changes 338 if (hasText || hasClass) { 339 $.watermark._hide($input); 340 341 if (hasText) { 342 $input.data(dataText, text); 343 } 344 345 if (hasClass) { 346 $input.data(dataClass, options.className); 347 } 348 } 349 } 350 else { 351 352 // Detect and use native browser support, if enabled in options 353 if (options.useNative.call(this, $input)) { 354 355 // Placeholder attribute (WebKit) 356 // Big thanks to Opera for the wacky test required 357 358 if ((("" + $input.css("-webkit-appearance")).replace("undefined", "") !== "") && ((($input.attr("tagName") || "") !== "TEXTAREA")) && $input.size() > 0 && $input[0].tagName !== "TEXTAREA") { 359 360 // className is not set because WebKit doesn't appear to have 361 // a separate class name property for placeholders (watermarks). 362 if (hasText) { 363 $input.attr("placeholder", text); 364 } 365 366 // Only set data flag for non-native watermarks (purposely commented-out) 367 // $input.data(dataFlag, 1); 368 return; 369 } 370 } 371 372 $input.data(dataText, hasText ? text : ""); 373 $input.data(dataClass, options.className); 374 $input.data(dataFlag, 1); // Flag indicates watermark was initialized 375 376 // Special processing for password type 377 if (($input.attr("type") || "") === "password") { 378 var $wrap = $input.wrap("<span>").parent(), 379 $wm = $($wrap.html().replace(/type=["']?password["']?/i, 'type="text"')); 380 381 $wm.data(dataText, $input.data(dataText)); 382 $wm.data(dataClass, $input.data(dataClass)); 383 $wm.data(dataFlag, 1); 384 $wm.attr("maxLength", text.length); 385 386 $wm.focus( 387 function () { 388 $.watermark._hide($wm, true); 389 } 390 ).bind("dragenter", 391 function () { 392 $.watermark._hide($wm); 393 } 394 ).bind("dragend", 395 function () { 396 window.setTimeout(function () { 397 $wm.blur(); 398 }, 1); 399 } 400 ); 401 $input.blur( 402 function () { 403 $.watermark._show($input); 404 } 405 ).bind("dragleave", 406 function () { 407 $.watermark._show($input); 408 } 409 ); 410 411 $wm.data(dataPassword, $input); 412 $input.data(dataPassword, $wm); 413 } 414 else { 415 416 $input.focus( 417 function () { 418 $input.data(dataFocus, 1); 419 $.watermark._hide($input, true); 420 } 421 ).blur( 422 function () { 423 $input.data(dataFocus, 0); 424 $.watermark._show($input); 425 } 426 ).bind("dragenter", 427 function () { 428 $.watermark._hide($input); 429 } 430 ).bind("dragleave", 431 function () { 432 $.watermark._show($input); 433 } 434 ).bind("dragend", 435 function () { 436 window.setTimeout(function () { 437 $.watermark._show($input); 438 }, 1); 439 } 440 ).bind("drop", 441 // Firefox makes this lovely function necessary because the dropped text 442 // is merged with the watermark before the drop event is called. 443 function (evt) { 444 var dropText = evt.originalEvent.dataTransfer.getData("Text"); 445 446 if ($input.val().replace(dropText, "") === $input.data(dataText)) { 447 $input.val(dropText); 448 } 449 450 $input.focus(); 451 } 452 ); 453 } 454 455 // In order to reliably clear all watermarks before form submission, 456 // we need to replace the form's submit function with our own 457 // function. Otherwise watermarks won't be cleared when the form 458 // is submitted programmatically. 459 if (this.form) { 460 var form = this.form, 461 $form = $(form); 462 463 if (!$form.data(dataFormSubmit)) { 464 $form.submit($.watermark.hideAll); 465 466 // form.submit exists for all browsers except Google Chrome 467 // (see "else" below for explanation) 468 if (form.submit) { 469 $form.data(dataFormSubmit, form.onsubmit || 1); 470 471 form.onsubmit = (function (f, $f) { 472 return function () { 473 var nativeSubmit = $f.data(dataFormSubmit); 474 $.watermark.hideAll(); 475 if (nativeSubmit instanceof Function) { 476 nativeSubmit(); 477 } else { 478 eval(nativeSubmit); 479 } 480 }; 481 })(form, $form); 482 } else { 483 $form.data(dataFormSubmit, 1); 484 485 // This strangeness is due to the fact that Google Chrome's 486 // form.submit function is not visible to JavaScript (identifies 487 // as "undefined"). I had to invent a solution here because hours 488 // of Googling (ironically) for an answer did not turn up anything 489 // useful. Within my own form.submit function I delete the form's 490 // submit function, and then call the non-existent function -- 491 // which, in the world of Google Chrome, still exists. 492 form.submit = (function (f) { 493 return function () { 494 $.watermark.hideAll(); 495 delete f.submit; 496 f.submit(); 497 }; 498 })(form); 499 } 500 } 501 } 502 } 503 504 $.watermark._show($input); 505 } 506 ); 507 }; 508 509 // Hijack any functions found in the triggerFns list 510 if (triggerFns.length) { 511 512 // Wait until DOM is ready before searching 513 $(function () { 514 var i, name, fn; 515 516 for (i = triggerFns.length - 1; i >= 0; i--) { 517 name = triggerFns[i]; 518 fn = window[name]; 519 520 if (typeof(fn) === "function") { 521 window[name] = (function (origFn) { 522 return function () { 523 $.watermark.hideAll(); 524 return origFn.apply(null, Array.prototype.slice.call(arguments)); 525 }; 526 })(fn); 527 } 528 } 529 }); 530 } 531 532 })(jQuery); 533