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