Pass ID of current record to Apex Controller - email

I'm working on a Visualforce Email Template which will be sent from the parent Loan (LLC_BI__Loan__c) record in Salesforce, and I'm trying to include fields from the child Entity Involvement (LLC_BI__Legal_Entities__c) record(s).
I'm unable to pass the correct parent (Loan) Id to get the correct child records. Can anyone see where I may be going wrong ?
Thank you in advance
Component:(Name = BorrowerRecordsFromLoans)
<apex:component controller="BorrowersOnLoans" access="global">
<apex:attribute name="currentRecordId" description="" assignTo="{!loanId}" type="Id"/>
<apex:dataTable value="{!relatedBorrowers}" var="borrower">
<apex:column >
<apex:facet name="header">Borrower Name</apex:facet>
{!borrower.LLC_BI__Borrower_Type__c}
</apex:column>
</apex:dataTable>
</apex:component>
Controller: (Name = BorrowersOnLoans)
public class BorrowersOnLoans {
public Id loanId { get; set { loanId = value; loadChildren(); } }
public LLC_BI__Legal_Entities__c[] relatedBorrowers { get; set; }
void loadChildren()
{
List <LLC_BI__Legal_Entities__c> entList = new List<LLC_BI__Legal_Entities__c>();
for(LLC_BI__Loan__c loan:
[SELECT Id, (SELECT Entity_Name__c FROM LLC_BI__Legal_Entities__r ORDER BY Borrower_Number__c) FROM LLC_BI__Loan__c WHERE Id = :loanId])
{
for(LLC_BI__Legal_Entities__c ent:loan.LLC_BI__Legal_Entities__r) entList.add(ent);
}
}
}
Email Template:
<c:BorrowerRecordsFromLoans currentRecordId="{!relatedTo.Id}" />

