Using this code I managed to change the renderings on the current item. However this changed it permenantly in Sitecore (the changes were could be seen in the CMS) and not temporarily, as I expected.
void ReplaceLayout(Item item)
{
if (item == null)
return;
using (new SecurityDisabler())
{
// New item
LayoutField newLayoutField = new LayoutField(item.Fields[Sitecore.FieldIDs.LayoutField]);
LayoutDefinition newLayoutDefinition = LayoutDefinition.Parse(newLayoutField.Value);
DeviceDefinition newDeviceDefinition = newLayoutDefinition.GetDevice(Sitecore.Context.Device.ID.ToString());
// Current item
LayoutField layoutField = new LayoutField(Sitecore.Context.Item.Fields[Sitecore.FieldIDs.LayoutField]);
LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(Sitecore.Context.Device.ID.ToString());
deviceDefinition.Layout = newDeviceDefinition.Layout;
deviceDefinition.Renderings = newDeviceDefinition.Renderings;
Sitecore.Context.Item.Editing.BeginEdit();
layoutField.Value = layoutDefinition.ToXml();
Sitecore.Context.Item.Editing.EndEdit();
}
}
I don't want to make permenant changes to the item, I just want to replace the currently displayed items renderings on the fly if some conditions are met. Does anyone know how to alter an item's layout in this way?
You explained in your comments that you want to display certain sublayouts in the sidebar depending on certain form parts/steps.
You can do that by adding a PlaceHolder that will fit the sublayouts (e.g. in your sidebar) and use this code to dynamically render sublayouts to it.
First you need an item (i call it a snippet item) that has the sublayout configured on its presentation settings.
Then you can use code to render that item inside the placeholder (phSideBarPlaceHolder).
// Load snippet item
Item snippet = Sitecore.Context.Database.GetItem("{id-or-path-of-snippet-item}");
// Get the first rendering from item's presentation definition
RenderingReference rendering = snippet.Visualization.GetRenderings(Sitecore.Context.Device, false).FirstOrDefault();
// We assume that its a Sublayout, but you can also check for xslt and create an XslFile() object
Sublayout sublayout = new Sublayout();
sublayout.DataSource = snippet.Paths.FullPath; // creates a reference to the snippet item, so you can pull data from that later on
sublayout.Path = rendering.RenderingItem.InnerItem["Path"];
sublayout.Cacheable = rendering.RenderingItem.Caching.Cacheable;
// Copy cache settings
if (rendering.RenderingItem.Caching.Cacheable)
{
sublayout.VaryByData = rendering.RenderingItem.Caching.VaryByData;
sublayout.VaryByDevice = rendering.RenderingItem.Caching.VaryByDevice;
sublayout.VaryByLogin = rendering.RenderingItem.Caching.VaryByLogin;
sublayout.VaryByParm = rendering.RenderingItem.Caching.VaryByParm;
sublayout.VaryByQueryString = rendering.RenderingItem.Caching.VaryByQueryString;
sublayout.VaryByUser = rendering.RenderingItem.Caching.VaryByUser;
}
// Now render the sublayout to the placeholder
phSideBarPlaceHolder.Controls.Add(sublayout);
If you need more info about how to read data the DataSource property inside the sublayout code, Mark Ursino has written an article about that: http://firebreaksice.com/using-the-datasource-field-with-sitecore-sublayouts
Sitecore constructs the rendering controls and inserts them into the page very early in the ASP.NET Webforms lifecycle. It's not likely you can easily do this on the layout itself. However, if you can evaluate your conditions outside the page (e.g. by examining the item itself), you can probably do this in the insertRenderings pipeline.
Use ILSpy or another decompiler to check out Sitecore.Pipelines.InsertRenderings and Sitecore.Pipelines.InsertRenderings.Processors. As with other pipelines, the order of execution of these processors is defined in Web.config. You will want to add a new processor after AddRenderings which evaluates your conditions (likely examining args.ContextItem) and then modifies args.Renderings as needed.
In your previous question you were just looking to remove sublayouts. That should be fairly easy here. Adding different sublayouts is more difficult since you would need to construct a RenderingReference. This may require that you actually create the XML definition needed for its constructor. Or, if you have another item that defines the desired new layout, consider just changing args.ContextItem earlier in the pipeline.
Rather than change sitecore presentation, can you not place the 'form' control and sidebar within one parent control container? You would then have an an easy id for a sidebar container with which to conditionally populate controls programatically from the form control.
Alternatively, could you add all the possible controls to the sidebar and 'activate' (or perhaps just make visible) the required control(s), maybe via session state variables? (I dont know if this falls foul of some lifecycle or timing limitiation)
Or you can simply use conditional rendering rules to display the renderings/sublayouts you want based on whatever logic you can dream up.
Related
I have a few Input Controls (text) created in the code-behind in this manner as part of dynamic RadiobuttonList (so that a textbox is next to a radiobutton):
RadioButtonList radioOption = new RadioButtonList();
radiobuttonlist.Items.Add(new ListItem(dt.Rows[i][9].ToString() + " <input id=\"" + name + "\" runat=\"server\" type=\"text\" value=\"Enter text\" />")
All the controls are within UpdatePanel.
Everytime there is postback, the text within the Input control disappears.
How do I keep the input text values?
Any ideas? Much appreciated!
The control tree must be rebuilt on every postback, including partial postbacks - or let it rebuild through ControlState/ViewState. In this case, in subsequent postbacks, the Items collection is not rebuilt (or is cleared) and is empty during the Render phase.
For cases like this I would approach it as:
Enable ViewState on the RadioButtonList and ensure it is added no later than Load1, or;
Store the ViewState of an appropriate Collection on the container control and then DataBind the consuming control - see GenericDataSourceControl for a clean way to set this up. I prefer this approach as it's consistent, predictable, and easy to control.
1This ought to work but it might not. I am usually confused as to which controls really support ViewState and to what extent as the usage always strikes me as .. inconsistent. In any case, it won't work if ViewState is disabled - remember that disable ViewState for the page (or parent control) disables ViewState all the way down. Also, the control must be loaded into the Control Tree at the appropriate time and with the same Control Path/ID (usually Init or Load) so that it will correctly function with the request ViewState.
Rough idea for #2:
Save the view state in the containing user control (must have ViewState enabled for this control):
// ListItem is appropriately serializable and works well for
// automatic binding to various list controls.
List<ListItem> Names {
// May return null
get { return (List<ListItem>)ViewState["names"]; }
set { ViewState["names"] = value; }
}
In the GenericDataSourceControl (put the GDS into the Markup so it has a nice ID) Select Event:
void SelectEvent(sender e, GenericSelectArgs args) {
args.SetData(Names);
}
Add the RadioButtonList dynamically (say, in Control.OnLoad):
// Unless this NEEDS to be dynamic, move it into the declarative markup.
// The dynamic control must be added to the *same location* it was before the
// postback or there will be ugly invalid control tree creation exceptions.
var radioList = new RadioButtonList();
someControl.Controls.Add(radioList);
// To minimize problem with Control Tree creation this should be unique and
// consistent for dynamic controls.
radioList.ID = "radioList";
// Binding to the DS "declarative" usually works better I've found
radioList.DataSourceID = "idOfTheGDS";
// You -may- need to DataBind, depending upon a few factors - I will usually call
// DataBind in PreRender, but generally in the Load is OK/preferred.
// If it already binds, don't call it manually.
radioList.DataBind();
If DataBinding is working correctly then it should be possible to disable ViewState for the RadioButtonList .. but sometimes ViewState is used when ControlState should have been, so make sure it functions as desired.
The following code will not add a control to an aspx placeholder:
var control = new ASCXControl { ID="searchFilters", Filters = filters };
var placeholder = Utility.FindControlRecursive(Page, "rightColumnSearchFilters") as PlaceHolder;
if(placeholder != null){
placeholder.Controls.Add(control);
placeholder.Visible = true;
}
When debugging, the placeholder is found and the control shows as added to the placeholder controls collection after entering the block, but yet I see nothing render into the placeholder on the page.
I currently need to pass variables to the control in order to find the filters I need to display. Although I don't like passing variables between controls, I don't see any other way.
What am I missing would make the control not render? Is there a better way to do this?
EDIT:
I am trying to get the HTML inside of the ascx to render. I am able to get the Filters parameters inside the Page_Load on the control.
This may or may not be the whole problem, but usually trying to instantiate a UserControl the way you're doing it leads to problems. You should do it using the LoadControl(path) method of the Page class:
ASCXControl ctl = (ASCXControl) LoadControl("path");
I'm not 100% sure, but I think that if you just instantiate it like an ordinary class/control, you wind up not running all the event handlers (such as Load) that you usually would.
Another noob question from me... Apologies!
My initial code would be as follows (this is simplified):
Image pic = new Image();
pic.ImageUrl = "~/Images/photo.jpg";
pic.BorderColor = "Black";
How can I assign the 'pic' Image object to an Image Control already on my ASP.NET page?
The following doesn't work but illustrates what I'm trying to do:
MyImageControl = pic;
I'm sure there must be an easier solution than:
MyImageControl.ImageUrl = pic.ImageUrl;
MyImageControl.BorderColor = pic.BorderColor;
If you want to dynamically put controls on the page you need to do just that. Have a container then add them to the container. If you have some sort of list or array that you are storing the controls in, you just need to iterate through the collection, setting any properties you need and call container.controls.add(control); You will have to do this every post-back as their state will not be kept.
Using an asp:Panel as your container where you want the controls to show up is the easiest way to style and position the controls.
SOLUTION (moved from original post) :
I have come up with something which works for me but would still be interested if there is a way to do what I've asked above - My solution is as follows.... Rather than having a blank Image Control in my .aspx page, I changed it for a PlaceHolder instead. Then, in the C# code, I can use the following to include my Image on the page:
MyPlaceHolder.Controls.Add(pic);
I want to add check box inside comboBox in C#. My purpose is that the user can select multiple values from one ComboBox ( Check all and Uncheck all ).
Please Help
You have to extend the ComboBox control by providing your own rendering strategy, and "manually" adding a CheckBox.
Theses open source project are ready to use :
http://www.codeproject.com/KB/combobox/CheckComboBox.aspx
http://www.codeproject.com/KB/combobox/extending_combobox.aspx
It is a wrong usage of a ComboBox control, because the user has no possibility to see his choices. For multiple selection, I recommend you to consider this CheckedListBox control:
link to MSDN
There is an ASP.NET open source control at http://dropdowncheckboxes.codeplex.com/ that I've used and been very happy with. There is also a WinForms open source control at http://www.codeproject.com/KB/combobox/extending_combobox.aspx that doesn't look quite as strong but maybe somebody could combine the best of both. If well implemented this is really a great addition to your toolkit. The above 2 implementations show all of the items selected and give you a number of related checkboxes in a reduced area and with excellent grouping. My addition to the ASP.NET version was to allow a list of checked files to use just file names instead of full paths if this gets too long. See above link for full code. Below is just my addition which is called instead of UpdateSelection in your postback handler:
// Update the caption assuming that the items are files
// If the caption is too long, eliminate paths from file names
public void UpdateSelectionFiles(int maxChars) {
StringBuilder full = new StringBuilder();
StringBuilder shorter = new StringBuilder();
foreach (ListItem item in Items) {
if (item.Selected) {
full.AppendFormat("{0}; ", item.Text);
shorter.AppendFormat("{0}; ", new FileInfo(item.Text).Name);
}
}
if (full.Length == 0) Texts.SelectBoxCaption = "Select...";
else if (full.Length <= maxChars) Texts.SelectBoxCaption = full.ToString();
else Texts.SelectBoxCaption = shorter.ToString();
}
I am dealing with a master page split into various content placeholders as per usual. On a number of pages I use a multiview to show and hide the content based on different view modes (view / edit / add). This means one multiview per master page content placeholder...
The trouble comes when I need to switch view modes, eg from 'View' to 'Edit'. I need to change every multiview on the page to show the corresponding information. So say 4 different content placeholders exist, with 3 different view modes - thats 12 lines of quite tedious code.
I'm wondering if there is a way to sync or link all multiviews on a page, so that when one changes - they all change accordingly? Like through some sort of master multiview?
I've thought about using the View's Activate Event, but this would still mean doing a lot of wiring up to begin with, which is what I'm trying to avoid.
C#eers!
The best way to do this would be to have an event on the master page which the individual multiviews add an event handler to.
That should not need too much code, and you could avoid duplicating that code by creating a base class inheriting from MultiView for all your instances.
Another more automatic option (but not as performant as RobW's one), would be to recursively look on the page's controls for any multiview. Something like:
ApplyToControlsRecursive(Page, c =>
{
var multi = c as System.Web.UI.WebControls.MultiView;
if (multi != null)
multi.ActiveViewIndex = 1;
});
void ApplyToControlsRecursive(System.Web.UI.Control control, Action<System.Web.UI.Control> action)
{
action(control);
foreach (System.Web.UI.Control child in control.Controls)
{
ApplyToControlsRecursive(child, action);
}
}