You need to sign in to do that
Don't have an account?
Steve Cairney
add row custom object visualforce
Hi I have to achive something that I'm struggling with at the moment (I've posted a few threads recently sorry)
I have a requirement as follows. I have a custom controller as follows
Heres the VF page code
What I need to do is achieve the following
1. Add row functionaility
I would like the user to be able to add additional Designs and Delivery rows related to the Product (Products can have multiple designs and multiple deliveries
2. I'd also like the user to be able to add a full row of related objects, IE. Another row with a new Product selected, along with Design and Deliveries (but that row to also have the option of adding additional Designs and Deliveries.
I've experimented with PageBlockTable, but I can't work out the List since until the VF save is pressed, nothing exisits.
I've got loads of code examples from around the web but the classes are so different to mine that I don't know where to start. I'm floundering and could really use some help.
I have a requirement as follows. I have a custom controller as follows
public with sharing class CreateIABookingController{ public ItsApproved_Booking__c ItsApprovedBooking {get; set;} //Name the objects as a variables public ItsApproved_Product__c ItsApprovedProduct {get; set;} public ItsApproved_Design__c ItsApprovedDesign {get; set;} public ItsApproved_Delivery__c ItsApprovedDelivery {get; set;} private string OpportunityId; public CreateIABookingController() { opportunityId = ApexPages.currentPage().getParameters().get('oppId'); ItsApprovedBooking = new ItsApproved_Booking__c(); ItsApprovedProduct = new ItsApproved_Product__c(); ItsApprovedDesign = new ItsApproved_Design__c(); ItsApprovedDelivery = new ItsApproved_Delivery__c(); } public PageReference Save() { ItsApprovedBooking.Opportunity__c = opportunityId; insert ItsApprovedBooking; ItsApprovedProduct.Booking_Reference__c = ItsApprovedBooking.Id; //insert the reference field for the parent and declare variable id ItsApprovedProduct.Opp__c = opportunityId; //relate the Opp to the Product insert ItsApprovedProduct; ItsApprovedDesign.Opportunity__c = opportunityId; //relate the Opp to the Design ItsApprovedDesign.Product_Reference__c = ItsApprovedProduct.Id; ItsApprovedDelivery.Product__c = ItsApprovedProduct.Id; insert ItsApprovedDesign; ItsApprovedDelivery.ItsApproved_Design__c = ItsApprovedDesign.Id; ItsApprovedDelivery.Opportunity__c = opportunityId; //relate the Opp to the Delivery insert ItsApprovedDelivery; return new PageReference ('/' + opportunityId); } }This allows the user to add a new Record (the Booking) with one related Product, Design and Delivery object on one page. This page is accessed via a button on the Opp record so that the Booking object is related to that OppID
Heres the VF page code
<apex:page controller="CreateIABookingController"> <apex:form > <apex:pageBlock title="Booking Wizard"> <apex:pageBlockButtons location="bottom"> <apex:commandButton value="Save" action="{!Save}"/> </apex:pageBlockButtons> <apex:pageBlockSection columns="1" title="ItsApproved Booking Details"> <apex:inputField value="{!ItsApprovedBooking.Booking_Title__c}" label="Booking Title: "/> <apex:inputField value="{!ItsApprovedBooking.Customer_Reference__c}" label="Customer Reference: "/> <apex:inputField value="{!ItsApprovedBooking.Incharge_Date__c}" label="In-Charge Date: "/> </apex:pageBlockSection> <apex:pageBlockSection columns="3" title="Product Details"> <apex:inputField value="{!ItsApprovedProduct.Product_Code__c}" label="Product Code: "/> </apex:pageBlockSection> <apex:pageBlockSection columns="1" title="Design Details"> <apex:inputField value="{!ItsApprovedDesign.Artwork_Title__c}" label="Artwork Title: "/> </apex:pageBlockSection> <apex:pageBlockSection columns="1" title="Delivery Details"> <apex:inputField value="{!ItsApprovedDelivery.Quantity__c}" label="Quantity: "/> <apex:inputField value="{!ItsApprovedDelivery.Depot_Code__c}" label="Depot Code: "/> </apex:pageBlockSection> </apex:pageBlock> </apex:form> </apex:page>
What I need to do is achieve the following
1. Add row functionaility
I would like the user to be able to add additional Designs and Delivery rows related to the Product (Products can have multiple designs and multiple deliveries
2. I'd also like the user to be able to add a full row of related objects, IE. Another row with a new Product selected, along with Design and Deliveries (but that row to also have the option of adding additional Designs and Deliveries.
I've experimented with PageBlockTable, but I can't work out the List since until the VF save is pressed, nothing exisits.
I've got loads of code examples from around the web but the classes are so different to mine that I don't know where to start. I'm floundering and could really use some help.
Just add the following line.
delivery.Product__c = product.Id;
That should set the product id on the delivery.
All Answers
I've made some progress with this by slowly trying out different methods I've found around the web. Currently I have edited the VF page and the Controller class as follows. This almost gives the result I need. However, when pressing save, only the last row on the page is saved against the relevant records. I believe I will have to edit the controller, but I'm not sure what to add in there.
I'm guessing the new !row value needs to taken into consideration around the public PageReference Save mark?
I have also successfully created a test class with 94% coverage.
VF Page
Controller class
Lastly here's how the page looks when rows have been added. It's a bit clunkly and it opens without any rows at all (you have to click Add Row) but it's better than before.
I'm wondering if I need to take into consideration the row ID here?
public ItsApproved_Product__c ItsApprovedProduct {get; set;}
public ItsApproved_Design__c ItsApprovedDesign {get; set;}
public ItsApproved_Delivery__c ItsApprovedDelivery {get; set;}
in your inner class "Row". Your inner class "Row" should look something like this.
public class Row {
public Integer rowId { get; set; }
public ItsApproved_Product__c ItsApprovedProduct {get; set;}
public ItsApproved_Design__c ItsApprovedDesign {get; set;}
public ItsApproved_Delivery__c ItsApprovedDelivery {get; set;}
public Row(Integer rowId, ItsApproved_Product__c product, ItsApproved_Design__c design, ItsApproved_Delivery__c delivery) {
this.rowId = rowId;
ItsApprovedProduct = product;
ItsApprovedDesign = design;
ItsApprovedDelivery = delivlery;
}
}
//add row method can be changed as follows.
public void addRow() {
Integer rowId = rows.size();
rows.put(rowId , new Row(rowId, new ItsApproved_Product__c(), new ItsApproved_Design__c(), new ItsApproved_Delivery__c()));
}
Then you can access them through your Map "rows" you declared.
Also create a list for each of your objects in your outer class.
List<ItsApproved_Product__c> productList = new List<ItsApproved_Product__c>();
//your save method should use the "rows" map to create the list of different objects. I have added the productList as an example. Insert the list //outside the for loop as shown
for (Row r : rows.values())
{
ItsApproved_Product__c product = new ItsApproved_Product__c();
product.Opp__c = opportunityId;
product.Booking_Reference__c = ItsApprovedBooking.Id;
product.Product_Code__c = r.ItsApprovedProduct.Product_Code__c;
//add this product to the list
productList.add(product);
// and so on
}
if (productList.size() > 0) // do this for each of your objects.
{
insert productList;
}
I have not tested any of the code I have given above. Hope it works. Let me know how it goes.
for (Row r : rows.values())
{
r.ItsApprovedProduct.Booking_Reference__c = ItsApprovedBooking.Id;
//add this product to the list
productList.add(r.ItsApprovedProduct);
// and so on
}
And in your visualforce page you have to do the following change.
<apex:pageBlockTable value="{!rows}" var="rid" id="tablet">
<apex:column headerValue="ID">
<apex:input type="auto" value="{!rows[rid].rowId}" />
</apex:column>
<apex:column headerValue="Product Code">
<apex:inputField value="{!rows[rid].ItsApprovedProduct.Product_Code__c}" />
</apex:column>
<!-- Do the same change to other fields in the page block table.
Hope this works as you expect.
I worked out that in the 'save' loop I had to add insert xxxList in there. I'm getting a strange error when hitting save...
SF is telling me
System.DmlException: Insert failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [Product Reference]: [Product Reference]
Error is in expression '{!Save}' in component <apex:commandButton> in page createiabookingpage: Class.CreateIABookingController.Save: line 57, column 1
Class.CreateIABookingController.Save: line 57, column 1
However, I can see the field in the code (HERE IT IS comment)
What could be the solution here?
Your requirement is a bit complex. You are referencing the ProductId (ItsApprovedProduct.Id) in the ItsApprovedDesign and so on, it is difficult to set the Product_Reference__c on the ItsApprovedDesign and ItsApproved_Design__c on ItsApprovedDelivery etc. You can only refer the id once the record is created. Remove the insert statements from the for loop as below.
You need to create some maps to identify which product belongs which design and which design belongs to which delivery. How can you identify this from the inputs of the visualforce page.
For each of the products selected in the Visualforce page, if you want to create a design and delivery record then you can do as below. Hope this helps.
I suspected this was the case as if I removed everything put the Product, the Booking saved fine. I'm going to work on this code and let you know how it goes.
Is it possible to have the page open with 1 row already created btw? (currently you have to click Add Row to begin inputting)
I'm getting a System.ListException: Before Insert or Upsert list must not have two identically equal elements error.
It's highlighting that insert deliveryList; is the culprit.
Could this be because the the deliveryList isn't inserted until the end, perhaps it should be inserted before?
Code snippet here (it's possible that the if statements are in the wrong place)
The following code should be outside the for loop. Move above lines outside the for loop and try it.
I found the error. The error is in your for loop. You have the following statement
deliveryList.add(r.ItsApprovedDelivery); twice in the loop.
Remove one of them and try again. Make sure to keep the lines I mentioned in my previous post out of the for loop.
But although it saved, the designs and deliveries don't get related to the correct product.
I'll work more on this tomorrow and will report back
Here's what I inputted
And here is the results on the Opp page (note how the Product Reference is the same on the designs and deliveries)
Map<string, string> product2DesignMap = new Map<string, string>(); //add this variable before for loop rows
//add the following line in the for loop of rows.
product2DesignMap.put(r.ItsApprovedProduct.Product_Code__c, r.ItsApprovedDesign.Artwork_Title__c)
and check the Artwork_Title__c before adding to list. You have to do a similar checks for the delivery.
Hope this helps.
Once I know you're thinking behind this I'll be able to apply the same technique to the delivery aspect.
It doesn't have to be Artwork Title, it can be some other field. I gave it as an example.
The productList, designList and deliveryList are prepared in a for loop "rows". It is not a good practice to use DML statements (like insert, update etc) in a for loop. So we are inserting the lists outside this for loop. As the lists are prepared before insert, we don't have the reference fields (for example we can't refer the Product Id in the Design object's Product_Reference__c field). So there should be some data other than the Id that matches the right product to the right design. It could be any field (text or number) that can say this design belongs to this product.
Hope you understand what I was trying to say.
What I guess I'm confused about is the following
if (product2DesignMap.get(product.Product_Code__c) == design.Artwork_Title__c)
I don't understand how they can be equal.
Here's how I'm reading it logically. If there is a product in the product2 map, get the product code and match the same product it with the artwork title. But that doesn't make sence to me.
Also, no matter what I try, I can't apply the same technique to the Delivery object. All that happens in the last product and design row is added twice.
I wonder if we can use the rowID?
product2DesignMap.put(r.ItsApprovedProduct.Product_Code__c, r.ItsApprovedDesign.Artwork_Title__c) where the first value r.ItsApprovedProduct.Product_Code__c is the key and the second value r.ItsApprovedDesign.Artwork_Title__c is the value. This map is created in the for loop "rows" as the Product Code is different on each row, it creates a map with product codes as key and Artwork_Title__c as the value in this case.
And when I use this map in IF condition product2DesignMap.get(product.Product_Code__c), I am passing the keyproduct.Product_Code__c which will return the value Artwork_Title__c which I have set earlier.
So if this value is equal to design.Artwork_Title__c then I set the Product_Reference__c using the Id field.
I hope it is clear now.
Something like
product2DeliveryMap.put(r.ItsApprovedDesign.Artwork_Title__c, r.ItsApprovedDelivery.Depot_Code__c)?
I'll have to add instruction to the user so they don't use the same artwork titles for different products, if we can't reference the integer rowId somehow.
Now, for the delivery I'm pairing the artwork title and the depot code (decimal) so I've got this outside the for loop
Map<string,decimal> product2DeliveryMap = new Map<string,decimal>();
I've got this inside the for loop
product2DeliveryMap.put(r.ItsApprovedDesign.Artwork_Title__c,r.ItsApprovedDelivery.Depot_Code__c);
and I've for this in the for loop
However, The deliveries still don't match the designs. Could I be placing the code in the wrong place?
product2DeliveryMap.put(r.ItsApprovedProduct.Product_Code__c,r.ItsApprovedDelivery.Depot_Code__c);
and later check using
if (product2DeliveryMap.get(design.ItsApproved_Product__r.Product_Code__c) == delivery.Depot_Code__c)
Hope this works.
You can see the results on the Opp page here
So now I need think I need to relate the Product and the Design with the Delivery.
The question is do I need to create a new Map for that, or can I reuse
(MultiDeliveryMap.get(product.Product_Code__c) == delivery.Depot_Code__c)
by just placing it in the code again.
I'll do some tests!
However! Still no change. The delivery assumes the last design regardless of product code.
What's that code trying to do Krishna?
I have declared the following, perhaps in the wrong place?
This is at line 10 after the other lists
This is outside the for loop
This is inside the for loop
And the loop through is at the end (after insert designList) My thinking is there's no inset for the new list we created back in line 10 "designs" so the code is ignoring it?
List<ItsApproved_Design__c> designs = [select Id, Product_Reference__r.Product_Code__c from ItsApproved_Design__c where Id IN designList];
I'll make the changes!
I had some left overs from previous attempts so I removed them and added your all your code exactly where you said.
Now the Designs are being added into the correct deliveries but the Products aren't.
So I think we are one list and one loop away from success.
I'm posting the code in it's entirety here so you can see the whole picture.
Just add the following line.
delivery.Product__c = product.Id;
That should set the product id on the delivery.
Should I add this in my Home renovation Leicestershire (https://cravenandhargreaves.co.uk/" target="_blank)website too??? Is it good for that??