4

Note: This has an accepted bounty, but no accepted answer because I still want to know how to add a node save confirm dialog using drupal code, not javascript. If you can write up a good answer on that I'll definitely accept it, and probably open and award you another bounty for your trouble.

Trying to implement part of the functionality mentioned in How can I add or remove links to nodes in a list depending on the values of some of the fields I would like to give users a warning, if the node they are submitting would end up being a stub.

I've been trying to implement this by following lots of examples related to confirm_form, especially this one.

I'm think my issue must be that I'm starting from hook_form_alter, so in hook_form_alter it doesn't work very well to return an entirely different form (the confirm_form). Realizing that hook_form_alter must be the wrong starting place though, doesn't help me know where I should start.

So here's what I need to be able to do:

When the user clicks 'Save' on a node edit form, I need to then evaluate whether the field values meet some criteria, and then provide the user a warning, that saving in the current state will have some effect on the way the node is displayed and give them an option to confirm or cancel.

Here is one of the many iterations of what I've tried:

function MYMODULE_form_node_form_alter(&$form, &$form_state, $form_id) {

    if (in_array(strstr($form_id, '_', TRUE), $GLOBALS['conf']['publication_types'])) { // only work on the publication forms
        if( !isset($form_state['storage']['submitted'] )) { // if the form hasn't previously been submitted
            $form['actions']['submit']['#submit'] = array('MYMODULE_node_form_submit');

            return $form;
        } else { // this is the confirmation step
            $form = _MYMODULE_build_confirm_form($form_state);
            return $form;
        }

    }
}

/**
 * invoke hook_node_validate
 */
function MYMODULE_node_validate($node, $form, &$form_state) {
    if (MYMODULE_is_stub($node)) {
        $form_state['storage']['stub'] = True;
        if ($form_state['triggering_element']['#value'] === 'Preview') {
//            dpm($form_state);
            drupal_set_message($message = 'If saved in this form, your publication will not have enough information to merit its own publication page.', $type = 'warning', $repeat = TRUE);
        }

    } else {
        $form_state['storage']['stub'] = False;
    }
}

function MYMODULE_node_form_submit($form, &$form_state) {

    if( !isset($form_state['storage']['submitted'] )) // this is the first time this has been submitted
    {
        // not confirmed yet.
//        $form_state['storage']['confirm'] = TRUE; // this will cause the form to be rebuilt, entering the confirm part of the form

        $form_state['storage']['submitted'] = TRUE;
        $form_state['rebuild'] = TRUE;
    }

    else // if we get here, confirm should have been clicked on the confirm form
    {
        node_form_submit($form, $form_state);

    }
}

function _MYMODULE_build_confirm_form(&$form_state) {
    $path = current_path();


    $form['processed'] = array(
        '#type' => 'hidden',
        '#value' => 'true'
    );
    return confirm_form($form, t('Do you really want to save a publication stub?'), $path, 'Confirm', 'Cancel');
}

This code doesn't work. When you click save gets through returning the form created by _MYMODULE_build_confirm_form, but in the processing afterwards, it seems clear that the system is expecting a node edit form and doesn't like what it is getting instead. The confirmation form is never displayed. I've tried using the node edit form as an input into confirm_form and the actions of the node edit form show up instead of the confirm_form buttons.

I'm at the end of my rope, how do I do this? I guess I need to override the menus so that I can run a callback function to determine which form should be displayed at node/%id%/edit before it gets to the stage of altering the form that has been specified? If so, how can I do that? I just need a solution that will use my existing MYMODULE_is_stub() feature to determine whether there needs to be a confirmation form, and then display the warning to the use and ask them to confirm or cancel. Please help!

