HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Reload Page After Submit

This discussion was created from comments split from: Code Snippets.
«1

Comments

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Hi @R_J, I tried that code snippet ("This one put at the end of a view will force a reload of the page after a popup has been closed.") but it didn't work for me.

    What I see is that when a popup form view is started (the usual way with the Popup class parameter) the form/view indeed shows in a Vanilla popup but when the form is submitted the popup state of the underlying windows is not removed. What I am looking for is a function that closes the popup without the user pressing the X/close button.

    I was hoping your snippet would work, but it doesn't for me. I get control in the code that initiated (rendered) the view but any further display is in the same popup state. I even tried wasteful redirect(url) -- didn't work...

    All suggestions are appreciated

  • R_JR_J Ex-Fanboy Munich Admin

    I'm not sure why I ever did that. :mrgreen:

    The most easiest way should be a simple redirect

    ...
            if ($sender->Form->authenticatedPostBack()) {
                // Process the form values here
                // ...
    
                // Finally reload the page
                redirect(url('/someplugin/somemethod'));
            }
    
  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Yea, that was my thought, as I wrote "I even tried wasteful redirect". But the popup state was still on.

    So here is what I think happens and is missing, and what I'm thinking on trying when I get a minute. The popup is created through the Popup class that initiates a js that initiates the popup state on the client side (browser). Simple enough. I'm thinking that I could create an "empty" view that includes a specific js that closes the popup and of course return to the caller (server). Let's say it is the "closepopup" view. So what one needs to do to close the popup is simply render "closepopup" and then continue whatever code path on the server assured that the next render will not be in a popup state. The js itself would undo what the popup js does (the popup layers, border, etc.). A complete solution would be to have a class with a name like "unpopup" that could be added to any rendered view (similar to the Popup class).

    Thoughts?

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭
    edited November 2017

    And perhaps it is worth noting that this "persistent" popup state is in a plugin setup (so possibly the dashboard controller behaves differently...)

  • R_JR_J Ex-Fanboy Munich Admin

    Why don't you want to reload the page that opened the popup?

    It would be s much easier if you could share some code...

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    I'll try to share, it's in the intranet so it takes time to sanitize...

  • R_JR_J Ex-Fanboy Munich Admin

    Here is some short code which you might be able alter to show your problem:

    class.demonstration.plugin.php

    <?php
    $PluginInfo['demonstration'] = [
        'Name' => 'Demonstration',
        'Description' => 'This and that...',
        'Version' => '0.1',
        'RequiredApplications' => ['Vanilla' => '>= 2.3'],
        'SettingsPermission' => 'Garden.Settings.Manage',
        'SettingsUrl' => '/dashboard/settings/demonstration',
        'MobileFriendly' => true,
        'HasLocale' => true,
        'AuthorUrl' => 'https://open.vanillaforums.com/profile/r_j',
        'License' => 'MIT'
    ];
    
    class DemonstrationPlugin extends Gdn_Plugin {
        public function settingsController_demonstration_create($sender) {
            $sender->permission('Garden.Settings.Manage');
    
            $sender->addSideMenu('dashboard/settings/plugins');
            $sender->setData('Title', t('Demonstration Settings'));
    
            $sender->setData('Kokolores', $this->getUserMeta(0, 'Kokolores'));
    
            $sender->render($sender->fetchViewLocation('settings', '', 'plugins/demonstration'));
        }
    
        public function settingsController_popupTest_create($sender, $args) {
            $sender->permission('Garden.Settings.Manage');
    
            $sender->addSideMenu('dashboard/settings/plugins');
            $sender->setData('Title', t('Demonstration Popup'));
    
            decho($sender->Form->authenticatedPostBack());
    
            if ($sender->Form->authenticatedPostBack()) {
                $this->setUserMeta(0, 'Kokolores', $sender->Form->getFormValue('Kokolores'));
            }
            $sender->render($sender->fetchViewLocation('popuptest', '', 'plugins/demonstration'));
        }
    }
    

    views/settings.php

    <h1><?= $this->title() ?></h1>
    <div class="P">
        <?php
        decho($this->data('Kokolores'));
        ?>
    </div>
    <div class="P">
        <?= anchor(t('Popup Test'), '/settings/popuptest', 'Popup Button') ?>   
    </div>
    

    views/popuptest.php

    <h1><?= $this->title() ?></h1>
    <?php
    echo $this->Form->open(),
        $this->Form->errors(),
        $this->Form->label('Kokolores', 'Kokolores'),
        $this->Form->textBox('Kokolores'),
        $this->Form->close('Submit');
    
  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    First, I should state that I am running Vanilla Version 2.3.1.

    Since popup is initiated by js, I decided to check for js errors in the browser inspector and I saw these errors:

    Uncaught TypeError: gdn.informError is not a function
    at Object.error (jquery.popup.js?v=2.3.1:319)
    at Object.options.error (jquery.form.js?v=2.3.1:211)
    at c (jquery.min.js?v=2.3.1:4)
    at Object.fireWith [as rejectWith] (jquery.min.js?v=2.3.1:4)
    at k (jquery.min.js?v=2.3.1:6)
    at XMLHttpRequest.r (jquery.min.js?v=2.3.1:6)
    error @ jquery.popup.js?v=2.3.1:319
    options.error @ jquery.form.js?v=2.3.1:211
    c @ jquery.min.js?v=2.3.1:4
    fireWith @ jquery.min.js?v=2.3.1:4
    k @ jquery.min.js?v=2.3.1:6
    r @ jquery.min.js?v=2.3.1:6
    XMLHttpRequest.send (async)
    send @ jquery.min.js?v=2.3.1:6
    ajax @ jquery.min.js?v=2.3.1:6
    $.fn.ajaxSubmit @ jquery.form.js?v=2.3.1:257
    doAjaxSubmit @ jquery.form.js?v=2.3.1:890
    dispatch @ jquery.min.js?v=2.3.1:5
    v.handle @ jquery.min.js?v=2.3.1:5

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    I finally know how to recreate it on a modified version of your "demonstration" plugin and now know exactly which Vanilla function triggers the problem. Apparently going back from a popup "window" to another Vanilla place through the "redirect" function causes the popped up window to retain it's state.

    Now, there is a bypass to that problem by not using the "redirect" function unless one needs to redirect elsewhere...

    To try it out modify your demonstration plugin as follows:

    Change

            if ($sender->Form->authenticatedPostBack()) {
                $this->setUserMeta(0, 'Kokolores', $sender->Form->getFormValue('Kokolores'));
            }
    

    to

            if ($sender->Form->authenticatedPostBack()) {
                $this->setUserMeta(0, 'Kokolores', $sender->Form->getFormValue('Kokolores'));
                if ($sender->Form->getFormValue('Kokolores') == "nonsense") {
                    redirect('/dashboard/settings/demonstration');
                }
            }
    

    Note that if you change settings.php and remove "popup" from the button class the error disappears.

    Any ideas?

  • R_JR_J Ex-Fanboy Munich Admin

    Without testing: have you tried redirect(url('/dashboard/settings/demonstration'));?

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Tried that, that addition breaks the link. As I said, without the popup there is no error, so the link was correct in the first place.

  • R_JR_J Ex-Fanboy Munich Admin

    Yes, I've tried it and I see it now. The new page is displayed inside of an error message which echoes "Error200" while 200 is the response code for a successful request. Quite strange and I don't have an explanation for that...

    First thing which comes to my mind is using e.g. $this->settingsController_demonstration_create($sender);, but that would only feel appropriate if you manually "route" inside of the same controller.

    The next thing I think of, is why you have to do so? If you enter something in a form, wouldn't the user expect to see the results at least in some kind of overview? Why should he be sent to a completely different page?
    And/or: couldn't it be sufficient to simply present a different view like that:

            $view = $sender->fetchViewLocation('popuptest', '', 'plugins/demonstration');
    
            if ($sender->Form->authenticatedPostBack()) {
                $this->setUserMeta(0, 'Kokolores', $sender->Form->getFormValue('Kokolores'));
                if ($sender->Form->getFormValue('Kokolores') == "nonsense") {
                    $view = $sender->fetchViewLocation('success', '', 'plugins/demonstration');
                }
            }
    
            $sender->render($view);
    
    
  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    II thought about that, but it complicates the code significantly because now the form handling code has to recognize whether postback is from the main form or the popup form. It leads to non elegant code. What if I told you that the main form has two different pop ups? Now the code splits into three flows? Sure, each could be handled in a different function, but still, IMHO not elegant.

    There is a "workaround" - just not use the Popup. But this is really not answering the question of why there is a problem in the first place, and negates the designer user interface wishes.

    Note that clicking on the "X" on the popup closes it and displays the underlying form (settings.php in your demonstration). So I wonder, what does the "X" do to close the popup state and whether it is possible to do it from the "Submit" button while still giving control to the code that tests for the postback?

  • R_JR_J Ex-Fanboy Munich Admin

    The X just triggers a small JS event. With your browser tools, when inspecting that X, you should be able to track what is happening. I would guess that there is an event attached.

    You could AJAXify that a lot more and submit your form to an endpoint which in fact doesn't return any output but just echoes a result. Here is some code snippet I found in one of my plugins for returning something that is echoed nicely:

                echo json_encode(
                    [
                        'InformMessages' => sprintf(
                            t('Discussion "%s" could not be created'),
                            $postValues['Name']
                        )
                    ]
                );
    

    That might be part of the problem! What your plugin echoes is displayed as a notification bubble. When you "redirect" it might only load that content and echo that content in the bubble, hence that 200 status code! Okay, let me get my mind clear...

    A PHP redirect can only redirect by sending the redirect header so that when a page is requested, the redirection request can be obeyed.

    That's the reason why a redirect issued "somewhere" in the output will not work.


    Well then, back to the JavaScript solution...
    https://open.vanillaforums.com/discussion/comment/244649/#Comment_244649

    I tend to forget things quickly. As far as I can tell from the code it catches the closing event of the popup and attaches an action (the reload) to that event.
    Vanilla re-uses some jquery popup library. I'm sure you would be able to find some descriptions for that library. But what happens when you add that to your view and debug it?
    By the way: I guess it has to be attached to the view that contains the a.Popup element and not the view that is shown in the popup

  • R_JR_J Ex-Fanboy Munich Admin

    @rbrahmson just stumbled upon jsonTarget again. Remember? :wink:

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    I may be as forgetful as you are. Have been distracted from this issue and haven't dug into it. Hopefully I will lest I forget again;-)

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Hi @R_J , good find!
    Interestingly, after reviewining one of @hgtonight 's comments I looked at github and decided to try Popdown instead of Popup and it worked...

    I am not happy about this solution because I couldn't find any use of Popdown in Vanilla so I'm concerned about the durability of that solution.

    There is also this discussion that uses a different technique, possibly to achieve the same goal. I've used it in another plugin without really researching why and how it works:

    $Sender->Render('Blank', 'Utility', 'Dashboard');

    I think jsontarget yields promise. I've got to research how popup closes and mimic it to close the popup from php and only then render the other view. I'm being pulled by different things;-)

  • R_JR_J Ex-Fanboy Munich Admin

    @rbrahmson said:
    I am not happy about this solution because I couldn't find any use of Popdown in Vanilla so I'm concerned about the durability of that solution.

    And a pure coincidence gives a good hint who could be the best person to addresss that question to. :mrgreen:
    @charrondev?

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    I can confirm that either Popdown or "$Sender->Render('Blank', 'Utility', 'Dashboard');" work. At least the latter is used all over Vanilla...

    Still have to research how to use jsontarget to close the popup from within, because it is more direct.

    But, IMHO the most elegant solution would be to have a "pseudoclass" (same way Popup and Popdown are) which could be added to the button on the popped-up window. I envision that that class will be captured by Vanilla which will internally close the Popup and give control to whatever the button says (back to a form Postback or the url parameter - if specified). Assuming that class is called "Popclose" the following example of a classic flow will take place:

    1. "Main" window is displayed. It has a button which has the "Popup" pseudoclass which invokes "Form B".
    2. "FormB" has the following (only the first is required):
      2.1 regular "X" on the top right corner (which today closes it and renders "Main").

      2.2 A "Submit" button which processed the form. The button has the "Popclose" pseudoclass. When clicked Vanilla captures the "Popclose" class, closes the popup (however it does it, JS, JsonTarget - I don't care) and gives control to the code that opened FormB (and as is the norm Postback is triggered ).

      2.3 A "ExampleUrl" button which has both the "Popclose" pseudoclass as well as a url parameter. Vanilla closes the Popup and redirects to the URL (which could be on an external site).

    That, I posit, is elegant.

    @charrondev, @R_J, @hgtonight - anyone concurs?;-)

  • R_JR_J Ex-Fanboy Munich Admin

    I assume $sender->jsonTarget('', '', 'Refresh'); is already the answer...

    Just some comments to your vision:

    @rbrahmson said:
    1. "Main" window is displayed. It has a button which has the "Popup" pseudoclass which invokes "Form B".
    2. "FormB" has the following (only the first is required):
    2.1 regular "X" on the top right corner (which today closes it and renders "Main").

    Pressing "x"/leaving that popup form doesn't render anything. It removes the popup (with JavaScript)

    @rbrahmson said:
    2.2 A "Submit" button which processed the form. The button has the "Popclose" pseudoclass. When clicked Vanilla captures the "Popclose" class, closes the popup (however it does it, JS, JsonTarget - I don't care) and gives control to the code that opened FormB (and as is the norm Postback is triggered ).

    That is not how it works. Let's look at some pseudo (not even that) code

    public function MainWindow {
        Display "index" view with link to FormB popup
        // No Form handling here!
    }
    
    public function FormBWindow {
        Handle postback
            Cause refresh on postback
        Display view with form
    }
    

    Main window doesn't have a chance to get information on the postback. It simply knows about its own view which only contains a link and no trace of a form.

    FormB has to handle all Form related code. If data shown on Main is changed, reloading Main should be triggered. If not, a simple removal of that FormB popup would be enough. I would try $sender->jsonTarget('.Overlay', '', 'Remove'); for that...

    @rbrahmson said:
    2.3 A "ExampleUrl" button which has both the "Popclose" pseudoclass as well as a url parameter. Vanilla closes the Popup and redirects to the URL (which could be on an external site).

    Looking at the global.js I see the following code:

    switch (item.Type) {
    case 'AddClass':
        $target.addClass(item.Data);
        break;
    case 'Ajax':
        $.ajax({
            type: "POST",
            url: item.Data
        });
        break;
    case 'Append':
        $target.appendTrigger(item.Data);
        break;
    case 'Before':
        $target.beforeTrigger(item.Data);
        break;
    case 'After':
        $target.afterTrigger(item.Data);
        break;
    case 'Highlight':
        $target.effect("highlight", {}, "slow");
        break;
    case 'Prepend':
        $target.prependTrigger(item.Data);
        break;
    case 'Redirect':
        window.location.replace(item.Data);
        break;
    case 'Refresh':
        window.location.reload();
        break;
    case 'Remove':
        $target.remove();
        break;
    case 'RemoveClass':
        $target.removeClass(item.Data);
        break;
    case 'ReplaceWith':
        $target.replaceWithTrigger(item.Data);
        break;
    case 'SlideUp':
        $target.slideUp('fast');
        break;
    case 'SlideDown':
        $target.slideDown('fast');
        break;
    case 'Text':
        $target.text(item.Data);
        break;
    case 'Trigger':
        $target.trigger(item.Data);
        break;
    case 'Html':
        $target.htmlTrigger(item.Data);
        break;
    case 'Callback':
        jQuery.proxy(window[item.Data], $target)();
        break;
    

    Based on that I would say that you can use $sender->jsonTarget('example.com', '', 'Redirect');

    @rbrahmson said:
    That, I posit, is elegant.

    @charrondev, @R_J, @hgtonight - anyone concurs?;-)

    From my understanding the View in MVC should be without any logic. I would prefer to see the action which happens after form submit in the Controller, not encoded in a CSS class of a link in the view.

    So given that the jsonTarget works as I understand it, I would say it is way more elegant. I have tried to alter that small demo plugin from above and that piece of code does exactly what I expect and what I assume that you are looking for:

            $view = $sender->fetchViewLocation('popuptest', '', 'plugins/demonstration');
    
            if ($sender->Form->authenticatedPostBack()) {
                $this->setUserMeta(0, 'Kokolores', $sender->Form->getFormValue('Kokolores'));
                if ($sender->Form->getFormValue('Kokolores') == "nonsense") {
                    // Whatever
                }
                $sender->jsonTarget('', '', 'Refresh');
            }
    
            $sender->render($view);
    
Sign In or Register to comment.