Form submitting with 'save and new document' button - atk4

I am using a simple form to submit some new document info, and would like the user to be able to hit a 'save and new document' button so he can easily add multiple documents.
I've tried to add a button to the form which works the first time I hit the 'save&new' button, and pops up another form over the existing one, but after that the button stops working.
possibly because I haven't properly closed the previous one, when spawning the new one?
how would I do that?
I'v tried using closeDialog() before calling the new dialogURL() but that obviously does not work...
(simplified) code sample follows:
class page_informa_documento extends Page {
function init(){
parent::init();
$f=$this->add('Form');
$f->setModel('Document');
$f->addSubmit('Save');
$f->addButton('Save and new document')->js('click',$f->js()->atk4_form('submitForm','otro'));
if($f->isSubmitted() )
{
// save document info we just got here
$doc->save();
if ($f->isClicked('otro'))
$f->js()->univ()->dialogURL('New Document',$this->api->getDestinationURL('/informa/documento'))->execute();
else $f->js()->univ()->closeDialog()->execute();
}

If you need form data, you must add multiple Submit buttons.
$a=$f->addSubmit('Save');
$b=$f->addSubmit('Save and Add More');
if($f->isSubmitted()){
// save your document here
if($f->isClicked($a)){
$this->js()->univ()->location($this->api->url('index'))->execute();
// go to index page, we are done
}
if($f->isClicked($b)){
$this->js()->univ()->location($this->api->url()->execute();
// stay on the same page, just reload
}
}
Here is a demo: http://test-suite.agiletoolkit.org/?page=form
Source is taken from here: https://github.com/atk4/atk4-testsuite/blob/master/page/form.php#L16

Related

Why does HTML output not run in a loop the same as a Prompt Response or Alert?

I currently have column A in the spreadsheet filled with checkboxes and have getA1Notation() used to grab this action when the user checks a box. I would then like to run an HTML dialog that has buttons to create an action for that row (HTML script not created yet, just running this test).
The issue I am running into is that when I put the HTML dialog box into a loop, it does not appear when the action (clicking on the checkbox) is done. I can replace the HTML dialog box with a prompt response or alert with buttons and it works perfect, but I would like to use an html box to have multiple custom buttons.
I am very new to using Apps Script for spreadsheets, so thank you for any help.
CODE:
function onEdit(e) {
for (var counter = 0; counter <= 500; counter = counter + 1)
{
const a1 = SpreadsheetApp.getActiveSheet().getRange(2+counter, 1).getA1Notation();
if (e.range.getA1Notation() == a1)
{
SpreadsheetApp.getActiveSheet().getRange(2+counter, 15).setValue('Hello'); //this is just to test the loop is working and doing this action anytime I click a checkbox
var tester = HtmlService.createHtmlOutput("<p>test</p").setWidth(250).setHeight(300);
SpreadsheetApp.getUi().showModelessDialog(tester, "teest");
}
}
}
Assign a function to each row
function onMyEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == "your sheet name" && e.range.rowStart < rowactions.length) {
const rowactions = ["","funk1","funk2","funk3"];//assigned functions
executeFunctionByName(rowactions[e.range.rowStart])
}
}
function executeFunctionByName(func) {
this[func]();
}
Probably need an installalble onedit.
Just stop, don't use a simple trigger that use a loop to show a dialog / sidebar.
The above because simple triggers have limitations, i.e. they have a shorter execution time limit, and they can't do some things that a function called from button / custom menu can do. They might be able to do certain tasks when the spreadsheet owner activates a trigger that might not work when a editor do the same.
By the other hand, SpreadsheetApp.getUi().showModelessDialog(tester, "teest"); is asynchronous, meaning that after executing this statement the runtime will execute the next one immediately, it will not wait for the Google Sheets UI to load the dialog.
Related
Creating a Dialog Prompt Box from an onEdit Function isn't working

Salesforce - Call external API on button click and update custom field from response

So I'll start by saying I'm a C# .Net/Javascript developer with a lot of experience, but I have zero experience with Salesforce. Never ever seen it before today. So, I've been asked by another team to add a custom button to a Contact object, which when clicked calls an external API and updates a custom field in the Contact with the response. It was pitched as "just write some Javascript that calls an API when a button is clicked, it's literally embedded into the page, 15 minute job...".
Following what appears to be quite an outdated document, I've ended up in the Object Manager, selected the Contact object and I'm into the Buttons, Links and Actions page. I'm assuming before this was done using the Execute Javascript behaviour, which in the Lightning version I'm advised against using. So after much Googling I've read about APEX classes, Visualforce Components, Lightning Components, the Salesforce REST API, etc, etc. Not a 15 min job.
Essentially the requirements are to embed a button (or action, or..?) into this Contact page, so that when the sales guy clicks it, it gathers some of the Contact's details and uses them to form an API call to an external service. The data will be used to form a response, which must then be read (as JSON, then parsed) and written into a custom field on the Contact.
What would be the best approach for developing a solution? In the Apex Debug environment I've put together the code to call the API and parse the JSON response, I'm assuming I need to wrap this in an Apex class, with a method that calls this code and returns the response. What I'm not sure of is how to call into this from the button, and update the field in the Contact.
Do I need to do all that from the Apex Class method? passing in a reference to the Contact, or is there another component that needs to sit in between and do this.
Am I right in assuming I'll need to use the Salesforce API to update the Contact?
Any pointers appreciated.
Oh man. It can be a 15 min job but it's definitely "easy when you know how" or have some examples ;)
What's your user interface, Classic or Lightning? Do they have plans to migrate to Lightning soon? I'm assuming it's Lightning if you figured out the "Execute JavaScript" hacks are passé.
Do you care where the button/action will be? Is the top right corner with all other buttons fine or do you want it to be droppable to pretty much any area in the page?
Does the API callout need username, password, maybe certificate? It'll determine whether you need just to whitelist the endpoint on firewall (Setup -> Remote Site Settings) or you'll need something more advanced (Setup -> Named Credentials).
Do you have SFDX command line (CLI), VSCode / are determined to install some tooling? The Lightning Web Components are cutting edge, most sleek etc but you can't create them straight in the browser (at least not yet), you need tooling. Visualforce is OK but nothing special for this use case, Aura components are bit clunky to write - but you can do both in Developer Console without extra tooling.
Parsing the JSON response - depends how complex it is, you can hand-craft parser with JSON.deserializeUntyped() but life's too short for this. Here's nice apex code generator similar to what you'd get from parsing WSDL: https://json2apex.herokuapp.com/
We'll try to do Aura component way. It's ugly, LWC is future but hey, it'll get you started.
Go to Setup -> Remote Site Settings and add new entry with https://en.wikipedia.org/
Create new Apex class:
public with sharing class Stack63364119 {
static final String endpoint = 'https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=';
#AuraEnabled
public static String doCallout(Id contactId){
if(contactId == null){
throw new MyException('Missing record id');
}
List<Contact> contacts = [SELECT MailingCountry FROM Contact WHERE Id = :contactId];
if(contacts.isEmpty() || String.isBlank(contacts[0].MailingCountry)){
throw new MyException('Could not find the contact');
}
Contact c = contacts[0];
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint + c.MailingCountry);
req.setMethod('GET');
HTTPResponse res = new Http().send(req);
System.debug(res.getStatus());
System.debug(res.getBody());
// no special parsing, just chuck it into Description field
// no error handling
if(res.getStatusCode() == 200){
c.Description = res.getBody().abbreviate(32000);
update c;
}
return res.getBody();
}
public class MyException extends Exception{}
}
Make new "Lighning Component" in developer console (it'll be Aura, not LWC). You can tick the last checkbox about "lightning quick action". Name can be same as class but doesn't have to be.
For component (~ html part) paste this
<!-- Loosely based on https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_call.htm -->
<aura:component controller="Stack63364119" implements="force:hasRecordId,force:lightningQuickAction" >
<!-- in the name of all that is holy do not name the JS function same as the Apex class function, it'll give you very cryptic errors to debug -->
<aura:handler name="init" value="{!this}" action="{!c.runCallout}"/>
</aura:component>
For controller (~ JavaScript) paste this
({
runCallout : function(cmp) {
let action = cmp.get('c.doCallout');
action.setParams({contactId : cmp.get('v.recordId')});
action.setCallback(this, function(response){
let state = response.getState();
if (state === "SUCCESS") {
alert('Saved OK: ' + response.getReturnValue());
$A.get("e.force:closeQuickAction").fire(); // if you want to self-close
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
})
Finally go Object Manager -> Contact -> Buttons Links and Actions. Create new Quick Action.
And add it to page layout(s)!
It should get you started. Maybe you'll decide to split it a bit, Apex would only do the callout, return results to UI and if user is happy - updating the contact can be done with one of these: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/data_service_save_record.htm. Separation of concerns blah blah (but will the user be happy with 2 clicks).
P.S. If you tweak it and it dies but it's hard to see any JavaScript errors - it'll be because default is to run in release mode, SF rewrites your source code a bit, optimises, polyfills for "browsers" like IE11... Go to Setup -> Debug Mode and enable for your user.
P.P.S. In sandbox / dev org it's good idea to go Setup -> Session Settings -> and untick "Enable secure and persistent browser caching to improve performance". Your component will be always fresh, saves some frantic hitting Ctrl+R. Don't do it in prod ;)
I will write to you my approach
Button on the record page/page layout -> lighting component or flow -> Apex class for collecting data -> apex class request & response API(don't forget to add the endpoint to remote site settings)-> parse response and update contact (you can use queries and DML operations inside Apex)
lighting component it will be very simple just have a the apex class as the controller example
<aura:component implements="force:appHostable,lightning:isUrlAddressable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" controller="contactController" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.fetchContact}"/>
</aura:component>
controller
({
fetchContact : function(component, event, helper) {
helper.fetchContactHelper(component, event, helper);
}
})
helper
({
fetchAccHelper : function(component, event, helper) {
var action = component.get("c.fetchContacts");
action.setParams({
});
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
}
});
$A.enqueueAction(action);
}
})
assuming is apex function is fetchContacts and class contactController

Custom changes in Sonata Admin PreUpdate prevent persiting of object

I am using Doctrine 2.4.8 in my Symfony 2.8.34 project.
My Problem is that custom changes I made in the preUpdate function preventing the expected changes from the admin form. Dependent on a choice field a new event should been created or deleted.
class CustomAdmin extends AbstractAdmin {
public function preUpdate($object) {
...
if ($object->isDetached()) {
$event = new Event();
...
$object->setDetachedEvent($event);
} else {
$object->setDetachedEvent(null);
}
}
}
Now, the line $object->setDetachedEvent(null); turns everything crazy. The first time I'm saving the form my custom changes in that preUpdate function are executed, but the changes in the fields of the admin form are ignored. The second time the changes from the admin form were executed. If I remove the line above, everythings works fine.
So, anybody has an idea how to handle this problem?

how to clear the file name list after fineUploader (3.5) uploads file

I download fine uploader 3.5, created http handler for a file upload function in my little website . the website is done by asp.net ajax and jquery. it runs at IE9. every time I upload a file, there is a list of file names shown below the load button. if I don't want thme, what should I do?
my code is like this:
html: ...
'<tr><td><div id="jquery-wrapped-fine-uploader"></div></td></tr>...
'ajax/jquery:...
'$('#jquery-wrapped-fine-uploader').fineUploader({
'request: { endpoint: 'xxx.ashx' }
'})
'$('#jquery-wrapped-fine-uploader').on("complete",
'function (event, id, fileName, responseJSON) {
' alert("UPLOAD SUCCESS");
' $.ajax({some ajax calls here});
' })
// WHERE TO PUT this TO CLEAR the UPLOADED FILE LIST??? $('#jquery-wrapped-fine-uploader').fineUploader('reset');
XXX.ashx:
'... public void ProcessRequest (HttpContext context) {
'do some http request work..
'context.Response.ContentType = "text/plain";
'context.Response.Write("{\"success\":true}");
'}
My question is:
I want to completely remove the uploaded file list which shows automatically in green color ( or red if they fail), in order to clear them, I tried to put: $('#jquery-wrapped-fine-uploader').fineUploader('reset'); right after .on('complete'), it's not working, also #jquery-wrapped-fine-uploader seems cached all the time. please help on this.
If you don't want to see the file list at all, you should be using FineUploaderBasic mode instead of using FineUploader mode and then removing all elements in the pre-built UI. FineUploaderBasic mode gives you access to the API, options, and callbacks, but assumes you will be creating your own UI. In other words, FineUploaderBasic mode does not create any DOM elements (except the opaque file input element as a child of your button container, if you supply one). This is all explained (in a great amount of detail) in the documentation.
Start here: http://docs.fineuploader.com

Backbone.js - How can I show a saving gif when I loop through and save my models?

I am allowing the user to press a Save button and so in the event method for that button I need to save all existing models in the collection and destroy any models that were deleted before the button was pressed.
What I would like to do is display an ajax gif while this is all happening. How can I find out when all model.save() and model.destroy() methods are finished?
Here is the method I call when the Save button is pressed:
Save: function() {
var response = confirm("Are you sure you want to save?");
if (response == true) {
// save items
var self = this;
this.collection.each(function(item) {
self.RemoveTempId(item); // if temp id exists remove it
item.save();
});
// destroy items in the trashcan
for (var i = this.trashCan.length - 1; i >= 0; i--) {
this.trashCan[i].destroy();
}
}
},
How can I tap into an event when they are all completed so I can hide my ajax gif?
Thanks..
Each of these method (save and destroy) returns a promise. You can wait for multiple promise to finish using the jQuery when method. e.g.:
$.when( model.save(), model2.destroy() ).then(function() { /* do stuff */ });
Of course this should be adapted to your used cases (because you loop through each model, so you'll probably use an array and .apply() the array of deferred on when), but you get the main idea.
Also I'd suggest separating the confirm dialog and the loading gif inside a view out of the model/collection. This will create a better separation of concern and a more manageable code.
An example with an array of deferreds:
var defs = [];
this.collection.each(function(item) {
defs.push( item.save() );
});
$.when.apply( null, defs ).then(function() {
/* everything is saved */
});
When user agrees to save after your prompt, just show your GIF Image on the page and set its position where you want to show it. Probably, you should display it at center of screen.
Pass a success and error handler in your save method as options hash and hide your GIF image there.
Take a look at Backbone Model save documentation
Some pseudo code goes like this:-
saveModel: function() {
//Show image now. Use CSS to make it visible.
$('#gifSelector').show();
model.save({ 'x':1, 'y':2}, // The default attributes in your model which we wanna save
success: function() {
alert('Model has been saved');
//Hide Gid image
$('#gifSelector').hide();
},
error: function(e) {
alert('Encountered some problem in saving model');
// Hide GIF image
$('#gifSelector').hide();
}
}
One Suggestion: Since you will be calling this method for each model and everytime we don't want to query DOM to get GIF image using its selector. Its better to store a global reference to it and show/hide it using that reference. It will be faster ;)

Resources