From e3216606a5627e5666d1e45d7cbfe809ed37d641 Mon Sep 17 00:00:00 2001 From: Alex Ivanov Date: Fri, 29 Nov 2013 00:50:46 +0200 Subject: [PATCH] Merge jquery.placeholder with github.com/mathiasbynens/jquery-placeholder This is a fork from Mathias Bynens' jquery.placeholder as of this commit https://github.com/mathiasbynens/jquery-placeholder/blob/47f05d400e2dd16b59d144141a2cf54a9a77c502/jquery.placeholder.js New features added: * Support for valHooks and propHooks * Clearing placeholders upon for submission * jquery.placeholder QUnit test suite Bug: 57179 Change-Id: I1ecca3d89bb964414ad8765caaa7e1958b94f762 --- resources/jquery/jquery.placeholder.js | 276 +++++++++++++----- tests/qunit/QUnitTestResources.php | 2 + .../jquery/jquery.placeholder.test.js | 145 +++++++++ 3 files changed, 353 insertions(+), 70 deletions(-) create mode 100644 tests/qunit/suites/resources/jquery/jquery.placeholder.test.js diff --git a/resources/jquery/jquery.placeholder.js b/resources/jquery/jquery.placeholder.js index abada1973c..5020b37a67 100644 --- a/resources/jquery/jquery.placeholder.js +++ b/resources/jquery/jquery.placeholder.js @@ -3,92 +3,228 @@ * * This will automatically use the HTML5 placeholder attribute if supported, or emulate this behavior if not. * + * This is a fork from Mathias Bynens' jquery.placeholder as of this commit + * https://github.com/mathiasbynens/jquery-placeholder/blob/47f05d400e2dd16b59d144141a2cf54a9a77c502/jquery.placeholder.js + * + * @author Mathias Bynens * @author Trevor Parscal , 2012 * @author Krinkle , 2012 - * @version 0.2.0 + * @author Alex Ivanov , 2013 + * @version 2.1.0 * @license MIT */ -( function ( $ ) { +(function($) { - $.fn.placeholder = function ( text ) { - var hasArg = arguments.length; + var isInputSupported = 'placeholder' in document.createElement('input'), + isTextareaSupported = 'placeholder' in document.createElement('textarea'), + prototype = $.fn, + valHooks = $.valHooks, + propHooks = $.propHooks, + hooks, + placeholder; - return this.each( function () { - var placeholder, $input; + if (isInputSupported && isTextareaSupported) { - if ( hasArg ) { - this.setAttribute( 'placeholder', text ); - } + placeholder = prototype.placeholder = function(text) { + var hasArgs = arguments.length; - // If the HTML5 placeholder attribute is supported, use it - if ( this.placeholder && 'placeholder' in document.createElement( this.tagName ) ) { - return; + if( hasArgs ) { + changePlaceholder.call(this, text); } - placeholder = hasArg ? text : this.getAttribute( 'placeholder' ); - $input = $(this); + return this; + }; + + placeholder.input = placeholder.textarea = true; + + } else { - // Show initially, if empty - if ( this.value === '' || this.value === placeholder ) { - $input.addClass( 'placeholder' ).val( placeholder ); + placeholder = prototype.placeholder = function(text) { + var $this = this, + hasArgs = arguments.length; + + if(hasArgs) { + changePlaceholder.call(this, text); } - $input - // Show on blur if empty - .blur( function () { - if ( this.value === '' ) { - this.value = placeholder; - $input.addClass( 'placeholder' ); - } - } ) - - // Hide on focus - // Also listen for other events in case $input was - // already focused when the events were bound - .on( 'focus drop keydown paste', function ( e ) { - if ( $input.hasClass( 'placeholder' ) ) { - if ( e.type === 'drop' && e.originalEvent.dataTransfer ) { - // Support for drag&drop. Instead of inserting the dropped - // text somewhere in the middle of the placeholder string, - // we want to set the contents of the search box to the - // dropped text. - - // IE wants getData( 'text' ) but Firefox wants getData( 'text/plain' ) - // Firefox fails gracefully with an empty string, IE barfs with an error - try { - // Try the Firefox way - this.value = e.originalEvent.dataTransfer.getData( 'text/plain' ); - } catch ( exception ) { - // Got an exception, so use the IE way - this.value = e.originalEvent.dataTransfer.getData( 'text' ); - } - - // On Firefox, drop fires after the dropped text has been inserted, - // but on IE it fires before. If we don't prevent the default action, - // IE will insert the dropped text twice. - e.preventDefault(); - } else { - this.value = ''; - } - $input.removeClass( 'placeholder' ); + + $this + .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]') + .filter(function() { + return !$(this).data('placeholder-enabled'); + }) + .bind({ + 'focus.placeholder drop.placeholder': clearPlaceholder, + 'blur.placeholder': setPlaceholder + }) + .data('placeholder-enabled', true) + .trigger('blur.placeholder'); + return $this; + }; + + placeholder.input = isInputSupported; + placeholder.textarea = isTextareaSupported; + + hooks = { + 'get': function(element) { + var $element = $(element), + $passwordInput = $element.data('placeholder-password'); + if ($passwordInput) { + return $passwordInput[0].value; + } + + return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value; + }, + 'set': function(element, value) { + var $element = $(element), + $passwordInput = $element.data('placeholder-password'); + if ($passwordInput) { + $passwordInput[0].value = value; + return value; + } + + if (!$element.data('placeholder-enabled')) { + element.value = value; + return value; + } + if (!value) { + element.value = value; + // Issue #56: Setting the placeholder causes problems if the element continues to have focus. + if (element !== safeActiveElement()) { + // We can't use `triggerHandler` here because of dummy text/password inputs :( + setPlaceholder.call(element); } - } ); - - // Blank on submit -- prevents submitting with unintended value - if ( this.form ) { - $( this.form ).submit( function () { - // $input.trigger( 'focus' ); would be problematic - // because it actually focuses $input, leading - // to nasty behavior in mobile browsers - if ( $input.hasClass( 'placeholder' ) ) { - $input - .val( '' ) - .removeClass( 'placeholder' ); + } else if ($element.hasClass('placeholder')) { + if(!clearPlaceholder.call(element, true, value)) { + element.value = value; } - }); + } else { + element.value = value; + } + // `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363 + return $element; } + }; + + if (!isInputSupported) { + valHooks.input = hooks; + propHooks.value = hooks; + } + if (!isTextareaSupported) { + valHooks.textarea = hooks; + propHooks.value = hooks; + } + + $(function() { + // Look for forms + $(document).delegate('form', 'submit.placeholder', function() { + // Clear the placeholder values so they don't get submitted + var $inputs = $('.placeholder', this).each(clearPlaceholder); + setTimeout(function() { + $inputs.each(setPlaceholder); + }, 10); + }); + }); + // Clear placeholder values upon page reload + $(window).bind('beforeunload.placeholder', function() { + $('.placeholder').each(function() { + this.value = ''; + }); }); - }; -}( jQuery ) ); + } + + function args(elem) { + // Return an object of element attributes + var newAttrs = {}, + rinlinejQuery = /^jQuery\d+$/; + $.each(elem.attributes, function(i, attr) { + if (attr.specified && !rinlinejQuery.test(attr.name)) { + newAttrs[attr.name] = attr.value; + } + }); + return newAttrs; + } + + function clearPlaceholder(event, value) { + var input = this, + $input = $(input); + if (input.value === $input.attr('placeholder') && $input.hasClass('placeholder')) { + if ($input.data('placeholder-password')) { + $input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id')); + // If `clearPlaceholder` was called from `$.valHooks.input.set` + if (event === true) { + $input[0].value = value; + return value; + } + $input.focus(); + } else { + input.value = ''; + $input.removeClass('placeholder'); + if(input === safeActiveElement()) { + input.select(); + } + } + } + } + + function setPlaceholder() { + var $replacement, + input = this, + $input = $(input), + id = this.id; + if (!input.value) { + if (input.type === 'password') { + if (!$input.data('placeholder-textinput')) { + try { + $replacement = $input.clone().attr({ 'type': 'text' }); + } catch(e) { + $replacement = $('').attr($.extend(args(this), { 'type': 'text' })); + } + $replacement + .removeAttr('name') + .data({ + 'placeholder-password': $input, + 'placeholder-id': id + }) + .bind('focus.placeholder drop.placeholder', clearPlaceholder); + $input + .data({ + 'placeholder-textinput': $replacement, + 'placeholder-id': id + }) + .before($replacement); + } + $input = $input.removeAttr('id').hide().prev().attr('id', id).show(); + // Note: `$input[0] != input` now! + } + $input.addClass('placeholder'); + $input[0].value = $input.attr('placeholder'); + } else { + $input.removeClass('placeholder'); + } + } + + function safeActiveElement() { + // Avoid IE9 `document.activeElement` of death + // https://github.com/mathiasbynens/jquery-placeholder/pull/99 + try { + return document.activeElement; + } catch (err) {} + } + + function changePlaceholder(text) { + var hasArgs = arguments.length, + $input = this; + if(hasArgs) { + if($input.attr('placeholder') !== text) { + $input.prop('placeholder', text); + if($input.hasClass('placeholder')) { + $input[0].value = text; + } + } + } + } + +}(jQuery)); diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php index c8743750b5..01fedc8252 100644 --- a/tests/qunit/QUnitTestResources.php +++ b/tests/qunit/QUnitTestResources.php @@ -19,6 +19,7 @@ return array( 'tests/qunit/suites/resources/jquery/jquery.localize.test.js', 'tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js', 'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js', + 'tests/qunit/suites/resources/jquery/jquery.placeholder.test.js', 'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js', 'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js', 'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js', @@ -49,6 +50,7 @@ return array( 'jquery.localize', 'jquery.makeCollapsible', 'jquery.mwExtension', + 'jquery.placeholder', 'jquery.tabIndex', 'jquery.tablesorter', 'jquery.textSelection', diff --git a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js new file mode 100644 index 0000000000..ca0ea6708c --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js @@ -0,0 +1,145 @@ +(function($) { + + QUnit.module('jquery.placeholder', QUnit.newMwEnvironment()); + + QUnit.test('caches results of feature tests', 2, function(assert) { + assert.strictEqual(typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input'); + assert.strictEqual(typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea'); + }); + + if ($.fn.placeholder.input && $.fn.placeholder.textarea) { + return; + } + + var html = '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + testElement = function($el, assert) { + + var el = $el[0], + placeholder = el.getAttribute('placeholder'); + + assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + + assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + + // test on focus + $el.focus(); + assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + + // and unfocus (blur) again + $el.blur(); + + assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + + // change the value + $el.val('lorem ipsum'); + assert.strictEqual($el.prop('value'), 'lorem ipsum', '`$el.val(string)` should change the `value` property'); + assert.strictEqual(el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute'); + assert.ok(!$el.hasClass('placeholder'), '`$el.val(string)` should remove `placeholder` class'); + + // and clear it again + $el.val(''); + assert.strictEqual($el.prop('value'), '', '`$el.val("")` should change the `value` property'); + assert.strictEqual(el.value, placeholder, '`$el.val("")` should change the `value` attribute'); + assert.ok($el.hasClass('placeholder'), '`$el.val("")` should re-enable `placeholder` class'); + + // make sure the placeholder property works as expected. + assert.strictEqual($el.prop('placeholder'), placeholder, '$el.prop(`placeholder`) should return the placeholder value'); + $el.placeholder('new placeholder'); + assert.strictEqual(el.getAttribute('placeholder'), 'new placeholder', '$el.placeholder() should set the placeholder value'); + assert.strictEqual(el.value, 'new placeholder', '$el.placeholder() should update the displayed placeholder value'); + $el.placeholder(placeholder); + }; + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#input-type-text'), assert); + }); + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#input-type-search'), assert); + }); + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#input-type-email'), assert); + }); + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#input-type-url'), assert); + }); + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#input-type-tel'), assert); + }); + + QUnit.test('emulates placeholder for ', 13, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + + var selector = '#input-type-password', + $el = $(selector), + el = $el[0], + placeholder = el.getAttribute('placeholder'); + + assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + + // Re-select the element, as it gets replaced by another one in some browsers + $el = $(selector); + el = $el[0]; + + assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + + // test on focus + $el.focus(); + + // Re-select the element, as it gets replaced by another one in some browsers + $el = $(selector); + el = $el[0]; + + assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + + // and unfocus (blur) again + $el.blur(); + + // Re-select the element, as it gets replaced by another one in some browsers + $el = $(selector); + el = $el[0]; + + assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); + assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); + assert.strictEqual($el.val(), '', 'valHooks works properly'); + assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + + }); + + QUnit.test('emulates placeholder for ', 22, function(assert) { + $('
').html(html).appendTo($('#qunit-fixture')); + testElement($('#textarea'), assert); + }); + +}(jQuery)); -- 2.20.1