@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a basic multipage form

Summary:
Ref T2232. Very busy day on IRC so I feel like I've made 20 minutes of progress in 1-minute spurts here, but this adds the basics for a form that can have multiple pages and automatically handle pagination and reading to/from the request, objects and responses.

The UIExample is reasonably instructive. Basically, you make a form, add pages to the form, and add controls to the pages. The core flow control looks like this:

if ($request->isFormPost()) {
$form->readFromRequest($request); // (1)
if ($form->isComplete()) { // (2)
$response = $form->writeToResponse($response); // (3)
// Process result here. // (4)
}
} else {
$form->readFromObject($object); // (5)
}

The key parts are:

# This reads the form state from the request, including reading all the inactive pages.
# This tests if all pages are valid and the user just clicked "Done" on the last page.
# This produces a "response", which might be writing to an object (for simpler forms) or creating a transaction record (for more complex forms).
# Here, we would save the object or apply the transactions.
# When the user views the form for the first time, we preload all the values from some object (which might just be empty).

Ultimate goal here is to fix repository creation to not be a terrible pit of awfulness.

There are probably a lot of rough edges and missing features still, but this seems to not be totally crazy.

I'm using two submit buttons with different names which doesn't work on IE7 or something, but we can JS our way out of that if we need to.

Test Plan: Paged forward and backward through the form.

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2232

Differential Revision: https://secure.phabricator.com/D6003

