I'm a bit lost trying to attach a pdf with populated values from an opportunity record
Here is the code:
Trigger:
trigger OpportunityTrigger on Opportunity (after insert)
if(trigger.isAfter && trigger.isUpdate) {
opportunityTriggerHelper.attachFileToOpportunityRecord(trigger.new);
}
Helper Class:
private void attachFileToOpportunityRecord(List<Opportunity> lstOpp) {
List<Id> oppListIdsForAttach = new List<Id>();
for(Opportunity opp : lstOpp) {
oppListIdsForAttach .add(opp.Id);
}
attachFileToOpportunities(oppListIdsForAttach);
}
#future(callout=true)
private static void attachFileToOppotunities(List<Id> OpportunityIds) {
List<Attachment> attachList = new List<Attachment>();
for(Id oppId : opportunityIds) {
OpportunityPdfController file = new OpportunityPdfController();
file.getData(oppId);
PageReference pdfPage = Page.PdfAttachmentForOpp;
blob pdfBody;
pdfBody = pdfPage.getContent();
Attachment attach = new Attachment();
attach.Body = pdfBody;
attach.Name = 'Pdf file';
attach.IsPrivate = false;
attach.ParenId = oppId;
attachList.add(attach);
}
insert attachList;
}
VF Page:
<apex:page controller="OpportunityPdfController" renderAs="pdf">
<apex:repeat value="{!pricingDetails}" var="pd">
<apex:outputText>{!pd.basePrice}</apex:outputText>
</apex:repeat>
</apex:page>
VF Page Controller:
public with sharing class OpportunityPdfController {
public List<PricingDetailWrapper> pricingDetails {get;set;}
public void getData(Id opportunityId) {
List<Pricing_Detail__c> pdList = [
SELECT basePrice
FROM Pricing_Detail__c
WHERE OpportunityId =: opportunityId
];
for(Pricing_Detail__c pd : pdList) {
PricingDetailWrapper pdw = new PricingDetailWrapper();
pdw.basePrice = pd.basePrice;
pricingDetails.add(pdw);
}
}
public class PricingDetailWrapper {
public String basePrice {get;set;}
}
}
The result is whenever I update an opportunity it attaches the corresponding pdf file but it is blank and if I add for example the following to vf page body: "<h1> hello World!</h1>" this works and shows as expected, but this is not happening to what I required above.
You didn't really pass the opportunity id to the VF page. And I doubt this actually works at all? If you manually access the VF page as /apex/PdfAttachmentForOpp?id=006... does it render the content ok? I'm assuming it doesn't.
Fixing the page
You didn't specify constructor so SF generates one for you, fine. I think you need to add something like
public OpportunityPdfController(){
if(ApexPages.currentPage() != null){
Id oppId = ApexPages.currentPage().getParameters().get('id');
System.debug(oppId);
getData(oppId);
}
}
Add this, try to access the page passing valid opp id and see if it renders ok, if right stuff shows in debug log. /apex/PdfAttachmentForOpp?id=006...
(VF page constructors are bigger topic, this might be simpler with standardController + extension class)
Fixing the callout
VF page (especially accessed as callout) will not share memory with the OpportunityPdfController controller you've created in the code. New object of this class will be created to support the page and your file will be ignored. You might try to make-do with some static variable holding current opportunity's id but it feels bit yucky.
In normal execute anonymous try if this returns correct pdf:
PageReference pdfPage = Page.PdfAttachmentForOpp;
pdfPage.getParameters().put('id', '006...');
Blob pdfBody = pdfPage.getContent();
System.debug(pdfBody.toString());
If it works - use similar trick in the actual code, pass the id as url parameter.
Related
I'm working on a Vue.JS project and I'm facing a strange error.
I got a WidgetDTO object filled by an axios.get request, it contains a WidgetParametersDTO object and it's defined like so:
export class WidgetParametersDTO {
[...]
public coverage: number = 0;
public colorOK: string = "";
public colorKO: string = "";
public constructor(parameters: any) {
this.coverage = parameters.coverage
this.colorOK = parameters.colorOK;
this.colorKO = parameters.colorKO;
}
}
export class WidgetDTO {
[...]
public id: string = null!;
public parameters: WidgetParametersDTO = null!;
public constructor(widgetDTO: any) {
this.id = widgetDTO.id;
this.parameters = new WidgetParametersDTO(widgetDTO.parameters);
}
When I print the full object widget.parameters, all attributes are correct, when I use widget.parameters.colorOK or widget.parameters.colorKO it works properly, but if I try to use widget.parameters.coverage, it shows 0 instead of the value printed earlier.
The type is correct (number) and I already tried several things to ensure the presence and correctness of the data, it's only acting like that when I use the attribute directly.
There's even more, I'm using npm run dev to be able to hot reload the changes as things progress. When I change some code and save, it reloads the page and prints the right value, but not when I'm hitting F5.
I tried to build the files and run the server with npm start but it didn't work.
Any advice ?
Thanks.
I have an MVC 4 application that sends out multiple emails. For example, I have an email template for submitting an order, a template for cancelling an order, etc...
I have an Email Service with multiple methods. My controller calls the Send method which looks like this:
public virtual void Send(List<string> recipients, string subject, string template, object data)
{
...
string html = GetContent(template, data);
...
}
The Send method calls GetContent, which is the method causing the problem:
private string GetContent(string template, object data)
{
string path = Path.Combine(BaseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return Engine.Razor.RunCompile(content, "htmlTemplate", null, data);
}
I am receiving the error:
The same key was already used for another template!
In my GetContent method should I add a new parameter for the TemplateKey and use that variable instead of always using htmlTemplate? Then the new order email template could have newOrderKey and CancelOrderKey for the email template being used to cancel an order?
Explanation
This happens because you use the same template key ("htmlTemplate") for multiple different templates.
Note that the way you currently have implemented GetContent you will run into multiple problems:
Even if you use a unique key, for example the template variable, you will trigger the exception when the templates are edited on disk.
Performance: You are reading the template file every time even when the template is already cached.
Solution:
Implement the ITemplateManager interface to manage your templates:
public class MyTemplateManager : ITemplateManager
{
private readonly string baseTemplatePath;
public MyTemplateManager(string basePath) {
baseTemplatePath = basePath;
}
public ITemplateSource Resolve(ITemplateKey key)
{
string template = key.Name;
string path = Path.Combine(baseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return new LoadedTemplateSource(content, path);
}
public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
{
return new NameOnlyTemplateKey(name, resolveType, context);
}
public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
throw new NotImplementedException("dynamic templates are not supported!");
}
}
Setup on startup:
var config = new TemplateServiceConfiguration();
config.Debug = true;
config.TemplateManager = new MyTemplateManager(BaseTemplatePath);
Engine.Razor = RazorEngineService.Create(config);
And use it:
// You don't really need this method anymore.
private string GetContent(string template, object data)
{
return Engine.Razor.RunCompile(template, null, data);
}
RazorEngine will now fix all the problems mentioned above internally. Notice how it is perfectly fine to use the name of the template as key, if in your scenario the name is all you need to identify a template (otherwise you cannot use NameOnlyTemplateKey and need to provide your own implementation).
Hope this helps.
(Disclaimer: Contributor of RazorEngine)
I am new to Zend and very keen to learn, so I would really appreciate some help and guidance.
I am trying to create a 'method in a class' that will save the session variables of product pages visited by members to a site i.e
i,e examplesite com/product/?producttype= 6
I want to save the number 6 in a session variable. I also do not want to have a global session for the entire site; I just want it for selected pages. So, I guess I have to have Zend_Session::start() on the selected page; but I am not clear how this should be done.
Should I instantiate it in the page view page. i.e products page or do this in the indexAction() method for the products page. I have attempted to instantiate it below but it did not work.
public function rememberLastProductSearched()
{ //my attempt to start a session start for this particular page.
Zend_Session::start();
}
$session->productSearchCategory = $this->_request->getParam('product-search-category');
return" $session->productSearchCategory ";
}
else
{
//echo " nothing there
return " $session->productSearchCategory";
//";
}
}
With the rememberLastProductSearched() method I was trying to get the method to first check whether the user had searched for a new product or just arrived at the page by default. i.e whether he had used the get() action to search for a new product. If the answer is no, then I wanted the system to check whether their had been a previous saved session variable. so in procedural syntax it would have gone like this:
if(isset($_Get['producttype']))
{
//$dbc database connection
$producttype = mysqli_real_escape_string($dbc,trim($_GET['producttype']));
}
else
if(isset($_SESSION['producttype'])){
$producttype = mysqli_real_escape_string($dbc,trim($_SESSION['producttype']));
}
Can you please help me with the Zend/oop syntax. I am totally confused how it should be?
you're asking about simple work flow in an action, it should begin something like:
//in any controller
public function anyAction()
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
//get value
$productCategory = $this->getRequest()->getParam('producttype');
//save value to namespace
$session->productType = $productCategory;
//...
}
now to move this off to a separate method you have to pass the data to the method...
protected function rememberLastProductSearched($productType)
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
$session->productType = $productType;
}
So now if you want to test for presence of a value...
//in any controller
public function anyAction()
{
//open seesion, call the namespace whenever you need to access it
$session = new Zend_Session_Namespace('products');
if (!isset($session->productType)) {
$productCategory = $this->getRequest()->getParam('producttype');
//save value to session
$this->rememberLastProductSearched($productCategory)
} else {
$productCategory = $session->productType;
}
}
That's the idea.
Be mindful of your work flow as it can sometimes be very simple to inadvertently overwrite your session values.
$session = new Zend_Session_Namespace("productSearch");
if ($this->getRequest()->getParam('producttype')) { //isset GET param ?
$session->productType = $this->getRequest()->getParam('producttype');
$searchedProductType = $session->productType;
} else { //take the session saved value
if ($session->productType) {
$searchedProductType = $session->productType;
}
}
//now use $searchedProductType for your query
Here's my migration code:
public Migrations(IRepository<ProductPartRecord> productPartRepository, IRepository<CategoryPartRecord> categoryPartRepository)
{
_productPartRepository = productPartRepository;
_categoryPartRepository = categoryPartRepository;
}
public int Create() {
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("AutoroutePart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("AutoroutePart", partBuilder => partBuilder
.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Category Title', Pattern: 'category/{Content.Slug}', Description: 'category/category-title'}]")));
SchemaBuilder.CreateTable("CategoryPartRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<string>("Description")
.Column<string>("Image")
);
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CategoryPart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.Creatable()
.Draftable());
return 1;
}
public int UpdateFrom1() {
_categoryPartRepository.Create(new CategoryPartRecord { Name = "Category1", Description = "Description1", Image = "Image1" });
return 2;
}
UpdateFrom1 obviously attempts to insert a dummy record, but this causes nHibernate to throw this exception:
"attempted to assign id from null one-to-one property: ContentItemRecord"
The Part Record looks like:
public class CategoryPartRecord : ContentPartRecord {
public CategoryPartRecord()
{
CategoryProducts = new List<CategoryProductRecord>();
}
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Image { get; set; }
public virtual IList<CategoryProductRecord> CategoryProducts { get; set; }
}
Any clues as to where I'm going wrong here? Google produced nothing.
Okay, so you are creating a contentpartrecord, not a content item there. What you want is something more along the lines of:
var item = _orchardServices.ContentManager.New("Category").As<CategoryPart>();
item.Name = "Bobs Item"; // Something like that...
item.ContentItem.As<TitlePart>().Title = "Yay a title"; // This syntax may be wrong, I'm very tired
_orchardServices.ContentManager.Create(item);
_orchardServices.ContentManager.Publish(item.ContentItem);
I think that is how you would do it. Maybe you would want to look into creating content items using the import/export module, that is the more common and safe way to do it.
Not sure if the answer from Hazza works. Haven't tried that.
I usually just do this: (But not sure if it's an inferior approach in some way)
var item = _orchardServices.ContentManager.New("Category");
var cpart = item.As<CategoryPart>();
var tpart = item.As<TitlePart>();
cpart.Name = "SomeName";
tpart.Title = "SomeTitle";
_orchardServices.ContentManager.Create(item);
But to address the comment by Lawrence Johnson:
Category in this case is the content item. He is creating a new Category content item, and then extracting the corresponding CategoryPart from it.
If you are getting null when trying to extract the part you're probably missing something.
In order for this to work you need to implement the CategoryPart, CategoryPartRecord, CategoryPartHandler and CategoryPartDriver. (And of course make sure to attach your CategoryPart to you Category content item. Not certain if placement.info is required, but would add it for consistency anyway.)
You can't leave any of these out if you plan to use a Part attached to a content item.
I'm not sure if/how you can create a Part with no content item, but you can create a Record with no part and no content item (Just make sure you don't inherit ContentPartRecord in your record object). If you simply want to add a record with no part or content item, then the code in UpdateFrom1 used by Ben Power would work for creating a record. (But migration part would have to be changed, taking out the content item and part, and manually setting the Id to be a primary key for the record)
I was trying to build a bean that always retrieves the same document ( a counter document), gets the current value, increment it and save the document with the new value. Finally it should return the value to the calling method and that would get me a new sequential number in my Xpage.
Since the Domino objects cannot be serialized or singleton'ed what's the benefit creating a bean doing this, over creating a SSJS function doing the exact same thing?
My bean must have calls to session, database, view and document, which then will be called every time.
The same within the SSJS-function except for session and database.
Bean:
public double getTransNo() {
try {
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
view.refresh();
doc = view.getFirstDocument();
transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
return transNo;
}
SSJS:
function getTransNo() {
var view:NotesView = database.getView("vCount");
var doc:NotesDocument = view.getFirstDocument();
var transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
return transNo;
}
Thank you
Both pieces of code are not good (sorry to be blunt).
If you have one document in your view, you don't need a view refresh which might be queued behind a refresh on another view and be very slow. Presumably you are talking about a single sever solution (since replication of the counter document would for sure lead to conflicts).
What you do in XPages is to create a Java class and declare it as application bean:
public class SequenceGenerator {
// Error handling is missing in this class
private double sequence = 0;
private String docID;
public SequenceGenerator() {
// Here you load from the document
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
doc = view.getFirstDocument();
this.sequence = doc.getItemValueDouble("count");
this.docID = doc.getUniversalId();
Utils.shred(doc, view); //Shred currenDatabase isn't a good idea
}
public synchronized double getNextSequence() {
return this.updateSequence();
}
private double updateSequence() {
this.sequence++;
// If speed if of essence I would spin out a new thread here
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
doc = db.getDocumentByUnid(this.docID);
doc.ReplaceItemValue("count", this.sequence);
doc.save(true,true);
Utils.shred(doc);
// End of the candidate for a thread
return this.sequence;
}
}
The problem for the SSJS code: what happens if 2 users hit that together? At least you need to use synchronized there too. Using a bean makes it accessible in EL too (you need to watch out not to call it too often). Also in Java you can defer the writing back to a different thread - or not write it back at all and in your class initialization code read the view with the actual documents and pick the value from there.
Update: Utils is a class with static methods:
/**
* Get rid of all Notes objects
*
* #param morituri = the one designated to die, read your Caesar!
*/
public static void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}