We don't have your objects (whatever "LLC_BI" managed package is) but this should help.
If it's just a plain old (directly) related list - you don't need a component & query. The related list is directly available in the VF email template, you just need to know exactly the relationship's name. Here's example with Account and Opportunities:
<messaging:emailTemplate subject="https://stackoverflow.com/q/59502890/313628" recipientType="User" relatedToType="Account">
<messaging:htmlEmailBody >
{!relatedTo.AccountNumber}<br/>
{!relatedTo.Name}<br/>
{!relatedTo.BillingCity}<br/>
Opportunities, like that:
<ol>
<apex:repeat value="{!relatedTo.Opportunities}" var="o">
<li>{!o.Name}, {!o.StageName}, {!o.Amount}</li>
</apex:repeat>
</ol>
<hr/>
or like that:
<apex:dataTable value="{!relatedTo.Opportunities}" var="o">
<apex:column value="{!o.Name}" />
<apex:column value="{!o.StageName}" />
<apex:column value="{!o.Amount}" />
</apex:dataTable>
</messaging:htmlEmailBody>
</messaging:emailTemplate>
This should get you started. Your relationship name will probably be LLC_BI__Legal_Entities__r. You can go pretty far with it in pure Visualforce, limit the list, apply custom styling, even filter some rows by not displaying them (not best performance wise but it's an email template, how often you'll use it. Read about <apex:variable> and rendered attribute if you're curious).
But if you really need a query (need WHERE clause, ORDER BY etc) - you need controller, component and the final template.
public with sharing class Stack59502890 {
public Id accountId {get; set;}
public List<Opportunity> getWonOpportunities(){
return [SELECT Name, StageName, Amount
FROM Opportunity
WHERE AccountId = :accountId AND IsWon = true
ORDER BY Name];
}
}
<apex:component access="global" controller="Stack59502890">
<apex:attribute name="currentRecordId" description="" assignTo="{!accountId}" type="Id"/>
<apex:repeat value="{!wonOpportunities}" var="o">
<li>{!o.Name}, {!o.StageName}, {!o.Amount}</li>
</apex:repeat>
</apex:component>
<messaging:emailTemplate subject="Stack59502890" recipientType="User" relatedToType="Account">
<messaging:htmlEmailBody >
<c:Stack59502890 currentRecordId="{!relatedTo.Id}" />
</messaging:htmlEmailBody>
</messaging:emailTemplate>
For my data this now returns only 2 opps.

Related

Returning a subset of a navigation propertie's object

I have a one to many relationship as outlined below. In some parts of the business layer there are queries of the Item table and in others the Client table (as well as its Items). LazyLoading and ProxyCreation are both false, reference loop handling is set to ignore.
public class Client {
public virtual ICollection<Item> Items { get; set; }
public string Name {get;set;}
}
public class Item {
public virtual Client TheClient {get;set;}
public string ItemProp {get;set;}
// another 10 properties or so
}
myitems = dbContextScopeX.Items.Include(x => x.Client).ToList();
The view has a list of items with the need to show the Client's Name (in my example). I am looking for item.Client.Name ultimate, however when myitems gets queries/serialized it contains:
myitems.Client.Items
If I set the attribute [JsonIgnore] on the Client's Item property it never comes through the graph which I need it to in other places. Is there a way to get myItems.Client.Name without having to get myitems.Client.Items in the query or without having to create an anonymous projection for the Item array?
Project the Item properties you want (be they simple or complex type) along with just the Client name into an anonymous type and serialize that.
myitems = dbContextScopeX.Items.Include(x => x.Client)
.Select(i=>new {
ItemProp = i.ItemProp,
ItemCollection = i.ItemCollection,
...
ClientName = i.Client.Name
}).ToList();
Only caveat is you have to do some manual work if you want to deserialize this back into entities.

Null fields after form submit in Spring

I've got a Product with a Rating rating attribute. I've got a product update form (updateStart method) which doesn't contain the rating field (since I don't want it to be editable).
The problem is that when I submit the form (with update method), the rating is automatically set to null.
So I tried to add the Rating to the form model in updateStart, retrieving it in the update method, but it keeps being rewritten as well.
I tried to set a #SessionAttributes("rating") annotation in the controller. This time the rating value is kept, but Spring creates a new entry in the database, cloned from the other rating object, and attaches it to the Product.
#Controller
#SessionAttributes("rating")
#RequestMapping("/products")
public class ProductsController {
#RequestMapping("/update_start")
public String updateStart(#RequestParam("id") Long id, Model model) throws BusinessException {
Product product = productService.findProductById(id);
System.out.println("RATING A START "+product.getRating().getAbsoluteRating());
List<Category> categories = productService.findAllCategories();
model.addAttribute("categories", categories);
model.addAttribute("product", product);
model.addAttribute("id", id);
model.addAttribute("rating",product.getRating());
return "products.updateform";
}
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(#ModelAttribute("rating") Rating rating, #ModelAttribute Product product, BindingResult bindingResult) throws BusinessException {
System.out.println("RATING A UPDATE "+rating.getAbsoluteRating());
validator.validate(product, bindingResult);
List<Image> images = imageService.getProductImages(product.getId());
product.setRating(rating);
productService.updateProduct(product,images,sellerid);
return "redirect:/products/viewsforsellers.do";
}
}
What can I do?
EDIT: I'd prefer to avoid placing a hidden input field with ratingId in my form.
In the form include a hidden input with the name and value specified for the Rating. The value should include
<form>
<input name="product.rating" value="${product.rating.id}"/>
<!-- Other fields -->
</form>
Now when the request comes over the wire it should include a Rating specified by id for the product.
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(#ModelAttribute Product product, BindingResult bindingResult) throws BusinessException {
//implementation
}
#ModelAttribute should attempt to bind this parameter to the Product however it is not aware of what a Rating is. This is where a Converter comes into play. A Converter is used during databinding to tell Spring MVC how to map a field of type String to a field of type Rating.
public class StringToRatingConverter implements Converter<String, Rating> {
public Rating convert(String source) {
//Use the source String to convert to rating
//Possibly via database call or enum conversion, pending ratings type and definition
//Ultimately the code needs to return the appropriate object of type Rating
return rating; //The above implementation will create the rating object.
}
}
The StringToRatingConverter must then be registered in the dispatcher configuration file.
<!-- Register Converters - Used for data binding-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="fully.qualified.path.to.StringToRatingConverter"/>
</list>
</property>
</bean>
The first time I encountered this scenario, I captured it in a post on my blog, which you may be helpful.
You should add "types" element to your #SessionAttributes("rating") annotation in order properties of attributes to be kept; e.g.
#SessionAttributes(types = Rating.class, names = "rating")

