      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