Remove submitted state in submission handler - forms

I am trying to reset a form so that it appears to Drupal 8 that it hasn't been submitted. So far I have been unable to do this, as I cannot find any available methods (setSubmitted() hardcodes it to TRUE without a FALSE option). The reason is this isn't a full submit, but a submit of one field after which I would like the user to be redirected to another page that has another form, and I would like this secondary form to use the value obtained in the first step.
In the submit handler for the first part I use this to redirect:
$form_state->setRedirect('my.route', [], []);
And this works, but when the form reaches the second form (it seems) that the second form thinks it is a submission. As a result any submit buttons I add to the second form seem to make it auto-submit, and this breaks my user journey.
In the submit for the first part I have tried:
$form_state->setRebuild(TRUE);
$form_state = new FormState();
unset($form_state);
Tried the above in various configurations to no avail. They all prevent/ignore the setRedirect call that I make afterwards. The reason I want/need to do it this way is I want to preserve the POST method used.

Do you want to obtain something similar to what core search module does? It has simple SearchBlockForm that sends data to more complex SearchPageForm.
SearchBlockForm uses GET method (though you may use POST):
$form['#method'] = 'get';
and has no id and token fields:
function search_form_search_block_form_alter(&$form, FormStateInterface $form_state) {
$form['form_build_id']['#access'] = FALSE;
$form['form_token']['#access'] = FALSE;
$form['form_id']['#access'] = FALSE;
}
BTW, the last change allows you to avoid running submit callbacks.
Hope this helps.

Related

Detecting if browser extension popup is running on a tab that has content script

