I want to create a custom web part that has more than 1 filter web part and that can be connected to Report Viewer Web Part (Integrated Mode) at runtime/design time.
I searched a lot for this, but could not find a way to have single web part that is a provider to more than 1 filters.
Say for example -
My Report accepts 2 parameter Department and Region.
I want to connect both parameters with single web part having two drop down (one for Department and one for Region)
Values from both the drop down should be passed to Department and Region
Report should be rendered in Report Viewer Web Part
Solution Tried so far
Create a web part that adds two custom drop down
Custom drop down class that implements from ITransformableFilterValues
Have 2 methods on the web pat each having ConnectionProvider attribute and return instance of drop down control
Problem:
Even though 2 connection option is shown on my custom filter web part only one can be added.
For example if I connect Filter1(custom web part) to Department then I am unable to connect it to Report Viewer web part again.
My web part have methods like this:
[ConnectionProvider("Departmet", "UniqueIDForDept", AllowsMultipleConnections = true)]
public ITransformableFilterValues ReturnCity()
{
return dropDownDepartment; // It implemets ITransformableFilterValues
}
[ConnectionProvider("Region", "UniqueIDForRegion", AllowsMultipleConnections = true)]
public ITransformableFilterValues ReturnMyRegionB()
{
return dropDownRegion; //It implemets ITransformableFilterValues
}
I did something similar. This might help point you in the right direction. I used data in a form library to create a detailed report. I used reporting services and connected to sharepoint using web services. http://server/_vti_bin/Lists.asmx. The report parameter I used was the item ID or GUID. Then I configured my report viewer. On the form library I used JavaScript to override the context menu to add "View Report". On the report page I used a Query String filter to grab the item ID out of the url.
Not sure if you were able to fix your problem..
Actually I tried with AllowsMultipleConnections = true and it worked fine:
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using aspnetwebparts = System.Web.UI.WebControls.WebParts;
using Microsoft.Office.Server.Utilities;
using wsswebparts = Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Portal.WebControls;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Microsoft.SharePoint.Utilities;
namespace FromMultiSource
{
[Guid("a0d068dd-9475-4055-a219-88513e173502")]
public class MultiSource : aspnetwebparts.WebPart
{
List<wsswebparts.IFilterValues> providers = new List<wsswebparts.IFilterValues>();
public MultiSource()
{
}
[aspnetwebparts.ConnectionConsumer("Multiple Source Consumer", "IFilterValues", AllowsMultipleConnections = true)]
public void SetConnectionInterface(wsswebparts.IFilterValues provider)
{
this.providers.Add(provider);
if (provider != null)
{
List<wsswebparts.ConsumerParameter> l = new List<wsswebparts.ConsumerParameter>();
l.Add (new wsswebparts.ConsumerParameter ("Value", wsswebparts.ConsumerParameterCapabilities.SupportsMultipleValues | Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsAllValue));
provider.SetConsumerParameters(new ReadOnlyCollection<wsswebparts.ConsumerParameter>(l));
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
// TODO: add custom rendering code here.
// Label label = new Label();
// label.Text = "Hello World";
// this.Controls.Add(label);
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
this.EnsureChildControls();
foreach (wsswebparts.IFilterValues provider in this.providers)
{
if (provider != null)
{
string prop = provider.ParameterName;
ReadOnlyCollection<string> values = provider.ParameterValues;
if (prop != null && values != null)
{
writer.Write("<div>" + SPEncode.HtmlEncode(prop) + ":</div>");
foreach (string v in values)
{
if (v == null)
{
writer.Write("<div> <i>"(empty)"/null</i></div>");
}
else if (v.Length == 0)
{
writer.Write("<div> <i>empty string</i></div>");
}
else
{
writer.Write("<div> " + v + "</div>");
}
}
}
else
{
writer.Write("<div>No filter specified (all).</div>");
}
}
else
{
writer.Write("<div>Not connected.</div>");
}
writer.Write("<hr>");
}
}
}
}
Related
I have added a new custom field to SOShipment and SOShipmentFilter. I am trying to use it to filter the grid for Process Shipments and I am having problems with the code. I have done other customizations where I have extended code but I was able to call the baseHandler first and then execute my snippet when it returned. However when I override the delegate function, it just sets up a template with a return to baseMethod. When I put my code in to include the new filter field, I get compile errors for undefined fields/references. Do I need to copy all of the original code from the original delegate and include it in my override function? Below is my current override code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.AR;
using PX.Objects.CM;
using POReceipt = PX.Objects.PO.POReceipt;
using POReceiptLine = PX.Objects.PO.POReceiptLine;
using POLineType = PX.Objects.PO.POLineType;
using PX.Objects;
using PX.Objects.SO;
namespace PX.Objects.SO
{
public class SOInvoiceShipment_Extension:PXGraphExtension<SOInvoiceShipment>
{
#region Event Handlers
public delegate IEnumerable ordersDelegate();
[PXOverride]
public IEnumerable orders(ordersDelegate baseMethod)
{
if (filter.usrTruckNbr != null)
{
((PXSelectBase<SOShipment>)cmd).WhereAnd<Where<SOShipment.usrTruckNbr, GreaterEqual<Current<SOShipmentFilter.usrTruckNbr>>>>();
}
return baseMethod();
}
protected virtual void SOShipmentFilter_UsrTruckNbr_CacheAttached(PXCache cache)
{
}
#endregion
}
}
cmd is a location variable and you cannot access it from your extension. I see two ways you could achieve the result you want:
Copy the whole delegate function to your extension. This is not ideal because it will have to be verified and updated with every new version of Acumatica.
Filter data on the client side after it is returned from the original delegate but before it is displayed on screen. This is not as efficient as filtering on SQL but at least would remove the need to copy too much code to your extension. Here's a complete sample which will filter the list of shipments to return only documents where shipment quantity is even:
public class SOInvoiceShipment_Extension : PXGraphExtension<SOInvoiceShipment>
{
[PXFilterable]
public PXFilteredProcessing<SOShipment, SOShipmentFilter> Orders;
protected IEnumerable orders()
{
// Filter the list of shipments to return only documents where shipment quantity is even
foreach (SOShipment shipment in Base.Orders.Select())
{
if(shipment.ShipmentQty % 2 == 0)
{
yield return shipment;
}
}
}
public void SOShipmentFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row == null) return;
SOShipmentFilter filter = e.Row as SOShipmentFilter;
if (filter != null && !string.IsNullOrEmpty(filter.Action))
{
Dictionary<string, object> parameters = Base.Filter.Cache.ToDictionary(filter);
Orders.SetProcessTarget(null, null, null, filter.Action, parameters);
}
}
}
In all cases, you should not use PXOverride to override the view delegate; the customization tools unfortunately generate the wrong method signature when you try to override a delegate, and this will be improved. You should refer to the T300 training available here for more information on this type of customization (look for "Declaring or Altering a BLC Data View Delegate).
OK - I've created a custom navigation provider that inherits PortalSiteMapProvider and I'm trying to extend my class via the GetChildNodes method (Simple example here).
Essentially what I'm trying to do is add the current SPWeb's SiteLogoUrl string as an attribute (called imgurl) of each SPWeb's respective SiteMapNode, with a view to retrieve this value later in my custom Menu control (which inherits SharePoint:AspMenu).
I'm setting the attributes like so in my provider under GetChildNodes:
public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
{
PortalSiteMapNode portalNode = (PortalSiteMapNode)node;
if (portalNode != null)
{
if (portalNode.Type == NodeTypes.Area)
{
SiteMapNodeCollection nodeColl = base.GetChildNodes(portalNode);
using (SPSite currentSite = new SPSite(portalNode.PortalProvider.CurrentSite.Url))
{
foreach (SiteMapNode topLevelNode in nodeColl)
{
foreach(SiteMapNode currentNode in topLevelNode.ChildNodes)
{
string currentWebUrl = currentNode.Url.Substring(0, currentNode.Url.ToLower().IndexOf("/pages/"));
using (SPWeb currentWeb = currentSite.OpenWeb(currentWebUrl))
{
if (!string.IsNullOrEmpty(currentWeb.SiteLogoUrl))
{
currentNode["imgurl"] = currentWeb.SiteLogoUrl;
}
}
}
}
}
return nodeColl;
}
Now I can debug this and retrieve the value once it has been inserted like so (x being the index of node, and me doing this in the Command Window):
? topLevelNode.ChildNodes[x]["imgurl"]
and that returns the url of the image fine : "/_layouts/images/myimage.jpg"
Now.. when I try to do this in my custom Menu control I first get a handle on my custom nav provider like so:
CustomNavProvider customProvider = (CustomNavProvider)SiteMap.Providers["CustomNavProvider"];
SiteMapNode currentNode = customProvider.FindSiteMapNode(childitem.NavigateUrl);
and the currentNode variable now contains my SiteMapNode...great, however instead of the attribute I added previously, there is now only a single attribute called 'AlternateUrl' - the imgurl attribute I added in the provider has now disappeared. All other properties like title, desc etc are there so it's definitely returning the correct node.
What's my issue here? Is the attribute not being persisted when I added it to the node in the provider?
Any help or suggestions appreciated.
I'm new to WCF RIA Services, and have been working with LightSwitch for 4 or so months now.
I created a generic screen to be used for editing lookup tables all over my LightSwitch application, mostly to learn how to create a generic screen that can be used with different entity sets on a dynamic basis.
The screen is pretty simple:
Opened with arguments similar to this:
Application.ShowLookupTypesList("StatusTypes", "StatusTypeId"); which correspond to the entity set for the lookup table in the database.
Here's my WCF RIA service code:
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.ServiceModel.DomainServices.EntityFramework;
using System.ServiceModel.DomainServices.Server;
namespace WCF_RIA_Project
{
public class LookupType
{
[Key]
public int TypeId { get; set; }
public string Name { get; set; }
}
public static class EntityInfo
{
public static Type Type;
public static PropertyInfo Key;
public static PropertyInfo Set;
}
public class WCF_RIA_Service : LinqToEntitiesDomainService<WCSEntities>
{
public IQueryable<LookupType> GetLookupTypesByEntitySet(string EntitySetName, string KeyName)
{
EntityInfo.Set = ObjectContext.GetType().GetProperty(EntitySetName);
EntityInfo.Type = EntityInfo.Set.PropertyType.GetGenericArguments().First();
EntityInfo.Key = EntityInfo.Type.GetProperty(KeyName);
return GetTypes();
}
[Query(IsDefault = true)]
public IQueryable<LookupType> GetTypes()
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
var types = from e in set
select new LookupType
{
TypeId = (int)EntityInfo.Key.GetValue(e, null),
Name = (string)EntityInfo.Type.GetProperty("Name").GetValue(e, null)
};
return types.AsQueryable();
}
public void InsertLookupType(LookupType lookupType)
{
dynamic e = Activator.CreateInstance(EntityInfo.Type);
EntityInfo.Key.SetValue(e, lookupType.TypeId, null);
e.Name = lookupType.Name;
dynamic set = EntityInfo.Set.GetValue(ObjectContext, null);
set.AddObject(e);
}
public void UpdateLookupType(LookupType currentLookupType)
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
dynamic modified = set.FirstOrDefault(t => (int)EntityInfo.Key.GetValue(t, null) == currentLookupType.TypeId);
modified.Name = currentLookupType.Name;
}
public void DeleteLookupType(LookupType lookupType)
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
var e = set.FirstOrDefault(t => (int)EntityInfo.Key.GetValue(t, null) == lookupType.TypeId);
Debug.Assert(e.EntityState != EntityState.Detached, "Entity was in a detached state.");
ObjectContext.ObjectStateManager.ChangeObjectState(e, EntityState.Deleted);
}
}
}
When I add an item to the list from the running screen, save it, then edit it and resave, I receive data conflict "Another user has deleted this record."
I can workaround this by reloading the query after save, but it's awkward.
If I remove, save, then readd and save an item with the same name I get unable to save data, "The context is already tracking a different entity with the same resource Uri."
Both of these problems only affect my generic screen using WCF RIA Services. When I build a ListDetail screen for a specific database entity there are no problems. It seems I'm missing some logic, any ideas?
I've learned that this the wrong approach to using LightSwitch.
There are several behind-the-scenes things this generic screen won't fully emulate and may not be do-able without quite a bit of work. The errors I've received are just one example. LightSwitch's built-in conflict resolution will also fail.
LS's RAD design means just creating a bunch of similar screens is the way to go, with some shared methods. If the actual layout needs changed across many identical screens at once, you can always find & replace the .lsml files if you're careful and make backups first. Note that modifying these files directly isn't supported.
I got that error recently. In my case I create a unique ID in my WCF RIA service, but in my screen behind code I must explicitly set a unique ID when I create the object that will later be passed to the WCF RIA Service insert method (this value will then be overwritten with the unique counter ID in the table of the underlying database).
See the sample code for this project:
http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/157/A-Visual-Studio-LightSwitch-Picture-File-Manager.aspx
I saw 2 different way to create web parts for sharepoint. Which one is preferred by most?
http://msdn.microsoft.com/en-us/library/aa973249%28office.12%29.aspx
Anything involving VSeWSS is just going to end in pain, so method 1 is definitely out. Method 2 isn't ideal either, as setting up html elements as controls becomes unmanageable at a level just beyond what you see in that demo. I use a fairly simple generic base class that takes a user control as a type parameter and lets me keep all the layout nicely seperated from the sharepoint infrastructure. If you are creating pages/web parts programatically most of the web part xml turns out to be optional also.
public abstract class UserControlWebPart<T> : Microsoft.SharePoint.WebPartPages.WebPart where T:UserControl
{
protected UserControlWebPart()
{
this.ExportMode = WebPartExportMode.All;
}
protected virtual void TransferProperties(T ctrl)
{
var tc = typeof(T);
var tt = this.GetType();
foreach (var p in tt.GetProperties()) {
if (p.IsDefined(typeof(ControlPropertyAttribute), true)) {
foreach (var p2 in tc.GetProperties()) {
if (p2.Name == p.Name) {
p2.SetValue(ctrl, p.GetValue(this, null), null);
}
}
}
}
}
protected override void CreateChildControls()
{
string controlURL = ControlFolder+typeof(T).Name+".ascx";
var ctrl = Page.LoadControl(controlURL) as T;
TransferProperties(ctrl);
this.Controls.Add(ctrl);
}
protected virtual string ControlFolder
{
get {
return "~/_layouts/UserControlWebParts/";
}
}
}
For the few web parts I've written, I guess I've gone more with method #2 than method #1. Seems more straightforward and has the potential to be reused outside of the SharePoint environment (depending on the depth of your business logic).
I working with Reporting services in Sharepoint Mode, I am able to show the report in Sql Server Reporting services report viewer , the report has multiple parameters , My question is how do I pass more than one parameter from a custom web part to this report.
I am able to pass one parameter by implementing the ITransformableFilterValues interface in the custom webpart , what I want to do is pass more than one parameter .
Ex: If there are 2 parameters on report then i should able to map each from the control in webpart.
Here is the Code for Custom Webpart:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using aspnetwebparts = System.Web.UI.WebControls.WebParts;
//using Microsoft.Office.Server.Utilities;
using wsswebparts = Microsoft.SharePoint.WebPartPages;
//using Microsoft.SharePoint.Portal.WebControls;
using System.Collections.ObjectModel;
using Microsoft.SharePoint.Utilities;
using System.Data;
using System.Collections;
namespace CustomWebPart
{
/// <summary>
/// Used to provide filter values for the status report.
/// </summary>
public class StatusReportFiler : aspnetwebparts.WebPart, wsswebparts.ITransformableFilterValues
{
DropDownList ddlCategory;
ListItem lstItem;
Label lblCaption;
public virtual bool AllowMultipleValues
{
get
{
return false;
}
}
public virtual bool AllowAllValue
{
get
{
return true;
}
}
public virtual bool AllowEmptyValue
{
get
{
return false;
}
}
public virtual string ParameterName
{
get
{
return "Category";
}
}
public virtual ReadOnlyCollection<string> ParameterValues
{
get
{
string[] values = this.GetCurrentlySelectedCategory();
return values == null ?
null :
new ReadOnlyCollection<string>(values);
}
}
protected override void CreateChildControls()
{
lblCaption = new Label();
lblCaption.Text = " Category: ";
Controls.Add(lblCaption);
ddlCategory = new DropDownList();
ddlCategory.AutoPostBack = true;
lstItem = new ListItem();
lstItem.Text = "Select All Category";
lstItem.Value = "0";
ddlCategory.Items.Add(lstItem);
lstItem = null;
lstItem = new ListItem();
lstItem.Text = "BING";
lstItem.Value = "Bing";
ddlCategory.Items.Add(lstItem);
lstItem = null;
lstItem = new ListItem();
lstItem.Text = "Google";
lstItem.Value = "Google";
ddlCategory.Items.Add(lstItem);
lstItem = null;
Controls.Add(ddlCategory);
// base.CreateChildControls();
}
[aspnetwebparts.ConnectionProvider("Category Filter", "ITransformableFilterValues", AllowsMultipleConnections = true)]
public wsswebparts.ITransformableFilterValues SetConnectionInterface()
{
return this;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
public string[] GetCurrentlySelectedCategory()
{
string[] selCategory = new string[1];
selCategory[0] = ddlCategory.SelectedValue;
return selCategory;
}
protected override void RenderContents(HtmlTextWriter htmlWriter)
{
/*htmlWriter.Write("<table border=\"0\" width=\"100%\">");
htmlWriter.Write("<tr><td>");
lblCaption.RenderControl(htmlWriter);
htmlWriter.Write("</td></tr>");
htmlWriter.Write("<tr><td>");
lblCaption.RenderControl(htmlWriter);
htmlWriter.Write("</td></tr>");
htmlWriter.Write("</table>");*/
this.EnsureChildControls();
RenderChildren(htmlWriter);
}
}
}
Once you build this Webpart deploy it to SharePoint.
Create a Webpart page in Sharpoint , Add the Custom Web Part to the page .
Once you add it you will be able to see the dropdownlist with values on the Webpart .
In another Add Webpart Section add a Sql Server Reporting Sevices ReportViewer web part and set the report URL in the properties section and click apply , this report should have the same parameter name as in Custom Webpart.
In the Custom Webpart click on Edit -> Connections-> Send Category Filter To -> ReportViewer - AAAA(This is the ReportName I Guess). This will popup a Window with the mapping section , Map the Filer Category to Filtered parameter on the Report and click Finish . This will pass the value from the Webpart to the Report.
Hope this helps.
I'm not sure about SharePoint integrated mode, but ReportServer correctly accept parameters passed via URL string.