14
  • Are you familiar with jQuery? You could do it with jQuery and make it that when the user hovers over the save button or clicks the save button for the first time, or when the save button is in view, if field X and/or field Y is empty, show popup message. You would have to add the jQuery to your Drupal admin theme. The default admin theme is Seven.
    – No Sssweat
    Jun 7, 2016 at 5:09
  • The validation would be a little bit complicated with jQuery (abstract field has at least 300 characters or the related website field has at least one url added or the public files field has at least one file in it.). I'm already going to need to use my is_stub function to strip out the links from node teasers when they should be stubs, so it would be nice to just maintain that code in one place, and it would be nice to make sure the users at least have to stop and think briefly until they've memorized the form. A modal popup would work, but a hover would be less useful.
    – UltraBob
    Jun 7, 2016 at 5:20
  • Please remove better or good way from the question. Those are subjective terms.
    – apaderno
    Jun 7, 2016 at 7:04
  • I've removed them everywhere where it isn't me expressing opinion.
    – UltraBob
    Jun 7, 2016 at 7:07
  • Have you seen Is there a way to alter a node save confirmation form??
    – berliner
    Jun 21, 2016 at 21:38

3 Answers 3

4
+50

You can also add the Javascript inside a custom module, using your hook_form_alter() from the question as a base:

function MY_MODULE_form_node_form_alter(&$form, &$form_state, $form_id) {

    if (in_array(strstr($form_id, '_', TRUE), $GLOBALS['conf']['publication_types'])) { // only work on the publication forms
        $form['#attached']['js'][] = drupal_get_path('module', 'MY_MODULE') . '/js/cool.js';
    }
}

In this version your cool.js file will go in the js folder in your module.

cool.js

(function ($) {

    (function Forever(){ // we create a function called Forever

        $('html').ready(function(){

            $('.cke_editable.cke_editable_themed.cke_contents_ltr.cke_show_borders').hide();

            var characters = $('#edit-field-doc-abstract-und-0-value').val().length;
            var field1 = $('#edit-field-doc-website-und-0-url').val();
            var field2 = $('#edit-field-doc-pub-files-und-0-upload').val();

            if (characters < 300 || field1 == '' || field2 == '') {
                var submit_button = document.getElementById("edit-submit");
                try {
                    submit_button.type = 'newId';
                } catch(e) {

                }

                $('#edit-submit').attr("id","msg").css({"width": "34px"});
            } else {
                var fake_button = document.getElementById("msg");
                fake_button.type = 'submit';
                $('#msg').attr("id","edit-submit");
            }

        });

        setTimeout(Forever, 1);

    })();



})(jQuery);


(function ($) {

    $( document ).ready(function() {



        $("#msg").click(function(){

            var characters = $('#edit-field-doc-abstract-und-0-value').val().length;
            var field1 = $('#edit-field-doc-website-und-0-url').val();
            var field2 = $('#edit-field-doc-pub-files-und-0-upload').val();

            if (characters < 300) {
                var a = '<li>Abstract is less than 300 characters.</li>';
            }
            else { a = ''; }
            if (field1 == '') {
                var b = '<li>No website to go for more information.</li>';
            }
            else { b = ''; }
            if (field2 == '') {
                var c = '<li>No public files available for download.</li>';
            }
            else { c = ''; }


            div1 = $("<div class='blackbg'></div>").css({"background-color": "rgba(0, 0, 0, 0.5)", "width": "100%", "height": "100%", "position": "absolute", "z-index": "5", "left": "0"})
            div = $("<div class='msgbox'><b>In its current form, your publication doesn't contain enough information to merit its own publication page because:</b><br/>" + a + b + c + "<br/><br/><span class='yes'> Save </span><span class='no'> Edit </span></div>").css({"width": "350px", "height": "180px", "position": "fixed", "top": "0", "bottom": "0", "left": "0", "right": "0", "margin": "auto", "z-index": "10", "background-color": "#cfd2cc", "padding": "10px"});

            $("#page").prepend(div1).prepend(div);
            $('.yes').css({"background-color": "#89c640", "padding": "5px 10px 5px 10px", "font-weight": "bold", "cursor": "pointer"});
            $('.no').css({"background-color": "#d2cd4c", "padding": "5px 10px 5px 10px", "font-weight": "bold", "cursor": "pointer", "margin-left": "10px"});

            $(".yes").click(function(){
                var fake_button = document.getElementById("msg");
                fake_button.type = 'submit';
                $('#msg').attr("id","edit-submit");;
                $('#edit-submit')[0].click();

            });

            $(".no").click(function(){

                $('.blackbg').remove();
                $('.msgbox').remove();


            });

        });


    });

})(jQuery);

Result

After clicking the save button you will get:

enter image description here

Caviat: The character count of the abstract field will work only if you are not using CKEditor for the abstract field.

