From c8afc741fa7dc2c18a55beafe0a2e063dd7bee2b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 30 Jul 2012 16:08:10 -0700 Subject: [PATCH] Add support for placeholders Summary: Support placeholder text for inputs. We currently don't use this because it requires JS and doesn't degrade (no JS means you have zero idea what the input is for if it isn't separately labeled) but there are some cases where intent is obvious from context (for example, the search input in the menu bar, which is fairly obvious on its own and will soon have a magnifying glass icon) and in such cases it's much prettier and saves a bunch of space over an explicit label. Add a behavior so we can add placeholders where they make sense. This implementation is somewhat sanity-checked agianst the two jQuery placeholder implementations I was able to google: https://github.com/danielstocks/jQuery-Placeholder/ https://github.com/mathiasbynens/jquery-placeholder Since we don't currently have any uses cases, I haven't included support for making JS access to the `value` work, for password inputs, or for dynamically altering the placeholder. Test Plan: Played around with the placeholder in the UI example in various browsers and couldn't break it. Reviewers: btrahan, chad Reviewed By: btrahan CC: aran Maniphest Tasks: T1569 Differential Revision: https://secure.phabricator.com/D3103 --- src/__celerity_resource_map__.php | 108 +++++++++--------- src/__phutil_library_map__.php | 2 + .../uiexample/examples/JavelinUIExample.php | 59 ++++++++++ .../application/base/standard-page-view.css | 8 ++ .../application/core/behavior-placeholder.js | 60 ++++++++++ 5 files changed, 180 insertions(+), 57 deletions(-) create mode 100644 src/applications/uiexample/examples/JavelinUIExample.php create mode 100644 webroot/rsrc/js/application/core/behavior-placeholder.js diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index ee146d88a5..dfd228376a 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -28,25 +28,11 @@ celerity_register_resource_map(array( 'disk' => '/rsrc/image/credit_cards.png', 'type' => 'png', ), - '/rsrc/image/custom/example_facebook.png' => + '/rsrc/image/glyph_sprite.png' => array( - 'hash' => '062911a848dd7b58159d4184488ecac4', - 'uri' => '/res/062911a8/rsrc/image/custom/example_facebook.png', - 'disk' => '/rsrc/image/custom/example_facebook.png', - 'type' => 'png', - ), - '/rsrc/image/custom/example_rainbow.png' => - array( - 'hash' => '7af08c16d969a2d63b4acdbd5cee2e8f', - 'uri' => '/res/7af08c16/rsrc/image/custom/example_rainbow.png', - 'disk' => '/rsrc/image/custom/example_rainbow.png', - 'type' => 'png', - ), - '/rsrc/image/custom/example_template.png' => - array( - 'hash' => '9971fe1adb16682a18f9b588c4fc051b', - 'uri' => '/res/9971fe1a/rsrc/image/custom/example_template.png', - 'disk' => '/rsrc/image/custom/example_template.png', + 'hash' => '0a1ea7c048be9f0b76ab2c807a9a1c0d', + 'uri' => '/res/0a1ea7c0/rsrc/image/glyph_sprite.png', + 'disk' => '/rsrc/image/glyph_sprite.png', 'type' => 'png', ), '/rsrc/image/grippy_texture.png' => @@ -888,18 +874,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/behavior-buoyant.js', ), - 'javelin-behavior-burn-chart' => - array( - 'uri' => '/res/0bc23e4d/rsrc/js/application/maniphest/behavior-burn-chart.js', - 'type' => 'js', - 'requires' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-dom', - 2 => 'javelin-vector', - ), - 'disk' => '/rsrc/js/application/maniphest/behavior-burn-chart.js', - ), 'javelin-behavior-countdown-timer' => array( 'uri' => '/res/5ee9cb13/rsrc/js/application/countdown/timer.js', @@ -1221,7 +1195,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-line-chart' => array( - 'uri' => '/res/653743c8/rsrc/js/application/maniphest/behavior-line-chart.js', + 'uri' => '/res/1aa5ac88/rsrc/js/application/maniphest/behavior-line-chart.js', 'type' => 'js', 'requires' => array( @@ -1486,6 +1460,17 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/phriction/phriction-document-preview.js', ), + 'javelin-behavior-placeholder' => + array( + 'uri' => '/res/7dc26990/rsrc/js/application/core/behavior-placeholder.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + ), + 'disk' => '/rsrc/js/application/core/behavior-placeholder.js', + ), 'javelin-behavior-projects-resource-editor' => array( 'uri' => '/res/ffdde7d9/rsrc/js/application/projects/projects-resource-editor.js', @@ -2179,6 +2164,15 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/flag/flag.css', ), + 'phabricator-glyph-css' => + array( + 'uri' => '/res/52f0dc90/rsrc/css/application/base/glyph.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/application/base/glyph.css', + ), 'phabricator-jump-nav' => array( 'uri' => '/res/8bdc0fc3/rsrc/css/application/directory/phabricator-jump-nav.css', @@ -2358,7 +2352,7 @@ celerity_register_resource_map(array( ), 'phabricator-standard-page-view' => array( - 'uri' => '/res/de559ba2/rsrc/css/application/base/standard-page-view.css', + 'uri' => '/res/48118f6f/rsrc/css/application/base/standard-page-view.css', 'type' => 'css', 'requires' => array( @@ -2613,7 +2607,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - '78836d86' => + '12ecfc95' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -2642,7 +2636,7 @@ celerity_register_resource_map(array( 21 => 'phabricator-flag-css', 22 => 'aphront-error-view-css', ), - 'uri' => '/res/pkg/78836d86/core.pkg.css', + 'uri' => '/res/pkg/12ecfc95/core.pkg.css', 'type' => 'css', ), 'f363b322' => @@ -2809,20 +2803,20 @@ celerity_register_resource_map(array( 'reverse' => array( 'aphront-attached-file-view-css' => '7839ae2d', - 'aphront-crumbs-view-css' => '78836d86', - 'aphront-dialog-view-css' => '78836d86', - 'aphront-error-view-css' => '78836d86', - 'aphront-form-view-css' => '78836d86', + 'aphront-crumbs-view-css' => '12ecfc95', + 'aphront-dialog-view-css' => '12ecfc95', + 'aphront-error-view-css' => '12ecfc95', + 'aphront-form-view-css' => '12ecfc95', 'aphront-headsup-action-list-view-css' => '96bc37d6', - 'aphront-headsup-view-css' => '78836d86', - 'aphront-list-filter-view-css' => '78836d86', - 'aphront-pager-view-css' => '78836d86', - 'aphront-panel-view-css' => '78836d86', - 'aphront-side-nav-view-css' => '78836d86', - 'aphront-table-view-css' => '78836d86', - 'aphront-tokenizer-control-css' => '78836d86', - 'aphront-tooltip-css' => '78836d86', - 'aphront-typeahead-control-css' => '78836d86', + 'aphront-headsup-view-css' => '12ecfc95', + 'aphront-list-filter-view-css' => '12ecfc95', + 'aphront-pager-view-css' => '12ecfc95', + 'aphront-panel-view-css' => '12ecfc95', + 'aphront-side-nav-view-css' => '12ecfc95', + 'aphront-table-view-css' => '12ecfc95', + 'aphront-tokenizer-control-css' => '12ecfc95', + 'aphront-tooltip-css' => '12ecfc95', + 'aphront-typeahead-control-css' => '12ecfc95', 'differential-changeset-view-css' => '96bc37d6', 'differential-core-view-css' => '96bc37d6', 'differential-inline-comment-editor' => 'f4bbbd84', @@ -2888,15 +2882,15 @@ celerity_register_resource_map(array( 'javelin-workflow' => 'f363b322', 'maniphest-task-summary-css' => '7839ae2d', 'maniphest-transaction-detail-css' => '7839ae2d', - 'phabricator-app-buttons-css' => '78836d86', + 'phabricator-app-buttons-css' => '12ecfc95', 'phabricator-content-source-view-css' => '96bc37d6', - 'phabricator-core-buttons-css' => '78836d86', - 'phabricator-core-css' => '78836d86', - 'phabricator-directory-css' => '78836d86', + 'phabricator-core-buttons-css' => '12ecfc95', + 'phabricator-core-css' => '12ecfc95', + 'phabricator-directory-css' => '12ecfc95', 'phabricator-drag-and-drop-file-upload' => 'f4bbbd84', 'phabricator-dropdown-menu' => 'f363b322', - 'phabricator-flag-css' => '78836d86', - 'phabricator-jump-nav' => '78836d86', + 'phabricator-flag-css' => '12ecfc95', + 'phabricator-jump-nav' => '12ecfc95', 'phabricator-keyboard-shortcut' => 'f363b322', 'phabricator-keyboard-shortcut-manager' => 'f363b322', 'phabricator-menu-item' => 'f363b322', @@ -2904,11 +2898,11 @@ celerity_register_resource_map(array( 'phabricator-paste-file-upload' => 'f363b322', 'phabricator-prefab' => 'f363b322', 'phabricator-project-tag-css' => '7839ae2d', - 'phabricator-remarkup-css' => '78836d86', + 'phabricator-remarkup-css' => '12ecfc95', 'phabricator-shaped-request' => 'f4bbbd84', - 'phabricator-standard-page-view' => '78836d86', + 'phabricator-standard-page-view' => '12ecfc95', 'phabricator-tooltip' => 'f363b322', - 'phabricator-transaction-view-css' => '78836d86', - 'syntax-highlighting-css' => '78836d86', + 'phabricator-transaction-view-css' => '12ecfc95', + 'syntax-highlighting-css' => '12ecfc95', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3b8cc94eb4..0de8f96484 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -467,6 +467,7 @@ phutil_register_library_map(array( 'HeraldValueTypeConfig' => 'applications/herald/config/HeraldValueTypeConfig.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', 'JavelinReactorExample' => 'applications/uiexample/examples/JavelinReactorExample.php', + 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', 'JavelinViewExample' => 'applications/uiexample/examples/JavelinViewExample.php', 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', 'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php', @@ -1525,6 +1526,7 @@ phutil_register_library_map(array( 'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptListController' => 'HeraldController', 'JavelinReactorExample' => 'PhabricatorUIExample', + 'JavelinUIExample' => 'PhabricatorUIExample', 'JavelinViewExample' => 'PhabricatorUIExample', 'JavelinViewExampleServerView' => 'AphrontView', 'LiskEphemeralObjectException' => 'Exception', diff --git a/src/applications/uiexample/examples/JavelinUIExample.php b/src/applications/uiexample/examples/JavelinUIExample.php new file mode 100644 index 0000000000..81cfed8fb1 --- /dev/null +++ b/src/applications/uiexample/examples/JavelinUIExample.php @@ -0,0 +1,59 @@ +getRequest(); + $user = $request->getUser(); + + $placeholder_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'placeholder', + array( + 'id' => $placeholder_id, + 'text' => 'This is a placeholder', + )); + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Placeholder') + ->setID($placeholder_id)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Submit')); + + $panel = new AphrontPanelView(); + $panel->setHeader('A Form'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild($form); + + return $panel; + } +} diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 619f884f88..58546d42e9 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -304,3 +304,11 @@ a.handle-disabled { background: #eeeeee; border-top: 1px solid #dddddd; } + + +/** + * Placeholder text added to inputs by the "placeholder" behavior. + */ +.jx-placeholder { + color: #888888; +} diff --git a/webroot/rsrc/js/application/core/behavior-placeholder.js b/webroot/rsrc/js/application/core/behavior-placeholder.js new file mode 100644 index 0000000000..baff60482c --- /dev/null +++ b/webroot/rsrc/js/application/core/behavior-placeholder.js @@ -0,0 +1,60 @@ +/** + * @provides javelin-behavior-placeholder + * @requires javelin-behavior + * javelin-dom + */ + +/** + * Add placeholder text to an input. Config are: + * + * - `id` (Required) ID of the element to add placeholder text to. + * - `text` (Required) Text to show. + * + * While the element is displaying placeholder text, the class `jx-placeholder` + * is added to it. Normally, you use a lower-contrast color to indicate that + * this text is instructional: + * + * .jx-placeholder { + * color: #888888; + * } + * + * @group ui + */ +JX.behavior('placeholder', function(config) { + var input = JX.$(config.id); + var placeholder_visible = false; + + function update(show_placeholder) { + placeholder_visible = show_placeholder; + JX.DOM.alterClass(input, 'jx-placeholder', placeholder_visible); + } + + function onfocus() { + if (placeholder_visible) { + input.value = ''; + update(false); + } + } + + function onblur() { + if (!input.value) { + input.value = config.text; + update(true); + } + } + + JX.DOM.listen(input, 'focus', null, onfocus); + JX.DOM.listen(input, 'blur', null, onblur); + + // When the user submits the form, remove the placeholder text (so it doesn't + // get submitted to the server) and then restore it after the submit finishes. + JX.DOM.listen(input.form, 'submit', null, function() { + onfocus(); + setTimeout(onblur, 0); + }); + + // If the element isn't currently focused, show the placeholder text. + if (document.activeElement != input) { + onblur(); + } +});