+ Start a Discussion

Creating multiple detail records of multiple master records- mass input from a form?



I'm struggling with the best way to approach something in Visualforce, any thought appreciated.


Our postroom folk would like a nice simple UI for logging the outgoing post. 

Each post item they log is a detail custom object belonging to an opportunity.

I have no problem creating a VF page for doing one item at a time.


What I'm struggling with is creating a page that allows them to set two or three common fields, enter up to ten rows of data and push the button to have ten almost-identical items created and associated with the appropriate opportunities.   Ten is a number that's "comfortable" on screen for them, in case anyone says "Why ten?" 


Since I'm dealing with multiple items and multiple "parent" opportunities, I can't use most of the pre-defined apex form components, which all tie back to one ID.


Right now, I'm exploring the idea of having HTML form items, and building the items in javascript to insert via API calls.  This is causing me problems with permission denied errors on picklist values, trying to work out indirect references to form elements rather than hardcode each item in the form, and a plague of other things.


Has anyone run into this type of idea before, and have any insight they can share on how to approach it please?


My first "evil half-breed" page of code looks like this, and it doesn't even approach working, but may give an idea of what I was thinking:


<apex:page standardcontroller="Disbursement__c"> <head> <link href="/sCSS/Theme2/en/common.css" rel="stylesheet" type="text/css"/> <script type="text/javascript" src="/js/functions.js"></script> <script src="/soap/ajax/12.0/connection.js"></script> <script type="text/javascript"> function PostThePost(form) { // Build an array of disbursements for the post items. var PostItem = new sforce.SObject("Disbursement__c"); var PostPile = new Array(PostItem); // Build an array of Tasks to enter completed tasks for return of docs var PostLog = new sforce.Sobject("Task"); var LogPile = new Array(PostLog); // Create a holder for the query returned records var Record = new Array(); // Create the basic query string var Querystub = "Select Id from Opportunity Where Claim_ref__c = " // Cycle through the inputs for (var x = 0; x < 10; x++) {

// TODO: check that the form field has data before triyng to query, we might only need to do 1 query of the ten.. // Find the relevant opportunityId by querying for it- this needs to be indirect, so if x=0 query T1, x=1 query T2 etc var saveResult = sforce.connection.query(Querystub+eval(form.+'T'+x+1+'.value')); // If it returned a record, it will be one- Claim_ref__c is a unique field if (saveResult.size != 0){ // grab the record Record = saveResult.records; //Put the opportunity ID into the new disbursement record. PostPile[x].Disbursements__c = Record.Id; //Fill in the postage type picklist field PostPile[x].Postage_type__c = form.Type_of_post.value; //The record type ID is constant for postage disbursements PostPile[x].RecordTypeId = '012200000004w5OAAQ'; //Set the field of the label, in theory only if postage type is registered or special delivery PostPile[x].Post_tracking__c = eval(form.+'T'+x+11+'.value');

// TODO: place an "OK" next to the relevant item, clear the field entry to prevent duplication,

// and if the checkbox is ticked, create a Task on the opportunity, subject "Docs returned" that is complete. } } //Shovel with an update var didTheUpdateWork = sforce.create(PostPile); // Do any cleanup and error catching we need to, TODO

// TODO Wait for use to do their thing, then //Get back to the Leads tab that this user invariably lives on //window.parent.location.href="{! urlFor( $Action.Lead.Tab , $ObjectType.Lead,null,true)}"; } </script> </head> This is the Outgoing post form<br></br> Designed to enter disbursements for outgoing post, and log a completed task for document returns. <br></br> <br></br> <form name="PostForm" method="POST" action="" class="dataCol"> <select name="Type_of_post" size="1" class="requiredBlock"> <option value="First class">First class</option> <option value="Second class">Second class</option> <option value="Registered">First class, Registered Signed for</option> <option value="Second class, Registered Signed for">Second class, Registered Signed For</option> <option value="Special Delivery next day">Special Delivery next day</option> <option value="Special Delivery before 9am">Special Delivery before 9am</option> </select> Tick here if these are doc returns <input type="checkbox" name="C1" value="ON"/><br></br> <table cols="2"> <tr><td>Case number&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </td> <td>Label number for Registered/SD</td></tr> <tr class="dataCol col02"><td><input type="text" name="T1" size="6"/></td><td><input type="text" name="T11" size="10"/></td></tr> <tr><td><input type="text" name="T2" size="6"/></td><td><input type="text" name="T12" size="10"/></td></tr> <tr><td><input type="text" name="T3" size="6"/></td><td><input type="text" name="T13" size="10"/></td></tr> <tr><td><input type="text" name="T4" size="6"/></td><td><input type="text" name="T14" size="10"/></td></tr> <tr><td><input type="text" name="T5" size="6"/></td><td><input type="text" name="T15" size="10"/></td></tr> <tr><td><input type="text" name="T6" size="6"/></td><td><input type="text" name="T16" size="10"/></td></tr> <tr><td><input type="text" name="T7" size="6"/></td><td><input type="text" name="T17" size="10"/></td></tr> <tr><td><input type="text" name="T8" size="6"/></td><td><input type="text" name="T18" size="10"/></td></tr> <tr><td><input type="text" name="T9" size="6"/></td><td><input type="text" name="T19" size="10"/></td></tr> <tr><td><input type="text" name="T10" size="6"/></td><td><input type="text" name="T20" size="10"/></td></tr></table> <p>&nbsp;</p> <input type="button" value="Submit Post" onclick="PostThePost(this.form)" name="B1" class="btn"/><input type="reset" value="Reset" name="B2" class="btn"/> </form></apex:page>




Message Edited by bunrotha on 01-21-2009 06:52 AM

Hi Nick,


i think we are on the same task. But the solution process is quite different, because I never worked with Javascript (you'll see on mycode below).

We were trying to keep apex-Code and combine some javascript-action. It's based on the following post:

Link to solved Discussion


We'd like to keep the common fields in a header and are trying to "copy" these values in each row. The inputFields won't be rendered or could be set as inputHidden or inpuSecret.

The code doesn't work yet, but perhaps we can find a combined solution.


<apex:page controller="multiInsert" tabstyle="our_object__c"> <apex:form > <apex:pageBlock > <apex:pageBlockButtons > <apex:commandButton value="Save" action="{!save}" rerender="error"/> </apex:pageBlockButtons> <apex:pageBlockTable value="{!ausl}" var="a" id="table_head"> <apex:column headerValue="FIELD_1"> <apex:inputField value="VARIABLE_1" id="IP01"/> </apex:column> <apex:column headerValue="FIELD_2"> <apex:inputField value="VARIABLE_2" id="IP02"/> </apex:column> </apex:pageBlockTable> <apex:pageBlockTable value="{!ausl}" var="a" id="table" rows="3"> <apex:facet name="footer"> <apex:commandLink value="add row" action="{!addRow}" rerender="table,error"/> </apex:facet> <apex:column headerValue="FIELD_1a"> <apex:inputField value="{!a.field1__c" id="OP01" rendered="false"/> </apex:column> <apex:column headerValue="FIELD_2a"> <apex:inputField value="{!a.field2__c" id="OP02" rendered="false"/> </apex:column> <apex:column headerValue="FIELD_3"> <apex:inputField value="{!a.field3__c}"/> </apex:column> <apex:column headerValue="FIELD_4"> <apex:inputField value="{!a.field4__c}"/> </apex:column> <apex:column headerValue="FIELD_5"> <apex:inputField value="{!a.field5__c}" required="false"/> </apex:column> <apex:column headerValue="FIELD_6"> <apex:inputField value="{!a.field6__c}" required="false"/> </apex:column> <apex:column headerValue="FIELD_7"> <apex:inputField value="{!a.field7__c}" required="false"/> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>

 To get the values in the hidden fields there would be a script, which could be invoked by onclick within the Save Button.


<script type="text/javascript"> function copy_head() { document.getElementById("IP01").value = OP01.value; document.getElementById("IP02").value = OP02.value; } </script> <apex:pageBlockButtons > <apex:commandButton value="kopieren" rerender="table,error" onclick="copy_head()"/> </apex:pageBlockButtons>


That's our idea, perhaps we can both take advantage of it.


Best regards across the channel,





Hi Tobi-


thanks for spotting the post, I've made quite a bit of progress here, and had also looked at using pageBlockTables.


So far, here's the solution I have, which creates Tasks, although my next version is for inserting custom objects.  It's also Javascript-free!

There is a data object class, which just hold pairs of items, which looks like this:


public class caserefs { public String caser {get; set;} public String poster {get; set;} public caserefs(){ caser= ''; poster=''; } }


 Then I have a custom controller class.  this version has a checkbox, a string to hold the common subject line, and some code to create a picklist (which is used to put data in that subject string.)


I also have a list of tasks, which I build in parallel with table rows, and populate on save.

Then finally, a list of my new two-item data objects, which is what will appear on the table itself.


Because I need to look up an opportunity from my data objects, my save function has a query to find the relevant ID, and copy it into the matching task item, so things get associated to the right opportunity.


The save function does what I needed without javascript, by iterating the list of objects in the table, updating the matching Task list, and finally doing an insert of the list of tasks.


public class multi_insert { public List<Task> tasks ; public List<caserefs> cases {get; set;} public String Subject; public Boolean docReturns; public List<SelectOption> gettypes() { List<SelectOption> options = new List<SelectOption>(); options.add(new SelectOption('','--Select Record Type --')); options.add(new SelectOption('No contact letter','No contact letter')); options.add(new SelectOption('Rebook letter','Rebook letter')); options.add(new SelectOption('Documents Received','Documents Received')); options.add(new SelectOption('1st class letter','1st class letter')); options.add(new SelectOption('Registered letter','registered letter')); options.add(new SelectOption('2nd class letter','2nd class letter')); options.add(new SelectOption('Special Delivery','Special Delivery')); return options; } public String getSubject() { return Subject; } public void setSubject(String Subject) { this.Subject= Subject; } public boolean getdocReturns() { return docReturns; } public void setdocReturns(Boolean docReturns) { this.docReturns= docReturns; } public multi_insert(){ tasks= new List<Task>(); cases= new List<caserefs>(); docReturns= false; tasks.add(new Task()); cases.add(new caserefs()); } public void addrow(){ tasks.add(new Task()); cases.add(new caserefs()); } public void removerow(){ Integer i = tasks.size(); tasks.remove(i-1); cases.remove(i-1); } public PageReference save(){ Integer count = cases.size(); Integer i = 0; do { tasks[i].whatID=[SELECT id from opportunity where Claim_Ref__c = :cases[i].caser limit 1].id; tasks[i].Subject=Subject; if (Subject=='Documents Received') { tasks[i].description=Subject +' '+cases[i].poster; } else { tasks[i].description='Correspondence sent '+Subject +' '+cases[i].poster; } if (docReturns) { tasks[i].description='Documents returned. '+tasks[i].description; } tasks[i].ownerID=UserInfo.getuserid(); tasks[i].status='Completed'; tasks[i].activitydate=system.today(); system.debug(tasks[i]); i++; } while (i < count); insert tasks; PageReference home = new PageReference('/apex/letter_out'); home.setRedirect(true); return home; } public PageReference cancel(){ PageReference home = new PageReference('/home/home.jsp'); home.setRedirect(true); return home; } }


Finally, it's implemented on a Visualforce page like this:



<apex:page controller="multi_insert"> <apex:form > <apex:pageBlock > <apex:pageBlockButtons > <apex:commandButton value="Save" action="{!save}" rerender="error"/> <apex:commandButton value="Cancel" action="{!cancel}"/> <apex:commandbutton value="Add Row" action="{!addRow}" rerender="table,error"/> <apex:commandbutton value="Remove Row" action="{!removeRow}" rerender="table,error"/> </apex:pageBlockButtons> <apex:pageblocksection > <apex:selectList value="{!Subject}" multiselect="false" size="1"> <apex:selectOptions value="{!types}"/> </apex:selectList> </apex:pageblocksection> <apex:pageblocksection columns="2"> <apex:pageblocksectionitem > <apex:outputtext value="Click here for a document return notation"/> <apex:inputcheckbox value="{!docReturns}" id="docret" /> </apex:pageblocksectionitem> </apex:pageblocksection> <apex:pageBlockTable value="{!cases}" var="a" id="table"> <apex:facet name="footer"> </apex:facet> <apex:column headerValue="Case reference"> <apex:inputtext value="{!a.caser}"/> </apex:column> <apex:column headerValue="Post tracking number"> <apex:inputtext value="{!a.poster}"/> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>


As you've probably guessed, this will insert a group of tasks into different opportunity objects relating to postal items, but it should be OK to adapt for most anything.


My chief uncertainty is over updating items while iterating through a list, so better methods are welcome!

I also don't know how well this scales, I'm testing using 5 or 6 object/tasks at once.


So far, all seems to work, and the postroom folk here think it's good.


Let me know if this helps you get closer to your objective- I'm new to all this, but it compiled so I must have done something right!