I am working on a customisation for CRM 2011. I have created a custom entity (which is based on activity so is an activity type aswell), created the relationship to account and on the account form created a custom navigation link pointing to a web resource which will power the custom display (this is just a html page with JavaScript).
The Web resource is used instead of a standard relationship link because it is doing a listings and preview display and not just the list view that the out of the box feature provides.
What i am trying to do is show the activities tab (the activities ribbon tab) on the accounts form, whenever the custom navigation link is pressed and a user goes into that section, in the same way it displays when you click the activities navigation link. I can't seem to find that much information on Tab Display Rules as i think that is the place (if this is possible) that it should be done.
Also, if there is a JavaScript way of doing it (although i have not found any) then that would work as well.
We have a solution.
DISCLAIMER This solution is not recommended (or supported) as it is a complete hack of epic proportions, but it solved the issue for us. this is a temporary bit of functionality as we build out a custom solution which will better fit the client, so it will not be there moving forwards which is why the hack worked for us. Additionally, a scripts could be written much better, we simply wanted to get it out the door.
NOTE: This solution will use some external libraries such as JQuery & CRM FetchKit
1) Adding a relationship to the custom object and account
We have a custom entity, and we created a 1:N relationship from the Account entity to our custom entity. What this does enables us to do is create a navigation link on the account form which points to an associated view of our custom entity. Once the link is in, we save and publish changes.
2) Get the id of the new navigation link created
The link above should now be on the form, so after saving and publishing we go to the live view and using the IE developer tools we get the id of the link as we need to catch the onclick and do some things with it later.
3) Decorating the onclick of the navigation link created
We have the id so and we want to decorate the onclick with some additional functionality. We create 2 new web resources:
"YourCustomEntity"_init : Javascript Web Resource: this will be used in the onload of the account form to get the link we created and alter the onclick to do some additional things
YourCustomEntity_page : HTML Page Web Resource: As per the original question, we have a additional requirement of showing a preview pane which is why we couldn't use the standard grid
Code for "YourCustomEntity"_init
Code is pretty basic and doesn't have any object caching or the like it was simply written to solve our problem. I have added comments to the code. Also Entity is a generic name, this was the name of our custom type. We set the original associated view to hidden instead of display:none because we still need it to load in the background as this is where the ribbon gets updated and loads, so there is definitely some script going on to do this, wish we knew it so we could just use that :)
function previewEntityInit(){
//Catch any navigation link click, we need this because we need to hide our
//grid when we are not on the custom entity page
$('.ms-crm-Nav-Subarea-Link, .ms-crm-FormSelector-SubItem').click(function () {
//Get the id of the link clicked and the frame injected object
var id = $(this).attr('id'),
cFrame = $('#entity_frame_preview');
//if the frame has already been injected
if(cFrame.length !== 0) {
//If we are not in the correct section
//(i.e. activities nav link was clicked) hide it
if (id !== 'nav_new_account_new_entity') {
cFrame.attr('style', 'display:none;');
}
else{
//The correct link was clicked, so show it
cFrame.attr('style', 'display:block; width:100%; height:100%;');
$('#new_account_new_entityFrame').attr('style', 'visibility: hidden;');
}
}
else{
//This is the first time the correct link has been clicked
//So we hide the default associated view and inject our
//own iframe point to the YourCustomEntity_page embedded resource we created
var src = Xrm.Page.context.getServerUrl();
if (id === 'nav_new_account_new_entity') {
$('#new_account_new_entityFrame').attr('style', 'visibility: hidden;');
$('<iframe />', {
id: 'entity_frame_preview',
src: src + '/WebResources/new_entity_page',
width: '100%',
height: '100%'
}).prependTo('#tdAreas');
}
}
});
YourCustomEntity_page
Not going to show all the code here, but we based ours on this link:
Preview Form Link
We changed it in the following ways:
Only the view name is hardcodes, the rest is taken via code (code below)
We don't use the second iframe opting instead to have a simple HTML section which loaded this (code for this and our load function below)
No Hard Coded Values
var server = Xrm.Page.context.getServerUrl();
var orgName = "";
// grid related settings
var entityId = window.parent.Xrm.Page.data.entity.getId();
var entityType = "1";
var areaView = "new_account_new_entity";
var internalGridElement = "crmGrid_new_account_new_entity";
var timeoutKey = null;
HTML For Preview
<div id="previewForm" style="display: none;">
<ol>
<li><span class="lbl">Account:</span><span id="lblAccount"></span></li>
<li><span class="lbl">Created:</span><span id="lblDate"></span></li>
<li><span class="lbl">Regarding:</span><span id="lblRegarding"></span></li>
<li><span class="lbl">Owner:</span><span id="lblOwner"></span></li>
</ol>
<div id="subject">
<span class="lbl">Subject:</span><span id="lblSubject" class="value"></span>
</div>
<div>
<span id="lblMsg"></span>
</div>
</div>
LoadPreview Code
This uses an external library called CRMFetchKit. The code actually does three fetch calls, this is not ideal and it should be one really (using joins, google it :)), but this wasn't working and was dragging so we decided just to go with three as this whole section will be replaced with a managed solution soon.
function LoadPreviewPane(entity) {
if (entity != null) {
$('#previewForm').show();
var fetchxml = ['<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">',
' <entity name="new_entity">',
'<attribute name="subject" />',
'<attribute name="description" />',
'<attribute name="createdon" />',
'<attribute name="new_account" />',
'<attribute name="ownerid" />',
' <filter type="and">',
' <condition attribute="activityid" operator="eq" value="' + entity.Id + '" />',
' </filter>',
' </entity>',
'</fetch>'].join('');
CrmFetchKit.Fetch(fetchxml).then(function (results) {
var account = window.parent.Xrm.Page.getAttribute("name").getValue(),
subject = results[0].getValue('subject'),
desc = results[0].getValue('description'),
created = new Date(results[0].getValue('createdon')),
theDate = created.getDate() + "/" + (created.getMonth() + 1) + "/" + created.getFullYear(),
owner = '',
regarding = '';
var fetchxml2 = ['<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">',
' <entity name="systemuser">',
'<attribute name="fullname" />',
' <filter type="and">',
' <condition attribute="systemuserid" operator="eq" value="' + results[0].getValue('ownerid') + '" />',
' </filter>',
' </entity>',
'</fetch>'].join('');
CrmFetchKit.Fetch(fetchxml2).then(function (users) {
owner = users[0].getValue('fullname');
$('#lblOwner').html(owner);
});
var fetchxml3 = ['<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">',
' <entity name="account">',
'<attribute name="name" />',
' <filter type="and">',
' <condition attribute="accountid" operator="eq" value="' + results[0].getValue('new_accountname') + '" />',
' </filter>',
' </entity>',
'</fetch>'].join('');
CrmFetchKit.Fetch(fetchxml3).then(function (regardings) {
regarding = regardings[0].getValue('name');
$('#lblRegarding').html(regarding);
});
$('#lblAccount').html(account);
$('#lblSubject').html(subject);
$('#lblMsg').html(nl2br(desc));
$('#lblDate').html(theDate);
});
}
}
Related
I'd like to extend the users content definition to include a short bio and picture that can be viewed on every blog post of an existing blog. I'm unsure of what the best method to do this is.
I have tried extending the User content type with those fields, but I can't seem to see them in the Model using the shape tracing tool on the front end.
Is there a way to pass through fields on the User shape in a blog post? If so, what is the best way to do it?
I also have done this a lot, and always include some custom functionality to achieve this.
There is a way to do this OOTB, but it's not the best IMO. You always have the 'Owner' property on the CommonPart of any content item, so in your blogpost view you can do this:
#{
var owner = Model.ContentItem.CommonPart.Owner;
}
<!-- This automatically builds anything that is attached to the user, except for what's in the UserPart (email, username, ..) -->
<h4>#owner.UserName</h4>
#Display(BuildDisplay((IUser) owner))
<!-- Or, with specific properties: -->
<h1>#T("Author:")</h1>
<h4>#owner.UserName</h4>
<label>#T("Biography")</label>
<p>
#Html.Raw(owner.BodyPart.Text)
</p>
<!-- <owner content item>.<Part with the image field>.<Name of the image field>.FirstMediaUrl (assuming you use MediaLibraryPickerField) -->
<img src="#owner.User.Image.FirstMediaUrl" />
What I often do though is creating a custom driver for this, so you can make use of placement.info and follow the orchard's best practices:
CommonPartDriver:
public class CommonPartDriver : ContentPartDriver<CommonPart> {
protected override DriverResult Display(CommonPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Common_Owner", () => {
if (part.Owner == null)
return null;
var ownerShape = _contentManager.BuildDisplay(part.Owner);
return shapeHelper.Parts_Common_Owner(Owner: part.Owner, OwnerShape: ownerShape);
});
}
}
Views/Parts.Common.Owner.cshtml:
<h1>#T("Author")</h1>
<h3>#Model.Owner.UserName</h3>
#Display(Model.OwnerShape)
Placement.info:
<Placement>
<!-- Place in aside second zone -->
<Place Parts_Common_Owner="/AsideSecond:before" />
</Placement>
IMHO the best way to have a simple extension on an Orchard user, is to create a ContentPart, e.g. "UserExtensions", and attach it to the Orchard user.
This UserExtensions part can then hold your fields, etc.
This way, your extensions are clearly separated from the core user.
To access this part and its fields in the front-end, just add an alternate for the particular view you want to override.
Is there a way to pass through fields on the User shape in a blog post?
Do you want to display a nice picture / vita / whatever of the blog posts author? If so:
This could be your Content-BlogPost.Detail.cshtml - Alternate
#using Orchard.Blogs.Models
#using Orchard.MediaLibrary.Fields
#using Orchard.Users.Models
#using Orchard.Utility.Extensions
#{
// Standard Orchard stuff here...
if ( Model.Title != null )
{
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ( (string)Model.ContentItem.ContentType ).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
// And here we go:
// Get the blogPost
var blogPostPart = (BlogPostPart)Model.ContentItem.BlogPostPart;
// Either access the creator directly
var blogPostAuthor = blogPostPart.Creator;
// Or go this way
var blogPostAuthorAsUserPart = ( (dynamic)blogPostPart.ContentItem ).UserPart as UserPart;
// Access your UserExtensions part
var userExtensions = ( (dynamic)blogPostAuthor.ContentItem ).UserExtensions;
// profit
var profilePicture = (MediaLibraryPickerField)userExtensions.ProfilePicture;
}
#tag.StartElement
<header>
#Display(Model.Header)
#if ( Model.Meta != null )
{
<div class="metadata">
#Display(Model.Meta)
</div>
}
<div class="author">
<img src="#profilePicture.FirstMediaUrl"/>
</div>
</header>
#Display(Model.Content)
#if ( Model.Footer != null )
{
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
Hope this helps, here's the proof:
I have a PXNumberEdit field which, on enter, adds a product to a grid on a customized SO301000 page. The insert works and the field
is emptied after the product is added. However, I would like to return the focus to that field. There doesn't seem to be
a SetFocus method for the field.
I have tried using the SO301000.cs code behind to set the focus, by adding a function for onValueChanged
to save the object as session variable and on pageload to set the focus on the saved object. This causes
the page to never finish loading.
I have also tried to use jquery in various ways but that hasn't worked either. Is there a way to do this?
The Hack
There is no SetFocus method in the framework like there is for SetEnabled/SetDisplayName/SetVisibility because most events are raised on focus changes and the framework ensures that the focus is not lost on every record updates. To set it up manually, you will then need to wait that the callback is completed before setting the focus.
To do so you will need to add a Javascript delegate to the list of event handlers to be called once the callback is over. The following code will set the focus on Customer Reference Nbr. every time Customer ID is changed (in SO301000):
<script type="text/javascript">
function FormView_Load() {
px_callback.addHandler(setFocusOnCustRef);
return;
}
var setFocus = false;
function CustomerID_ValueChanged() {
setFocus = true;
return;
}
function setFocusOnCustRef(context, error) {
if (setFocus === true)
{
setFocus = false;
var refNbr = px_alls["edCustomerRefNbr"];
refNbr.focus();
}
return;
}
</script>
<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-index: 100" Width="100%" DataMember="Document" Caption="Order Summary"
NoteIndicator="True" FilesIndicator="True" LinkIndicator="True" EmailingGraph="PX.Objects.CR.CREmailActivityMaint,PX.Objects"
ActivityIndicator="True" ActivityField="NoteActivity" DefaultControlID="edOrderType" NotifyIndicator="True"
TabIndex="14900" ClientEvents-Initialize="FormView_Load">
...
<px:PXSegmentMask CommitChanges="True" ID="edCustomerID" runat="server" DataField="CustomerID" AllowAddNew="True"
AllowEdit="True" DataSourceID="ds" ClientEvents-ValueChanged="CustomerID_ValueChanged"/>
...
</px:PXFormView>
Note that I added ClientEvents-Initialize="FormView_Load" on the PXFormView and ClientEvents-ValueChanged="CustomerID_ValueChanged" on the CustomerID's PXSegmentedMask.
As you can see this is a hack... When the setFocusOnCustRef is raised we have just refreshed the record (RowSelected) and we don't know what was changed prior to that (what field has been changed? was the change canceled?). The context that is passed to the delegate is only related to re-updating the records. To get a better understanding of what events are raised and in which order, please refer to the Update Scenario Event Model:
Thoughts and Tips
I don't know much of your implementation but I would like to point out that your needs look very similar to the function Add Stock Item that opens a SmartPanel with the buttons Add/Add & Close/Cancel. If the callback is raised from a button you will have meaningful information in your context and won't need to add a Javascript event on ValueChanged.
When you Save/Cancel. The focus will return to your first form element in the tab order (if successful).
You can set the Tab Order directly in the PXUIFieldAttribute :
PXUIField(DisplayName = "Asset ID", Visibility = PXUIVisibility.SelectorVisible, TabOrder=1)]
How do you access html controls inside an iframe from javascript in CRM?
I have:
var height = document.getElementById("IFRAME_TransactionProduct_RA").contentWindow.document.getElementById("txt").value;
but that results in "Error on page" and the content is not loaded.
The element I want to access is an html input with id of 'txt':
<input id="txt" type="hidden" />
Here's an example how you copy a value from a CRM field to a control in an embedded HTML control in an IFRAME. I'm assuming the names of the web resource and the field. You'll have to adapt those. You also might throw in a try-catch in case CRM throws in en exception (got the joke?) and please mind that I'm typing the code on my phone so there might be a typo somewhere (auto-correction, yey).
var source = Xrm.Page.data.entity.attributes.get("oneCoolField")
var information = source.getValue();
var customHtml = Xrm.Page.ui.controls.get("WebResource_EmbeddedHtmlContent");
var destination = customHtml.getObject().contentWindow.document;
if(destination) {
var customControl = destination.getElementById("elementToAccess");
if(customControl) {
customControl.value = information;
}
}
EDIT:
This gets you to the web resource.
var customHtml = Xrm.Page.ui.controls.get("WebResource_EmbeddedHtmlContent");
This gets you to the DOM of the IFRAME.
var destination = customHtml.getObject().contentWindow.document;
This gets you to the control on the custom page.
var customControl = destination.getElementById("elementToAccess");
This gets you the contents of the control.
var contents = customControl.innerHTML;
Which part fails on your computer?
With jQuery:
$(Xrm.Page.ui.controls.get('IFRAME_TransactionProduct_RA').getObject()).contents().find('#txt').val();
Pure JS:
Xrm.Page.ui.controls.get('IFRAME_TransactionProduct_RA').getObject().contentWindow.document.getElementById('txt').value;
http://msdn.microsoft.com/en-us/library/gg334266.aspx#BKMK_getObject
I want to get the value of the selected item of a radiobutton List using javascript.
My code is:
<asp:RadioButtonList runat="server" ID="Radio" RepeatColumns="3" CssClass="textfont">
<asp:ListItem Value="1" Selected="True">First</asp:ListItem>
<asp:ListItem Value="2">Second</asp:ListItem>
<asp:ListItem Value="3">Third</asp:ListItem>
</asp:RadioButtonList>
And this is my Javascript code:
<script type="text/javascript">
function sendParameters() {
var Id = '<%=HiddenField1.Value%>';
var ddl1 = document.getElementById("Radio").checked;
</script>
How to proceed?
First of all please view the source of the resulting web page and post the resulting radio button html. This will make it easier to answer because then the question comes down to plain HTML and jQuery.
The Reason is asp often changes the name of the ID, unless you add ClientIDMode="Static" to your control.
Once that is done this should do it:
var chosenValue = $('input:radio[id="Radio"]:checked').val();
alert(chosenValue);
I am using a customised version of search-theme-form.tpl
When I use the search box, I do get transferred to the search page. But the search does not actually take place. The search box on the search results page does work though. This is my search-them-form.tpl.php file (demo :
<input type="text" name="search_theme_form_keys" id="edit-search-theme-form-keys" value="Search" title="Enter the terms you wish to search for" class="logininput" height="24px" onblur="restoreSearch(this)" onfocus="clearInput(this)" />
<input type="submit" name="op" id="edit-submit" value="" class="form-submit" style="display: none;" />
<input type="hidden" name="form_token" id="edit-search-theme-form-form-token" value="<?php print drupal_get_token('search_theme_form'); ?>" />
<input type="hidden" name="form_id" id="edit-search-theme-form" value="search_theme_form" />
There is also a javascript file involved. I guess it's use is pretty clear from the code:
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
function clearInput(e) {
e.value=""; // clear default text when clicked
e.className="longininput_onfocus"; //change class
}
function restoreSearch(e) {
if (trim(e.value) == '') {
{
e.value="Search"; // reset default text onBlur
e.className="logininput"; //reset class
}
}
}
What can be the problem and how can I fix it?
Apparently, you cannot directly modify the HTML in search-theme-form.tpl.php since thats not the right way to do it. So my adding the class and onFocus and onBlur attributes was the problem.
The correct way to do it is to modify the themes template.php file. Basically we will be using form_alter() to modify the form elements. Since using the HTML way is wrong. Take a look at the code below (taken from : here )
<?php
/**
* Override or insert PHPTemplate variables into the search_theme_form template.
*
* #param $vars
* A sequential array of variables to pass to the theme template.
* #param $hook
* The name of the theme function being called (not used in this case.)
*/
function yourthemename_preprocess_search_theme_form(&$vars, $hook) {
// Note that in order to theme a search block you should rename this function
// to yourthemename_preprocess_search_block_form and use
// 'search_block_form' instead of 'search_theme_form' in the customizations
// bellow.
// Modify elements of the search form
$vars['form']['search_theme_form']['#title'] = t('');
// Set a default value for the search box
$vars['form']['search_theme_form']['#value'] = t('Search this Site');
// Add a custom class and placeholder text to the search box
$vars['form']['search_theme_form']['#attributes'] = array('class' => 'NormalTextBox txtSearch',
'onfocus' => "if (this.value == 'Search this Site') {this.value = '';}",
'onblur' => "if (this.value == '') {this.value = 'Search this Site';}");
// Change the text on the submit button
//$vars['form']['submit']['#value'] = t('Go');
// Rebuild the rendered version (search form only, rest remains unchanged)
unset($vars['form']['search_theme_form']['#printed']);
$vars['search']['search_theme_form'] = drupal_render($vars['form']['search_theme_form']);
$vars['form']['submit']['#type'] = 'image_button';
$vars['form']['submit']['#src'] = path_to_theme() . '/images/search.jpg';
// Rebuild the rendered version (submit button, rest remains unchanged)
unset($vars['form']['submit']['#printed']);
$vars['search']['submit'] = drupal_render($vars['form']['submit']);
// Collect all form elements to make it easier to print the whole form.
$vars['search_form'] = implode($vars['search']);
}
?>
In yourthemename_preprocess_search_theme_form - 'yourthemename' will obviously reflect the name of your custom theme. Basically the code is self-explanatory. what with the comments and all.
So, basically thats the way it works.