core/includes/form.inc | 3 +- core/misc/form.autosave.js | 17 +++-- .../node/lib/Drupal/node/NodeFormController.php | 4 +- .../lib/Drupal/node/Tests/NodeCreationTest.php | 10 +++ .../node/lib/Drupal/node/Tests/PageEditTest.php | 9 +++ .../lib/Drupal/system/Tests/Form/AutoSaveTest.php | 80 ++++++++++++++++++++ .../tests/modules/form_test/form_test.module | 51 +++++++++++++ 7 files changed, 162 insertions(+), 12 deletions(-) diff --git a/core/includes/form.inc b/core/includes/form.inc index bc98bf2..ddd8a4f 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -969,12 +969,13 @@ function drupal_process_form($form_id, &$form, &$form_state) { } } + // Attach localStorage-powered auto form saving behavior. if (!empty($form['#autosave'])) { $form['#attached']['library'][] = array('system', 'drupal.formAutoSave'); $form['#attached']['js'][] = array( 'data' => array( 'formAutoSave' => array( - $form['#id'] => !empty($form['#autosave']['#changed']) ? $form['#autosave']['#changed'] : 0 + $form['#id'] => ctype_alnum($form['#autosave']) ? $form['#autosave'] : 1, ), ), 'type' => 'setting', diff --git a/core/misc/form.autosave.js b/core/misc/form.autosave.js index df10f2a..ef3fdf4 100644 --- a/core/misc/form.autosave.js +++ b/core/misc/form.autosave.js @@ -15,6 +15,7 @@ Drupal.behaviors.formAutoSave = { .attr('data-localStorage-changed', changed) // Attach garlic. .garlic({ + inputs: 'input[type!=file][type!=hidden], textarea, select', expires: 86400 * 30, conflictManager: { enabled: false @@ -30,18 +31,18 @@ Drupal.behaviors.formAutoSave = { * Override for garlic.js' localStorage key generation. * * garlic.js' getPath() implementation contains a lot of complexity, because - * they can't make any assumptions about the HTML of the form; because all - * forms in Drupal are generated by Form API, we can simplify a lot. - * We don't need to store the entire DOM node path, because each form item - * (as well as the form itself) is uniquely identified by its id attribute. + * they can't make any assumptions about the HTML of the form; because all forms + * in Drupal are generated by Form API, we can simplify a lot. + * We don't need to store the entire DOM node path, because each form item (as + * well as the form itself) is uniquely identified by its id attribute. * We also don't need to look at the current URL thanks to * drupalSettings.currentPath; without that, we could run into edge cases of * e.g. www vs. no-www URLs of the same site. - * Finally, it takes a "last modification" identifier into account, which - * allows us to ignore localStorage data that doesn't apply anymore to the - * latest version of the object that the form allows the user to edit. + * Finally, it takes a "last modification" identifier into account, which allows + * us to ignore localStorage data that doesn't apply anymore to the latest + * version of the object that the form allows the user to edit. * - * @param {jQuery} $element + * @param jQuery $element * A jQuery element of a form item. */ function generateGarlicLocalStorageKey ($element) { diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index 68dea10..aff7281 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -59,9 +59,7 @@ public function form(array $form, array &$form_state, EntityInterface $node) { $user_config = config('user.settings'); // Let the node form use localStorage to prevent data loss. - $form['#autosave'] = array( - '#changed' => isset($node->changed) ? $node->changed : NULL, - ); + $form['#autosave'] = isset($node->changed) ? $node->changed : TRUE; // Some special stuff when previewing a node. if (isset($form_state['node_preview'])) { diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php index 36baad2..c654759 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php @@ -43,6 +43,16 @@ function setUp() { * Creates a "Basic page" node and verifies its consistency in the database. */ function testNodeCreation() { + // Check that the autosaving functionality is present. + $this->drupalGet('node/add/page'); + $this->assertTrue(FALSE !== strpos($this->content, 'core/misc/garlic/garlic.js'), 'garlic.js is present.'); + $this->assertTrue(FALSE !== strpos($this->content, 'core/misc/form.autosave.js'), 'form.autosave.js is present.'); + $expected_settings = array( + 'page-node-form' => 1, + ); + $js_settings = $this->drupalGetSettings(); + $this->assertIdentical($expected_settings, $js_settings['formAutoSave'], 'formAutoSave JS setting for this form is 1.'); + // Create a node. $edit = array(); $langcode = LANGUAGE_NOT_SPECIFIED; diff --git a/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php index b461492..9f9ebb5 100644 --- a/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php @@ -54,6 +54,15 @@ function testPageEdit() { $actual_url = $this->getURL(); $this->assertEqual($edit_url, $actual_url, 'On edit page.'); + // Check that the autosaving functionality is present. + $this->assertTrue(FALSE !== strpos($this->content, 'core/misc/garlic/garlic.js'), 'garlic.js is present.'); + $this->assertTrue(FALSE !== strpos($this->content, 'core/misc/form.autosave.js'), 'form.autosave.js is present.'); + $expected_settings = array( + 'page-node-form' => $node->changed, + ); + $js_settings = $this->drupalGetSettings(); + $this->assertIdentical($expected_settings, $js_settings['formAutoSave'], 'formAutoSave JS setting for this form is correct.'); + // Check that the title and body fields are displayed with the correct values. $active = '' . t('(active tab)') . ''; $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active)); diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/AutoSaveTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/AutoSaveTest.php new file mode 100644 index 0000000..d804678 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Form/AutoSaveTest.php @@ -0,0 +1,80 @@ + 'Automatic form saving', + 'description' => 'Tests #autosave property to automatically save form data to localStorage.', + 'group' => 'Form API', + ); + } + + function setUp() { + parent::setUp(); + + $this->web_user = $this->drupalCreateUser(); + $this->drupalLogin($this->web_user); + } + + /** + * Checks if garlic.js and form.autosave.js are included, and in proper order. + */ + function checkJS($present = TRUE) { + $position1 = strpos($this->content, 'core/misc/garlic/garlic.js'); + $position2 = strpos($this->content, 'core/misc/form.autosave.js'); + if ($present) { + $this->assertTrue($position1 !== FALSE && $position2 !== FALSE && $position1 < $position2, 'garlic.js is included before form.autosave.js.'); + } + else { + $this->assertTrue($position1 === FALSE && $position2 === FALSE, 'garlic.js and form.autosave.js are not included.'); + } + } + + /** + * Ensures that JS settings and files #attached for #autosave are correct. + */ + function testAutoSave() { + // Simple #autosave === FALSE/NULL. + $this->drupalGet('form_test/autosave/disabled'); + $this->checkJS(FALSE); + $this->assertIdentical(array(), $this->drupalGetSettings(), 'No JS settings.'); + + // Simple: #autosave === TRUE. + $this->drupalGet('form_test/autosave/enabled-simple'); + $this->checkJS(); + $expected_settings = array( + 'form-test-autosave-enabled-simple-form' => 1, + ); + $js_settings = $this->drupalGetSettings(); + $this->assertIdentical($expected_settings, $js_settings['formAutoSave'], 'formAutoSave JS setting for this form is 1.'); + + // Advanced: #autosave === 569650842. + $this->drupalGet('form_test/autosave/enabled-advanced'); + $this->checkJS(); + $expected_settings = array( + 'form-test-autosave-enabled-advanced-form' => 569650842, + ); + $js_settings = $this->drupalGetSettings(); + $this->assertIdentical($expected_settings, $js_settings['formAutoSave'], 'formAutoSave JS setting for this form is 569650842.'); + } +} diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index f85fd6d..030ec5e 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -20,6 +20,27 @@ function form_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['form_test/autosave/disabled'] = array( + 'title' => 'Auto save disabled test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_autosave_disabled_form'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['form_test/autosave/enabled-simple'] = array( + 'title' => 'Auto save enabled (simple) test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_autosave_enabled_simple_form'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['form_test/autosave/enabled-advanced'] = array( + 'title' => 'Auto save enabled (advanced) test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_autosave_enabled_advanced_form'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['form-test/validate'] = array( 'title' => 'Form validation handlers test', 'page callback' => 'drupal_get_form', @@ -477,6 +498,36 @@ function form_test_validate_form_validate(&$form, &$form_state) { } /** + * Form constructor to test the #autosave property (disabled). + */ +function form_test_autosave_disabled_form($form, &$form_state) { + $form['test'] = array( + '#type' => 'textfield', + '#title' => 'Test', + ); + return $form; +} + +/** + * Form constructor to test the #autosave property (enabled, simple mode). + */ +function form_test_autosave_enabled_simple_form($form, &$form_state) { + $form = form_test_autosave_disabled_form($form, $form_state); + $form['#autosave'] = TRUE; + return $form; +} + +/** + * Form constructor to test the #autosave property (enabled, advanced mode). + */ +function form_test_autosave_enabled_advanced_form($form, &$form_state) { + $form = form_test_autosave_disabled_form($form, $form_state); + $last_modification_timestamp = 569650842; + $form['#autosave'] = $last_modification_timestamp; + return $form; +} + +/** * Form constructor to test the #required property. */ function form_test_validate_required_form($form, &$form_state) {