12
  • I'm trying desperately to test this before the bounty is required to be awarded. I got updated the characters field1 and field2 id values with mine (var characters = $('#edit-field-doc-abstract-und-0-value').val().length; var field1 = $('#edit-field-doc-website-und-0-url').val(); var field2 = $('#edit-field-doc-pub-files-und-1-upload').val();) in 2 places. It's still not doing anything when I submit. Is there anything else I should be changing?
    – UltraBob
    Jun 29, 2016 at 8:07
  • I should probably mention I'm using media for the files field
    – UltraBob
    Jun 29, 2016 at 8:07
  • @UltraBob did you replace it in 2 places? there are 2 of var characters
    – No Sssweat
    Jun 29, 2016 at 8:08
  • Yes, I mentioned that I had, but thanks for checking.
    – UltraBob
    Jun 29, 2016 at 8:09
  • Maybe you need to use jQuery Update module and assign jquery 1.10 to the admin section
    – No Sssweat
    Jun 29, 2016 at 8:11
1

When the user clicks 'Save' on a node edit form, I need to then evaluate whether the field values meet some criteria, and then provide the user a warning, that saving in the current state will have some effect on the way the node is displayed and give them an option to confirm or cancel.

You could get pretty close to what you're trying to do by simply (and only) using the Rules module. Some more details about this approach:

  • Use Rules Event "Before saving content".
  • Use as many Rules Conditions as needed, for any of your fields you want to validate.
  • Use Rules Action "Set a Drupal message*" (of type "error") for any validations that fail.

However, be aware that using Rules Event "Before saving content", you do not prevent the node from being saved. So with a rule as described above, you only show an extra error message when saving the node. This is true for either creating new nodes, or for updating existing nodes. There are a few approaches to somehow get around that node being saved anyway, such as:

  • Delete the node that just got created (or updated). Obviously this destroys all data entered before saving the node.
  • Unpublish the node, which avoids that all data entered before saving the node are destroyed. And wait for something to happen afterwards to get the node published later on.

To continue with the approach to unpublish a node that fails a validation, have a look at this rule (in Rules export format):

{ "rules_field_validations_before_saving_nodes" : {
    "LABEL" : "Field validations before saving nodes",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules" ],
    "ON" : { "node_presave--article" : { "bundle" : "article" } },
    "IF" : [
      { "entity_has_field" : { "entity" : [ "node" ], "field" : "field_optionlist" } },
      { "data_is" : { "data" : [ "node:field-optionlist" ], "value" : "C" } },
      { "NOT data_is" : { "data" : [ "node:field-free-format-text" ], "value" : "ignore" } },
      { "NOT user_has_role" : {
          "account" : [ "site:current-user" ],
          "roles" : { "value" : { "4" : "4" } }
        }
      }
    ],
    "DO" : [
      { "node_unpublish" : { "node" : [ "node" ] } },
      { "drupal_message" : {
          "message" : "The option list value = \u0022[node:field-optionlist]\u0022 is no longer allowed for nodes of content type \u0022[node:type]\u0022. Its status is automatically changed to \u003Cstrong\u003EUnpublished\u003C\/strong\u003E until an appropriate corrective action has been applied.\u003Cbr \/\u003E\u003Cbr \/\u003E\r\n\r\nTo still use that value: (a) enter \u0022\u003Cstrong\u003Eignore\u003C\/strong\u003E\u0022 in the field \u0022Free Format Text\u0022 to overrule this validation, or use another allowed value for option list and (b) change the node status back to \u003Cstrong\u003EPublished\u003C\/strong\u003E again.",
          "type" : "error"
        }
      }
    ]
  }
}