+604 -49
+47 -47
src/__celerity_resource_map__.php
··· 800 800 ), 801 801 'aphront-form-view-css' => 802 802 array( 803 - 'uri' => '/res/70404bd5/rsrc/css/aphront/form-view.css', 803 + 'uri' => '/res/901cc9be/rsrc/css/aphront/form-view.css', 804 804 'type' => 'css', 805 805 'requires' => 806 806 array( ··· 2933 2933 ), 2934 2934 'phabricator-core-buttons-css' => 2935 2935 array( 2936 - 'uri' => '/res/da2e42df/rsrc/css/core/buttons.css', 2936 + 'uri' => '/res/9250d98f/rsrc/css/core/buttons.css', 2937 2937 'type' => 'css', 2938 2938 'requires' => 2939 2939 array( ··· 3981 3981 ), array( 3982 3982 'packages' => 3983 3983 array( 3984 - '190fa563' => 3984 + 'efb6389a' => 3985 3985 array( 3986 3986 'name' => 'core.pkg.css', 3987 3987 'symbols' => ··· 4030 4030 41 => 'phabricator-property-list-view-css', 4031 4031 42 => 'phabricator-tag-view-css', 4032 4032 ), 4033 - 'uri' => '/res/pkg/190fa563/core.pkg.css', 4033 + 'uri' => '/res/pkg/efb6389a/core.pkg.css', 4034 4034 'type' => 'css', 4035 4035 ), 4036 4036 '77faef00' => ··· 4224 4224 'reverse' => 4225 4225 array( 4226 4226 'aphront-attached-file-view-css' => '6b1fccc6', 4227 - 'aphront-dialog-view-css' => '190fa563', 4228 - 'aphront-error-view-css' => '190fa563', 4229 - 'aphront-form-view-css' => '190fa563', 4230 - 'aphront-list-filter-view-css' => '190fa563', 4231 - 'aphront-pager-view-css' => '190fa563', 4232 - 'aphront-panel-view-css' => '190fa563', 4233 - 'aphront-table-view-css' => '190fa563', 4234 - 'aphront-tokenizer-control-css' => '190fa563', 4235 - 'aphront-tooltip-css' => '190fa563', 4236 - 'aphront-typeahead-control-css' => '190fa563', 4227 + 'aphront-dialog-view-css' => 'efb6389a', 4228 + 'aphront-error-view-css' => 'efb6389a', 4229 + 'aphront-form-view-css' => 'efb6389a', 4230 + 'aphront-list-filter-view-css' => 'efb6389a', 4231 + 'aphront-pager-view-css' => 'efb6389a', 4232 + 'aphront-panel-view-css' => 'efb6389a', 4233 + 'aphront-table-view-css' => 'efb6389a', 4234 + 'aphront-tokenizer-control-css' => 'efb6389a', 4235 + 'aphront-tooltip-css' => 'efb6389a', 4236 + 'aphront-typeahead-control-css' => 'efb6389a', 4237 4237 'differential-changeset-view-css' => 'dd27a69b', 4238 4238 'differential-core-view-css' => 'dd27a69b', 4239 4239 'differential-inline-comment-editor' => '9488bb69', ··· 4247 4247 'differential-table-of-contents-css' => 'dd27a69b', 4248 4248 'diffusion-commit-view-css' => 'c8ce2d88', 4249 4249 'diffusion-icons-css' => 'c8ce2d88', 4250 - 'global-drag-and-drop-css' => '190fa563', 4250 + 'global-drag-and-drop-css' => 'efb6389a', 4251 4251 'inline-comment-summary-css' => 'dd27a69b', 4252 4252 'javelin-aphlict' => '77faef00', 4253 4253 'javelin-behavior' => 'c1359b5d', ··· 4321 4321 'javelin-util' => 'c1359b5d', 4322 4322 'javelin-vector' => 'c1359b5d', 4323 4323 'javelin-workflow' => 'c1359b5d', 4324 - 'lightbox-attachment-css' => '190fa563', 4324 + 'lightbox-attachment-css' => 'efb6389a', 4325 4325 'maniphest-task-summary-css' => '6b1fccc6', 4326 4326 'maniphest-transaction-detail-css' => '6b1fccc6', 4327 - 'phabricator-action-list-view-css' => '190fa563', 4328 - 'phabricator-application-launch-view-css' => '190fa563', 4327 + 'phabricator-action-list-view-css' => 'efb6389a', 4328 + 'phabricator-application-launch-view-css' => 'efb6389a', 4329 4329 'phabricator-busy' => '77faef00', 4330 4330 'phabricator-content-source-view-css' => 'dd27a69b', 4331 - 'phabricator-core-buttons-css' => '190fa563', 4332 - 'phabricator-core-css' => '190fa563', 4333 - 'phabricator-crumbs-view-css' => '190fa563', 4334 - 'phabricator-directory-css' => '190fa563', 4331 + 'phabricator-core-buttons-css' => 'efb6389a', 4332 + 'phabricator-core-css' => 'efb6389a', 4333 + 'phabricator-crumbs-view-css' => 'efb6389a', 4334 + 'phabricator-directory-css' => 'efb6389a', 4335 4335 'phabricator-drag-and-drop-file-upload' => '9488bb69', 4336 4336 'phabricator-dropdown-menu' => '77faef00', 4337 4337 'phabricator-file-upload' => '77faef00', 4338 - 'phabricator-filetree-view-css' => '190fa563', 4339 - 'phabricator-flag-css' => '190fa563', 4340 - 'phabricator-form-view-css' => '190fa563', 4341 - 'phabricator-header-view-css' => '190fa563', 4338 + 'phabricator-filetree-view-css' => 'efb6389a', 4339 + 'phabricator-flag-css' => 'efb6389a', 4340 + 'phabricator-form-view-css' => 'efb6389a', 4341 + 'phabricator-header-view-css' => 'efb6389a', 4342 4342 'phabricator-hovercard' => '77faef00', 4343 - 'phabricator-jump-nav' => '190fa563', 4343 + 'phabricator-jump-nav' => 'efb6389a', 4344 4344 'phabricator-keyboard-shortcut' => '77faef00', 4345 4345 'phabricator-keyboard-shortcut-manager' => '77faef00', 4346 - 'phabricator-main-menu-view' => '190fa563', 4346 + 'phabricator-main-menu-view' => 'efb6389a', 4347 4347 'phabricator-menu-item' => '77faef00', 4348 - 'phabricator-nav-view-css' => '190fa563', 4348 + 'phabricator-nav-view-css' => 'efb6389a', 4349 4349 'phabricator-notification' => '77faef00', 4350 - 'phabricator-notification-css' => '190fa563', 4351 - 'phabricator-notification-menu-css' => '190fa563', 4352 - 'phabricator-object-item-list-view-css' => '190fa563', 4350 + 'phabricator-notification-css' => 'efb6389a', 4351 + 'phabricator-notification-menu-css' => 'efb6389a', 4352 + 'phabricator-object-item-list-view-css' => 'efb6389a', 4353 4353 'phabricator-object-selector-css' => 'dd27a69b', 4354 4354 'phabricator-phtize' => '77faef00', 4355 4355 'phabricator-prefab' => '77faef00', 4356 4356 'phabricator-project-tag-css' => '6b1fccc6', 4357 - 'phabricator-property-list-view-css' => '190fa563', 4358 - 'phabricator-remarkup-css' => '190fa563', 4357 + 'phabricator-property-list-view-css' => 'efb6389a', 4358 + 'phabricator-remarkup-css' => 'efb6389a', 4359 4359 'phabricator-shaped-request' => '9488bb69', 4360 - 'phabricator-side-menu-view-css' => '190fa563', 4361 - 'phabricator-standard-page-view' => '190fa563', 4362 - 'phabricator-tag-view-css' => '190fa563', 4360 + 'phabricator-side-menu-view-css' => 'efb6389a', 4361 + 'phabricator-standard-page-view' => 'efb6389a', 4362 + 'phabricator-tag-view-css' => 'efb6389a', 4363 4363 'phabricator-textareautils' => '77faef00', 4364 4364 'phabricator-tooltip' => '77faef00', 4365 - 'phabricator-transaction-view-css' => '190fa563', 4366 - 'phabricator-zindex-css' => '190fa563', 4367 - 'phui-form-css' => '190fa563', 4368 - 'phui-icon-view-css' => '190fa563', 4369 - 'spacing-css' => '190fa563', 4370 - 'sprite-apps-large-css' => '190fa563', 4371 - 'sprite-gradient-css' => '190fa563', 4372 - 'sprite-icons-css' => '190fa563', 4373 - 'sprite-menu-css' => '190fa563', 4374 - 'syntax-highlighting-css' => '190fa563', 4365 + 'phabricator-transaction-view-css' => 'efb6389a', 4366 + 'phabricator-zindex-css' => 'efb6389a', 4367 + 'phui-form-css' => 'efb6389a', 4368 + 'phui-icon-view-css' => 'efb6389a', 4369 + 'spacing-css' => 'efb6389a', 4370 + 'sprite-apps-large-css' => 'efb6389a', 4371 + 'sprite-gradient-css' => 'efb6389a', 4372 + 'sprite-icons-css' => 'efb6389a', 4373 + 'sprite-menu-css' => 'efb6389a', 4374 + 'syntax-highlighting-css' => 'efb6389a', 4375 4375 ), 4376 4376 ));
+8
src/__phutil_library_map__.php
··· 671 671 'PHUIBoxView' => 'view/phui/PHUIBoxView.php', 672 672 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 673 673 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 674 + 'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php', 675 + 'PHUIFormPageView' => 'view/form/PHUIFormPageView.php', 674 676 'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php', 675 677 'PHUIIconView' => 'view/phui/PHUIIconView.php', 678 + 'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php', 676 679 'PHUITextExample' => 'applications/uiexample/examples/PHUITextExample.php', 677 680 'PHUITextView' => 'view/phui/PHUITextView.php', 678 681 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', ··· 1218 1221 'PhabricatorPHIDController' => 'applications/phid/controller/PhabricatorPHIDController.php', 1219 1222 'PhabricatorPHIDLookupController' => 'applications/phid/controller/PhabricatorPHIDLookupController.php', 1220 1223 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', 1224 + 'PhabricatorPagedFormExample' => 'applications/uiexample/examples/PhabricatorPagedFormExample.php', 1221 1225 'PhabricatorPaste' => 'applications/paste/storage/PhabricatorPaste.php', 1222 1226 'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php', 1223 1227 'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php', ··· 2442 2446 'PHUIBoxView' => 'AphrontTagView', 2443 2447 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 2444 2448 'PHUIFeedStoryView' => 'AphrontView', 2449 + 'PHUIFormMultiSubmitControl' => 'AphrontFormControl', 2450 + 'PHUIFormPageView' => 'AphrontView', 2445 2451 'PHUIIconExample' => 'PhabricatorUIExample', 2446 2452 'PHUIIconView' => 'AphrontTagView', 2453 + 'PHUIPagedFormView' => 'AphrontTagView', 2447 2454 'PHUITextExample' => 'PhabricatorUIExample', 2448 2455 'PHUITextView' => 'AphrontTagView', 2449 2456 'PackageCreateMail' => 'PackageMail', ··· 2973 2980 'PhabricatorPHIDController' => 'PhabricatorController', 2974 2981 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 2975 2982 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', 2983 + 'PhabricatorPagedFormExample' => 'PhabricatorUIExample', 2976 2984 'PhabricatorPaste' => 2977 2985 array( 2978 2986 0 => 'PhabricatorPasteDAO',
+67
src/applications/uiexample/examples/PhabricatorPagedFormExample.php
··· 1 + <?php 2 + 3 + final class PhabricatorPagedFormExample extends PhabricatorUIExample { 4 + 5 + public function getName() { 6 + return pht('Form (Paged)'); 7 + } 8 + 9 + public function getDescription() { 10 + return pht( 11 + 'Use %s to render forms with multiple pages.', 12 + hsprintf('<tt>PHUIPagedFormView</tt>')); 13 + } 14 + 15 + public function renderExample() { 16 + $request = $this->getRequest(); 17 + $user = $request->getUser(); 18 + 19 + 20 + $page1 = id(new PHUIFormPageView()) 21 + ->addControl( 22 + id(new AphrontFormTextControl()) 23 + ->setName('page1') 24 + ->setLabel('Page 1')); 25 + 26 + $page2 = id(new PHUIFormPageView()) 27 + ->addControl( 28 + id(new AphrontFormTextControl()) 29 + ->setName('page2') 30 + ->setLabel('Page 2')); 31 + 32 + $page3 = id(new PHUIFormPageView()) 33 + ->addControl( 34 + id(new AphrontFormTextControl()) 35 + ->setName('page3') 36 + ->setLabel('Page 3')); 37 + 38 + $page4 = id(new PHUIFormPageView()) 39 + ->addControl( 40 + id(new AphrontFormTextControl()) 41 + ->setName('page4') 42 + ->setLabel('Page 4')); 43 + 44 + $form = new PHUIPagedFormView(); 45 + $form->setUser($user); 46 + 47 + $form->addPage('page1', $page1); 48 + $form->addPage('page2', $page2); 49 + $form->addPage('page3', $page3); 50 + $form->addPage('page4', $page4); 51 + 52 + if ($request->isFormPost()) { 53 + $form->readFromRequest($request); 54 + if ($form->isComplete()) { 55 + return id(new AphrontDialogView()) 56 + ->setUser($user) 57 + ->setTitle(pht('Form Complete')) 58 + ->appendChild(pht('You submitted the form. Well done!')) 59 + ->addCancelButton($request->getRequestURI(), pht('Again!')); 60 + } 61 + } else { 62 + $form->readFromObject(null); 63 + } 64 + 65 + return $form; 66 + } 67 + }
+133
src/view/form/PHUIFormPageView.php
··· 1 + <?php 2 + 3 + /** 4 + * @concrete-extensible 5 + */ 6 + class PHUIFormPageView extends AphrontView { 7 + 8 + private $key; 9 + private $form; 10 + private $controls = array(); 11 + private $values = array(); 12 + private $isValid; 13 + 14 + public function addControl(AphrontFormControl $control) { 15 + $name = $control->getName(); 16 + 17 + if (!strlen($name)) { 18 + throw new Exception("Form control has no name!"); 19 + } 20 + 21 + if (isset($this->controls[$name])) { 22 + throw new Exception( 23 + "Form page contains duplicate control with name '{$name}'!"); 24 + } 25 + 26 + $this->controls[$name] = $control; 27 + $control->setFormPage($this); 28 + 29 + return $this; 30 + } 31 + 32 + public function getControls() { 33 + return $this->controls; 34 + } 35 + 36 + public function getControl($name) { 37 + if (empty($this->controls[$name])) { 38 + throw new Exception("No page control '{$name}'!"); 39 + } 40 + return $this->controls[$name]; 41 + } 42 + 43 + protected function canAppendChild() { 44 + return false; 45 + } 46 + 47 + public function setPagedFormView(PHUIPagedFormView $view, $key) { 48 + if ($this->key) { 49 + throw new Exception("This page is already part of a form!"); 50 + } 51 + $this->form = $view; 52 + $this->key = $key; 53 + return $this; 54 + } 55 + 56 + public function getKey() { 57 + return $this->key; 58 + } 59 + 60 + public function render() { 61 + return $this->getControls(); 62 + } 63 + 64 + public function getForm() { 65 + return $this->form; 66 + } 67 + 68 + public function getRequestKey($key) { 69 + return $this->getForm()->getRequestKey('p:'.$this->key.':'.$key); 70 + } 71 + 72 + public function validateObjectType($object) { 73 + return true; 74 + } 75 + 76 + public function validateResponseType($response) { 77 + return true; 78 + } 79 + 80 + protected function validateControls() { 81 + $result = true; 82 + foreach ($this->getControls() as $name => $control) { 83 + if (!$control->isValid()) { 84 + $result = false; 85 + break; 86 + } 87 + } 88 + 89 + return $result; 90 + } 91 + 92 + public function isValid() { 93 + if ($this->isValid === null) { 94 + $this->isValid = $this->validateControls(); 95 + } 96 + return $this->isValid; 97 + } 98 + 99 + public function readFromRequest(AphrontRequest $request) { 100 + foreach ($this->getControls() as $name => $control) { 101 + $control->readValueFromRequest($request); 102 + } 103 + 104 + return $this; 105 + } 106 + 107 + public function readFromObject($object) { 108 + return $this; 109 + } 110 + 111 + public function writeToResponse($response) { 112 + return $this; 113 + } 114 + 115 + public function readSerializedValues(AphrontRequest $request) { 116 + foreach ($this->getControls() as $name => $control) { 117 + $key = $this->getRequestKey($name); 118 + $control->readSerializedValue($request->getStr($key)); 119 + } 120 + 121 + return $this; 122 + } 123 + 124 + public function getSerializedValues() { 125 + $dict = array(); 126 + foreach ($this->getControls() as $name => $control) { 127 + $key = $this->getRequestKey($name); 128 + $dict[$key] = $control->getSerializedValue(); 129 + } 130 + return $dict; 131 + } 132 + 133 + }
+252
src/view/form/PHUIPagedFormView.php
··· 1 + <?php 2 + 3 + /** 4 + * 5 + * @task page Managing Pages 6 + */ 7 + final class PHUIPagedFormView extends AphrontTagView { 8 + 9 + private $name = 'pages'; 10 + private $pages = array(); 11 + private $selectedPage; 12 + private $choosePage; 13 + private $nextPage; 14 + private $prevPage; 15 + private $complete; 16 + 17 + protected function canAppendChild() { 18 + return false; 19 + } 20 + 21 + 22 + /* -( Managing Pages )----------------------------------------------------- */ 23 + 24 + 25 + /** 26 + * @task page 27 + */ 28 + public function addPage($key, PHUIFormPageView $page) { 29 + if (isset($this->pages[$key])) { 30 + throw new Exception("Duplicate page with key '{$key}'!"); 31 + } 32 + $this->pages[$key] = $page; 33 + $page->setPagedFormView($this, $key); 34 + return $this; 35 + } 36 + 37 + 38 + /** 39 + * @task page 40 + */ 41 + public function getPage($key) { 42 + if (!$this->pageExists($key)) { 43 + throw new Exception("No page '{$key}' exists!"); 44 + } 45 + return $this->pages[$key]; 46 + } 47 + 48 + 49 + /** 50 + * @task page 51 + */ 52 + public function pageExists($key) { 53 + return isset($this->pages[$key]); 54 + } 55 + 56 + 57 + /** 58 + * @task page 59 + */ 60 + protected function getPageIndex($key) { 61 + $page = $this->getPage($key); 62 + 63 + $index = 0; 64 + foreach ($this->pages as $target_page) { 65 + if ($page === $target_page) { 66 + break; 67 + } 68 + $index++; 69 + } 70 + 71 + return $index; 72 + } 73 + 74 + 75 + /** 76 + * @task page 77 + */ 78 + protected function getPageByIndex($index) { 79 + foreach ($this->pages as $page) { 80 + if (!$index) { 81 + return $page; 82 + } 83 + $index--; 84 + } 85 + 86 + throw new Exception("Requesting out-of-bounds page '{$index}'."); 87 + } 88 + 89 + protected function getLastIndex() { 90 + return count($this->pages) - 1; 91 + } 92 + 93 + protected function isFirstPage(PHUIFormPageView $page) { 94 + return ($this->getPageIndex($page->getKey()) === 0); 95 + 96 + } 97 + 98 + protected function isLastPage(PHUIFormPageView $page) { 99 + return ($this->getPageIndex($page->getKey()) === (count($this->pages) - 1)); 100 + } 101 + 102 + public function getSelectedPage() { 103 + return $this->selectedPage; 104 + } 105 + 106 + public function readFromObject($object) { 107 + foreach ($this->pages as $page) { 108 + $page->validateObjectType($object); 109 + $page->readFromObject($object); 110 + } 111 + 112 + return $this->processForm(); 113 + } 114 + 115 + public function writeToResponse($response) { 116 + foreach ($this->pages as $page) { 117 + $page->validateResponseType($response); 118 + $response = $page->writeToResponse($page); 119 + } 120 + 121 + return $response; 122 + } 123 + 124 + public function readFromRequest(AphrontRequest $request) { 125 + $active_page = $request->getStr($this->getRequestKey('page')); 126 + 127 + foreach ($this->pages as $key => $page) { 128 + if ($key == $active_page) { 129 + $page->readFromRequest($request); 130 + } else { 131 + $page->readSerializedValues($request); 132 + } 133 + } 134 + 135 + $this->choosePage = $active_page; 136 + $this->nextPage = $request->getStr('__submit__'); 137 + $this->prevPage = $request->getStr('__back__'); 138 + 139 + return $this->processForm(); 140 + } 141 + 142 + public function setName($name) { 143 + $this->name = $name; 144 + return $this; 145 + } 146 + 147 + 148 + public function getValue($page, $key, $default = null) { 149 + return $this->getPage($page)->getValue($key, $default); 150 + } 151 + 152 + public function setValue($page, $key, $value) { 153 + $this->getPage($page)->setValue($key, $value); 154 + return $this; 155 + } 156 + 157 + public function processForm() { 158 + foreach ($this->pages as $key => $page) { 159 + if (!$page->isValid()) { 160 + break; 161 + } 162 + } 163 + 164 + if ($this->pageExists($this->choosePage)) { 165 + $selected = $this->getPage($this->choosePage); 166 + } else { 167 + $selected = $this->getPageByIndex(0); 168 + } 169 + 170 + $is_attempt_complete = false; 171 + if ($this->prevPage) { 172 + $prev_index = $this->getPageIndex($selected->getKey()) - 1;; 173 + $index = max(0, $prev_index); 174 + $selected = $this->getPageByIndex($index); 175 + } else if ($this->nextPage) { 176 + $next_index = $this->getPageIndex($selected->getKey()) + 1; 177 + if ($next_index > $this->getLastIndex()) { 178 + $is_attempt_complete = true; 179 + } 180 + $index = min($this->getLastIndex(), $next_index); 181 + $selected = $this->getPageByIndex($index); 182 + } 183 + 184 + $validation_error = false; 185 + foreach ($this->pages as $key => $page) { 186 + if (!$page->isValid()) { 187 + $validation_error = true; 188 + break; 189 + } 190 + if ($page === $selected) { 191 + break; 192 + } 193 + } 194 + 195 + if ($is_attempt_complete && !$validation_error) { 196 + $this->complete = true; 197 + } else { 198 + $this->selectedPage = $page; 199 + } 200 + 201 + return $this; 202 + } 203 + 204 + public function isComplete() { 205 + return $this->complete; 206 + } 207 + 208 + public function getRequestKey($key) { 209 + return $this->name.':'.$key; 210 + } 211 + 212 + public function getTagContent() { 213 + $form = id(new AphrontFormView()) 214 + ->setUser($this->getUser()); 215 + 216 + $selected_page = $this->getSelectedPage(); 217 + if (!$selected_page) { 218 + throw new Exception("No selected page!"); 219 + } 220 + 221 + $form->addHiddenInput( 222 + $this->getRequestKey('page'), 223 + $selected_page->getKey()); 224 + 225 + foreach ($this->pages as $page) { 226 + if ($page == $selected_page) { 227 + continue; 228 + } 229 + foreach ($page->getSerializedValues() as $key => $value) { 230 + $form->addHiddenInput($key, $value); 231 + } 232 + } 233 + 234 + $submit = id(new PHUIFormMultiSubmitControl()); 235 + 236 + if (!$this->isFirstPage($selected_page)) { 237 + $submit->addBackButton(); 238 + } 239 + 240 + if ($this->isLastPage($selected_page)) { 241 + $submit->addSubmitButton(pht("Save")); 242 + } else { 243 + $submit->addSubmitButton(pht("Continue \xC2\xBB")); 244 + } 245 + 246 + $form->appendChild($selected_page); 247 + $form->appendChild($submit); 248 + 249 + return $form; 250 + } 251 + 252 + }
+51
src/view/form/control/AphrontFormControl.php
··· 11 11 private $id; 12 12 private $controlID; 13 13 private $controlStyle; 14 + private $formPage; 15 + private $required; 14 16 15 17 public function setID($id) { 16 18 $this->id = $id; ··· 82 84 83 85 public function getValue() { 84 86 return $this->value; 87 + } 88 + 89 + public function isValid() { 90 + if ($this->error && $this->error !== true) { 91 + return false; 92 + } 93 + 94 + if ($this->isRequired() && $this->isEmpty()) { 95 + return false; 96 + } 97 + 98 + return true; 99 + } 100 + 101 + public function isRequired() { 102 + return $this->required; 103 + } 104 + 105 + public function isEmpty() { 106 + return !strlen($this->getValue()); 107 + } 108 + 109 + public function getSerializedValue() { 110 + return $this->getValue(); 111 + } 112 + 113 + public function readSerializedValue($value) { 114 + $this->setValue($value); 115 + return $this; 116 + } 117 + 118 + public function readValueFromRequest(AphrontRequest $request) { 119 + $this->setValue($request->getStr($this->getName())); 120 + return $this; 121 + } 122 + 123 + public function setFormPage(PHUIFormPageView $page) { 124 + if ($this->formPage) { 125 + throw new Exception("This control is already a member of a page!"); 126 + } 127 + $this->formPage = $page; 128 + return $this; 129 + } 130 + 131 + public function getFormPage() { 132 + if ($this->formPage === null) { 133 + throw new Exception("This control does not have a page!"); 134 + } 135 + return $this->formPage; 85 136 } 86 137 87 138 public function setDisabled($disabled) {
+38
src/view/form/control/PHUIFormMultiSubmitControl.php
··· 1 + <?php 2 + 3 + final class PHUIFormMultiSubmitControl extends AphrontFormControl { 4 + 5 + private $buttons = array(); 6 + 7 + public function addBackButton($label = null) { 8 + if ($label === null) { 9 + $label = pht("\xC2\xAB Back"); 10 + } 11 + return $this->addButton('__back__', $label, 'grey'); 12 + } 13 + 14 + public function addSubmitButton($label) { 15 + return $this->addButton('__submit__', $label); 16 + } 17 + 18 + public function addButton($name, $label, $class = null) { 19 + $this->buttons[] = phutil_tag( 20 + 'input', 21 + array( 22 + 'type' => 'submit', 23 + 'name' => $name, 24 + 'value' => $label, 25 + 'class' => $class, 26 + 'disabled' => $this->getDisabled() ? 'disabled' : null, 27 + )); 28 + } 29 + 30 + protected function getCustomControlClass() { 31 + return 'phui-form-control-multi-submit'; 32 + } 33 + 34 + protected function renderInput() { 35 + return array_reverse($this->buttons); 36 + } 37 + 38 + }
+6
webroot/rsrc/css/aphront/form-view.css
··· 100 100 margin: 0.5em 0 0em 2%; 101 101 } 102 102 103 + .phui-form-control-multi-submit input { 104 + float: right; 105 + margin: 0.5em 0 0em 2%; 106 + width: auto; 107 + } 108 + 103 109 .aphront-form-control-textarea textarea.aphront-textarea-very-short { 104 110 height: 44px; 105 111 }
+2 -2
webroot/rsrc/css/core/buttons.css
··· 6 6 button, 7 7 a.button, 8 8 a.button:visited, 9 - input.inputsubmit { 9 + input[type="submit"] { 10 10 background-color: #3477ad; 11 11 color: white; 12 12 text-shadow: 0 -1px rgba(0,0,0,0.75); ··· 53 53 } 54 54 55 55 button.grey, 56 - input.inputaux, 56 + input[type="submit"].grey, 57 57 a.grey, 58 58 a.grey:visited { 59 59 background-color: #f7f7f7;