Newly added entity is not showed correctly in my foreach knockout loop

I work on an asp.net mvc project with Breeze.
I have a page where I display details about a transport and list linked transports in a table below it.
Consider the following entities:
public class Transport
{
[Key]
public int Id { get; set; }
public string TransportNumber { get; set; }
public string Description { get; set; }
public virtual List<LinkedTransport> LinkedTransports { get; set; }
...
}
public class LinkedTransport
{
[Key, Column(Order = 0)]
public int TransportId { get; set; }
[Key, Column(Order = 1)]
public int TransportRelatedId { get; set; }
public virtual Transport Transport { get; set; }
public virtual Transport TransportRelated { get; set; }
}
So these 2 entities allows me to define my transports and (for each) linked transports.
First, I load my (main) transport into an observable named transport:
var query = entityQuery.from('Transports')
.where('id', '==', transportId)
.expand("LinkedTransports.transportRelated");
query = query.using(breeze.FetchStrategy.FromServer);
Please pay attention to the expand where I retrieve also the linked transports.
Now I have the following code for showing my transport and linked transports:
<div data-bind="with: transport()">
<input type="text" data-bind="text: transportNumber"></input>
<input type="text" data-bind="value: description"></input>
...
<table data-bind="foreach: linkedTransports">
<tr>
<td data-bind="text: transportRelated().transportNumber()"></td>
<td data-bind="text: transportRelated().description()"></td>
</tr>
</table>
</div>
So far, so good. I can display my main transport informations and also linked transports informations. Now I need to let the user add some linked transports. So I have the following code:
var createLinkedTransport = function (transpId, transpRelatedId) {
var initialValues = ({
transportId: transpId,
transportRelatedId: transpRelatedId
});
manager.createEntity(entityNames.linkedTransport, initialValues);
};
At runtime, when I call this function, I can add a linked transport to my (main) transport.
My problem: the added element is not correctly showed in my foreach knockout loop. I see that a new row is added to the table but this one is empty. I think the problem is because I don't expand any information on my newly added element but I don't know how to proceed.
Any idea how to proceed?
UPDATE
To be clear, when I add a linkedTransport to my transport, immediately after the add (and without reloading the page) I cannot read properties of the targetted (*) linkedTransport. BUT if I saveChanges AND reload my page, THEN I can read properties of the targetted linkedTransport.
When I say 'the targetted linkedTransport' I mean the transport which is referenced by my TransportRelatedId (>> public virtual Transport TransportRelated).
So If I add the linkedTransport #123 to the transport #456 THEN immediately (without reloading the page) I cannot display the Description property of the #123.
<div data-bind="with: transport()">
...
<div data-bind="foreach: linkedTransports">
<label data-bind="text: transportRelated().description()"></label>
</div>
</div>
Hope I'm clear.
UPDATE 2
As suggested by Ward, I set a breakpoint immediately after adding the linkedTransports and check the transport().linkedTransports(). Below is the results:
transport().linkedTransports()[0].transport() >> the properties are there
transport().linkedTransports()[0].transportId() >> 1
transport().linkedTransports()[0].transportNumber >> '123456' ("dummy" property, does not help)
transport().linkedTransports()[0].transportRelated() >> null
transport().linkedTransports()[0].transportRelatedId() >> 5 (the id of the linkedTransport)
So my problem is that the transportRelated() is null.
UPDATE 3
Finally I got it working. It is important to have the added (referenced) element in cache when it is added. For my case I do something like:
ctxTransport.getTransportById(5, obs);
ctxTransport.createLinkedTransport(1, 5);
So before calling the createLinkedTransport I call the getTransportById with the 5th id (the id of the linkedTransport).
Anothing important thing is how we display the linkedTransports on our page:
<div data-bind="foreach: linkedTransports">
<span class="input-control text">
<!-- ko with: transportRelated -->
<!-- /ko -->
<i class="icon-remove btn" data-bind="click: $root.removeLinkedTransport" style="cursor:pointer;"/>
</span>
</div>
Pay attention to the ko with: transportRelated followed by <a href="#" data-bind="text: id()". At first I do wrong <a href="#" data-bind="text: transportRelated().id()". We cannot proceed like that.
Updated Answer
The short of it: the related transport entity wasn't showing up because it was not in cache.
Mr. B had the ID of the related entity and used that id while creating the new transport-link entity. But that id referred to an entity which is not in cache. Therefore, the navigation property to the related transport entity returned ... null.
Breeze navigation properties do not lazy load (as a server-side ORM such as EF might do). You have to ensure that the referenced entities are in cache.
This is a design choice, not an omission. In our experience, lazy loading leads to misuse which leads to performance problems that are blamed on the framework. We decided to bypass this particular hassle .. at least for now.
See the comments for more information.
Perhaps as important ... see how we diagnosed the problem. Good lessons learned there.
Original "wrong" Answer
[This answer turned out to be "wrong" but it lead to the "right" answer. Preserved for continuity.]
How is EF treating your LinkedTransport entity? It looks like it lacks data properties of its own; it only has foreign keys to related entities. Could it be that EF regards LinkedTransport as the junction table of a many-to-many association.
As you probably know, Breeze does not (yet ... and won't soon) support many to many. What happens if you give this table a data property ... even a dummy data property such as a Boolean? That will force EF to treat it as its own entity type ... with a compound key. The OrderDetail in Northwind has this same structure: a compound key (Order, Product) and small pay load (quantity, price).
There is probably another way to tell EF not to treat this as many-to-many without adding a dummy property. I just don't remember what that is.

Entity Framework 4 - Navigation Property Object Null on Client Side

There are two tables of interest in my entity conceptual model: tblProducts and tblInstalledProducts.
Each installed product has a ProductID foreign key linking it to a specific product, which was set up automatically as a navigation property.
Within the entity domain service I have the following query:
public IQueryable<tblInstalledProduct> GetInstalledProductsBySiteID(string SiteID)
{
ObjectSet<tblInstalledProduct> installedProducts = this.ObjectContext.tblInstalledProducts;
var filterBySite =
from p in installedProducts.Include("tblProduct")
where p.SiteID == SiteID
select p;
return filterBySite;
}
I have a DataGridView bound to a DomainDataSource configured to use this query.
When I debug this query, p.tblProduct and p.tblProductReference are populated as expected. The problem arises when trying to access the tblProduct property of any tblInstalledProduct from the client side.
//Find associated install record for the selected product
tblInstalledProduct selectedInstall =
Context.tblInstalledProducts.Where(
p => p.SiteID == "Site1" && p.ProductID == 38
).First();
string productName = selectedInstall.tblProduct.ProductName;
For some reason tblProduct is always null. I've tried .Include() / .Load() and can't seem to get it to populate itself.
Why is tblInstalledProduct.tblProduct loaded up as expected on the service side of things, but is seemingly inaccessible on the client side?
Thanks for reading.
Edit:
XAML DataSource:
<telerik:RadDomainDataSource x:Key="InstalledProductsDataSource"
Name="InstalledProductsDataSource"
DomainContext="{StaticResource DomainContext}"
AutoLoad="True"
QueryName="GetInstalledProductsInfoBySiteID"
SubmittedChanges="InstalledProductsDataSource_SubmittedChanges">
<telerik:RadDomainDataSource.QueryParameters>
<telerik:QueryParameter
ParameterName="SiteID"
Value="{Binding SelectedValue,ElementName=SiteList}" />
</telerik:RadDomainDataSource.QueryParameters>
</telerik:RadDomainDataSource>
XAML DataGrid:
<telerik:RadGridView x:Name="InstalledProductsGridView"
ItemsSource="{Binding DataView, Source={StaticResource InstalledProductsDataSource}}">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="Product Name" DataMemberBinding="{Binding ProductName, Mode=TwoWay}" />
<telerik:GridViewDataColumn Header="Version" DataMemberBinding="{Binding ProductVersion, Mode=TwoWay}" />
<telerik:GridViewDataColumn Header="Description" DataMemberBinding="{Binding Description, Mode=TwoWay}" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>
Right now the grid is bound to a collection of tblProducts, but I'd like to bind it to a collection of tblInstalledProducts (as there is some extra information in that table that I need access to) like so:
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="DateInstalled" DataMemberBinding="{Binding DateInstalled, Mode=TwoWay}" />
<telerik:GridViewDataColumn Header="Product Name" DataMemberBinding="{Binding tblProduct.ProductName, Mode=TwoWay}" />
<telerik:GridViewDataColumn Header="Version" DataMemberBinding="{Binding tblProduct.ProductVersion, Mode=TwoWay}" />
<telerik:GridViewDataColumn Header="Description" DataMemberBinding="{Binding tblProduct.Description, Mode=TwoWay}" />
</telerik:RadGridView.Columns>
you need to do something like this
tblInstalledProduct selectedInstall = Context.GetInstalledProductsBySiteID("Site1").Where(p=> p.ProductID == 38 ).FirstOrDefault();
string productName="";
if(selectedInstall !=null)
{
productName= selectedInstall.tblProduct.ProductName;
}
for testing try to use;
public IQueryable<tblInstalledProduct> GetInstalledProductsNew()
{
//Im nut Sure of 'tblProduct' or 'tblProducts' it is dependent on your relations
return this.ObjectContext.tblInstalledProducts.Include("tblProduct");
}
For anyone else having problems with this, I did eventually find the solution. You need to use both .Include() on the query to tell it to load related objects, as well as the [Include] attribute in the metadata to allow those related objects to be serialized and sent to the client.

Using ListBoxFor in ASP.NET MVC 2

I am trying to update the Roles a specific group has in my application. The Group model I use in my view has an additional AllRoles IEnumerable attached to it, so that in my view I can do something like this:
<%: Html.ListBoxFor( model => model.aspnet_Roles, new MultiSelectList( Model.AllRoles, "RoleId", "RoleName" ), new { #class = "multiselect" } )%>
This generates a multiple select drop down as expected. However, coming form PHP, I noticed that the name of the select was without square brackets, maybe that is OK in ASP.NET but in PHP it is wrong.
Now, how do I go about updating the group after submiting the form, more precisely, how can I read the multiselct selected values. What I need is that based on the RoleIds that I receive to Add respective aspnet_Roles to my Group model.
Trying to read the received values using HttpContext.Request.Form["aspnet_Roles"] failed and is also ugly. Can I somehow use the model to fetch the needed data? Controller function:
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Edit( SYSGroups updatedGroup ) {}
Thanks
The selected ids will be sent as a collection:
[HttpPost]
public ActionResult Edit(string[] aspnet_Roles)
{
// the aspnet_Roles array will contain the ids of the selected elements
return View();
}
If the form contains other elements that need to be posted you could update your model:
public class SYSGroups
{
public string[] Aspnet_Roles { get; set; }
... some other properties
}
and have your action method look like this:
[HttpPost]
public ActionResult Edit(SYSGroups updatedGroup)
{
// updatedGroup.Aspnet_Roles will contain an array of all the RoleIds
// selected in the multiselect list.
return View();
}