Some more details to further explain the above above sample:

  • The Rules event is "After saving new content", but as a sample I've limited it to content type "article" only (omit or adapt that extra verification as needed).
  • In my case I have a selection list field with machine name field_optionlist.
  • This field actually has 3 possible values, like so:
    • A (=Advanced).
    • B (=Basic).
    • C (=Custom).
  • As a sample, I decided to add these Rules Conditions (validations), in the order specified:
    • a validation to ensure that this rule only applies for nodes that have a field with machine name field_optionlist.
    • a validation about attempts to save a node where "The selected value" of field_optionlist equals Custom.
    • a validation to check if the text field with machine name field_free_format_text does NOT contain the exact value (case sensitive!) of ignore.
    • a validation to check that the user editing the node does NOT have a role with rid = 4 (change to whatever rid fits for your case), which could be like a content editor or so (so that users with special roles can still edit such nodes without being stuck by this rule).
  • If all these Rules Conditions are satisfied, then the Rules Action will perform 2 actions:

    • mark the node as unpublished.
    • display a message which looks like so (feel free to change that to whatever you want):

    The option list value = "Custom" is no longer allowed for nodes of content type "article". Its status is automatically changed to Unpublished until an appropriate corrective action has been applied.

    To still use that value: (a) enter "ignore" in the field "Free Format Text" to overrule this validation, or use another allowed value for option list and (b) change the node status back to Published again.

If you want, you can use the Rules UI to import the above rule in your own site. But before doing so, make sure you have defined 2 similar fields (with machine names field_optionlist and field_free_format_text). As a variation, adapt the machine names to similar fields you already have (attention about field-optionlist versus field_optionlist though, same for the 2nd field, important!).

If you're not familiar (enough/yet) with Rules, checkout the 32 (!!!) great, and free, video tutorials Learn the Rules framework, organized in 7 chapters.

Voilà, the above solution depends on Rules, nothing but Rules. Read on if you're in for adding some additional modules (and further refine it all) ...

Refinement

If you have a lot of validations to implement, you could simply add as many similar rules as you want/need, or you might want to add the Conditional Rules module to further enhance this approach. Using this extra module, you could consolidate multiple rules in a single one, which IMO makes these rules easier to maintain.

Heading for the golden cradle

If you want to take this approach (using Rules) a step further, you should have a look at the Rules Forms Support module. Some details from its project page:

Rules Forms Support provides a Rules based method for controlling forms and their elements. The module can be used to alter any form provided by Drupal core or contributed modules. Change element titles, descriptions, weights, and more, or validate form data and set form errors. Rules forms saves time and cost by affording site builders the ability to monitor and alter forms without the need to implement a custom module.

Have a look at its Community Documentation to get started with it.

2
  • Pierre! Thank you so much for this detailed answer! None of the answers was quite what I wanted, I want to be able to use my MYMODULE_is_stub() function for all instances if checking whether the document should be a stub. Your answer doesn't quite work for me for a couple of reasons: 1) I can't delete or unpublish these entries, they may already be on the site either as full entries or stubs, removing them from the site, and especially trashing the data is unacceptable. 2) I was able to implement the other answer without installing a module I'm not already using.
    – UltraBob
    Jun 29, 2016 at 8:34
  • I also intend to try modifying the query in the other answer to work without jquery update.
    – UltraBob
    Jun 29, 2016 at 8:38
0

You can know which button has been clicked by the user with $form_state['triggering_element']

function MYMODULE_node_validate($node, $form, &$form_state) {
 if ($form_state['triggering_element']['#value'] === 'Preview') {
   if (MYMODULE_is_stub($node)) {
     dpm("This would be a stub");
   } else {
     dpm("This would not be a stub.");
   }
 }
}
5
  • Ok, so I can actually limit validation to submit only. Thanks, that is handy. Any idea on how add the confirm_form in place of dpm("This would be a stub");?
    – UltraBob
    Jun 7, 2016 at 22:24
  • There is a very good example in Drupal.org.
    – Jimmy Ko
    Jun 7, 2016 at 22:45
  • I read that example, and I'm sure it's because I'm new to Drupal development, but it is not clear how I integrate that with the existing content entry forms.
    – UltraBob
    Jun 7, 2016 at 23:58
  • @UltraBob have a look at the core node module. node_form_delete_submit is also a form submit callback (of the node delete button on the node edit page) and it redirects to another path where a new form is shown. However that is part of the form submit part. You should not do any redirects in validation as shown here. Validation is just to make sure all required properties are there and within valid ranges. Jun 9, 2016 at 9:13
  • Thanks, I've looked at it, but I'll look again. I'm a bit frazzled. I'm not actually doing any redirect in the validation hook, just setting information on whether the node qualifies to be have its own page or not.
    – UltraBob
    Jun 9, 2016 at 9:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.