There a few similar questions, but none of them have really gotten at what I'm asking.
I have a browser action popup. In the popup, I want to display settings if you're on a page where the content script has been injected (i.e., any page that matches the matches key within the content_scripts in the `manifest).
If I'm on a page that doesn't match the content_scripts matches pattern (and so wasn't injected), I just want to display a generic message "this plugin activates when you're on so-and-so sites".
What is the cleanest way to do it, without adding any unnecessary permissions?
It seems like one option is sending a message to a content script in the active tab, and seeing if I get a reply, but that seems really.. hacky. I should be able to know just based on a regex if I'm on one of the domains that matches my content script.
I'm looking for something that works in both manifest v2 and v3, btw.
TL;DR;
What's the simplest way to display a "you're on a page that matches your content_script" or "you're not on a page that matches your content_script" in a browser_action popup?
I build chrome extensions full time for an agency and have had projects where I needed to do exactly what you're asking.
The solution can be implemented w/o any permissions whatsoever. I built mine locally with an empty array for permissions. (for mv3)
for popup.html just create 2 divs and have them default to display none.
<div id="unsupported" style="display: none;">Ooops! This is not a supported site.</div>
<div id="supported" style="display: none;">Wohoo! This is a supported site!!!!!</div>
for your script.js, wait till the popup loads then query the active tab in the current window and get that tab's ID to send a message directly to it. If the tab is supported with a content script, it will send a true response (see last code snippet). If it wasn't supported, it will be an 'undefined' response.
async function setUI() {
let tabData = await chrome.tabs.query({ active: true, currentWindow: true })
let tabId = tabData[0].id // tabs.query returns an array, but we filtered to active tab within current window which yields only 1 object in the array
chrome.tabs.sendMessage(tabId, {
'message': 'isSupported'
}, (response) => {
console.log(response)
// response will be true if the message was successfuly sent to the tab and "undefined" if the message was never received (i.e. not supported w/ your content script)
if (response) return showSupportedHTML()
// else
showUnsupportedHTML()
})
}
function showSupportedHTML() {
document.querySelector('#supported').style['display'] = ''
}
function showUnsupportedHTML() {
document.querySelector('#unsupported').style['display'] = ''
}
window.addEventListener('DOMContentLoaded', () => {
setUI()
})
Lastly, in your content script, add a message listener to receive the message 'isSupported' that comes in from your content script. If the content script receives that message, have it send a response back with 'true'.
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.message == 'isSupported') {
console.log('run')
sendResponse(true)
}
})
Now, this of course only works for manifest v3 because as far as I know you can't use chrome.tabs.query for mv2. However, I recommend this solution as I've implemented pretty much this exact same code in other projects for clients and it's never had any issues.
I could look into a solution for mv2, though using the "activeTab" permission would be the right way to do it, I believe. Now, if you really don't want to go that route then you could implement a rather hacky solution. For example, you could use window 'focus' and window 'blur' events to see when a user has entered or left a tab. Then set a local storage variable every time a user enters / leaves a supported page. The order of operations for blur and focus is always blur => focus. So, when the blur event occurs you set a local storage variable to false. However, if you leave a supported tab for another supported tab then the 'focus' event will trigger immediately afterwards so you can set that same storage variable back to true.
Now, your content script will load after the tab has been focused so you'll need to add a function for when the page loads. You can run something like document.hidden and if that returns true, do nothing because the user already left this tab. If it returns false, then the user is still on the tab and you can set your local storage variable to true.
When the user opens the popup, you'll check that local storage variable and if its true or false, you can set the UI accordingly.
Let me know if the mv2 solution made sense or sounds too hacky. Happy to look into it more! :)
edit: Here is the code for mv2, I tested it and it does work and without any permissions, other than storage which is not an invasive permission.
Script.js for the mv2 popup:
async function setUI() {
chrome.storage.local.get(['isSupported'], function (response) {
console.log(response['isSupported'])
// response will be true if the message was successfuly sent to the tab and "undefined" if the message was never received (i.e. not supported w/ your content script)
if (response['isSupported']) return showSupportedHTML()
// else
showUnsupportedHTML()
})
}
function showSupportedHTML() {
document.querySelector('#supported').style['display'] = ''
}
function showUnsupportedHTML() {
document.querySelector('#unsupported').style['display'] = ''
}
window.addEventListener('DOMContentLoaded', () => {
setUI()
})
code for the content script in mv2:
if (!document.hidden) chrome.storage.local.set({'isSupported': true})
window.addEventListener('blur', () => {
console.log('left site')
chrome.storage.local.set({'isSupported': false})
})
window.addEventListener('focus', () => {
console.log('entered site')
chrome.storage.local.set({'isSupported': true})
})
Let me know if you have any additional questions.
Disclaimer: I have no prior browser extension development experience and am just going by the docs. I might be spouting nonsense or giving an answer that is plainly against your requirements, but that would be out of ignorance and not malicious intent. If you find my answer problematic, comment, or cast a vote and move on.
According to MDN, the activeTab permission allows to read the active tab's Tab.url property. One solution could be to request that permission, and then use that API to get the active tab's URL, and then use the same regex from the manifest.json's matches property to test for a match, and then use that information to modify your extension's browser_action UI.
You should be able to read the matches property from the manifest file via the .runtime.getManifest() API. MDN docs, chrome docs.
Snippet to get active tab in a background script: tabs.query({active: true}). (link to MDN docs). A content script should instead use tabs.getCurrent and the Tab.active property of the resolved result.
If you don't want to request the activeTab permission, what you're suggesting with the message-passing between the browser_action scripts and the content scripts might be the right way to go, but I don't know for a fact. The tabs.onActivated event would probably be useful with this approach. Note that to send a message from a background script to a content script, you need to use tabs.sendMessage (MDN docs, chrome docs) instead of runtime.sendMessage.
Another possible (maybe?) approach would be to listen for the tab change in the content script and then send the notification message from the content script to the extension's background scripts via the onfocus event (or similar events), and runtime.sendMessage.
If you go with a messaging-related approach, you might want to put a condition in the content script to only do messaging if the content script is in the top frame of the tab (Ie. iframes don't do messaging), since only one frame of the tab really needs to do this kind of messaging when the active tab changes, and content scripts can be applied to all frames in a browsing context.
Of these possible solutions I can think of, I don't know which is best for you, since you want both minimal permission requirements and a simple/clean approach, and each seems to be a tradeoff.

How to moving fields in forms of ncurses

I've written a program in C using ncurses and specially forms. I need that a particular field of my form moves as I'm filling the form. I tried move_field, but it doesn't work.
Here is how I wrote it :
if (typact==ADSD && rowc>rowg )
{
move_field(field[ietg],rowg=rowc,colg);
refresh();
}
I'm sure that the move_field is executed (I use xCode for debugging my program). I presume that refresh is not sufficient. I tried also placing move_field between unpost_form and post_form like this:
if (typact==ADSD && rowc>rowg /* && !field_status(field[ietg]) */ )
{ unpost_form(my_form);
move_field(field[ietg],rowg=rowc,colg);
post_form(my_form); refresh();
}
but it doesn't work once again. The form is erased and re-posted without the texts I have written and the field is always in the same place.
How could I use move_field?
The manual page says
The function move_field moves the given field (which must be disconnected) to a specified location on the screen.
You can disconnect a field by retrieving the current list of fields with form_fields (and its length using field_count), removing the field from that list and updating the list using set_form_fields.
When using move_field, you must also (temporarily) unpost the form with unpost_form. Otherwise, move_field returns E_POSTED (The form is already posted). After moving the field, use post_form to get the form-driver to work with the updated form.
The test/move_field.c file in ncurses sources provides an example of these calls.

Event xforms-model-construct-done behaviour

In my form I would like to call web service after to form is loaded after publishing. I've created custom XBL control for it, where I have :
<xf:group id="component-group">
<xf:action ev:event="xforms-enabled" ev:target="component-group">
<xf:send ev:event="xforms-enabled" submission="my-submission"/>
</xf:action>
</xf:group>
But it doesn't work as expected : my submission is sent everytime when I add new element in FormBuilder or change a name of some other controls. Generally speaking submission is sent when my form is changing in some way.
Now I want submission to be sent ONLY when I publish my form and someone would open it to fill (and of course when I press "Test" in FormBuilder, but I guess it's the same as filling form after publishing).
I was trying something like this :
<xf:group id="component-group">
<xf:action ev:event="xforms-model-construct-done" ev:target="component-group">
<xf:send ev:event="xforms-model-construct-done" submission="my-submission"/>
</xf:action>
</xf:group>
Unfortunately it's not working, this way submission is not sent at all. Any thoughts ?
This is due to the fact that XBL components are live at design time too. So you need a way to test whether the component is running within Form Builder or not.
There should be a function for this, really, but there isn't (I added this to the list of functions we should add to the API here). You can do:
<xf:group id="component-group">
<xf:var name="fr-params" value="xxf:instance('fr-parameters-instance')"/>
<xf:action
event="xforms-enabled"
target="component-group"
if="not($fr-params/app = 'orbeon' and $fr-params/form = 'builder')">
<xf:send submission="my-submission"/>
</xf:action>
</xf:group>
A few minor comments:
you don't need to (in fact shouldn't) place event attributes on nested actions
you don't even need the ev prefix

Determine caller in Installable Script

I've create an installable script which creates forms on the fly for 2 to 3 party verification. Since these forms are filled out asynchronously, and often in batches, it is important to determine which form called the function.
First the response form is created, then a trigger is installed.
CreateForm function:
function createForm(title){
var form = FormApp.create(title)
//set parameters for new form
form.setCollectEmail(true);
form.setShowLinkToRespondAgain(false);
...
return form;
};
InstallTrigger function:
function InstallTrigger(form, funct) {
var trigger= ScriptApp.newTrigger(funct)
.forForm(form)
.onFormSubmit()
.create()
return trigger.getUniqueId();
};
During the initial form submit, a function called createMessage() creates an email based on responses and runs the two scripts above. The email contains a link to the newly created form to await approval. To that point, everything works fine.
The problem comes when the newly installed trigger fires. I haven't found a way to set the trigger source as a variable. I'm sure that it has to do with an event, but the documentation on events doesn't go too far into forms from what I can see. I read that it can be done (Understanding Events). I just don't quite understand where (e) is defined on an installable script. Would it be in the install function or some other place?
Thanks again Serge inas for the walkthrough.
(e) is defined when the function defined. In my example above, the InstallTrigger() function adds a trigger to the form passed to it. The script that executes on submission is also passed to it. So, the installation of the trigger comes from a pre-installed onSubmit() trigger. In the submission function, it calls
var NewForm = FormApp.createForm();
InstallTrigger(NewForm, triggerFunction);
The function "triggerFunction" should look something like this:
function triggerFunction (e) {
Logger.log(JSON.stringify(e))
GmailApp.sendEmail("yourEmailString","Debugging",Logger.getLog() //sends an email to you with the logfile.
var form = FormApp.openByUrl(e.response.getEditResponseUrl());
...
}
The e.source mentioned in the aforementioned article says that it works for installable triggers, but when enumerated, only the e.resource and e.authMode were available. Fortunately, the e.response contains the method editUrl that can be used with the method .openByUrl() and return the original form.

Redirect to last page when overlay comment-form is used

I've got a view (phase4) with some custom content type content in it, where the users may comment on.
When the users want to comment, the comment form should appear in a modal form. I solved this by using the admin overlay.
Adding following function to my custom module:
function phase2_admin_paths_alter(&$paths) {
$paths['comment/reply/*'] = TRUE;
}
and using following link:
Comment
to open the comment form in a modal way. So far so good... but....
How do I redirect the user back to the page, the user was coming from.
I know that I have to overwrite the #action of the form in the template_form_FORMID_alter, like
$form['#action'] = $lasturl;
but how do I get the last url, so that it is reusable (so hardcoding the url isn't an option)?
My first idea was that I transfer the last url by adding it to the url as a $_GET-parameter, but it looks like this:
www.example.com/phase4#overlay=comment/reply/161%3Furl%3Dphase4
I also tried it with drupal_get_destination(), but either with no success, because of the tranformation of the "?" and the "=" in the url.
Are there other ways to find out where the user was coming from?
Note: phase4 isn't the alias of node 161. Phase 4 is a view, where node 161 is an element of.
Cheers
Tom
You have to use the drupal_get_destination() function with l() function to create such links.
$destination = drupal_get_destination(); // Store current path
Comment