I have a troubling task. I have a page contained within an outer frame, on the outer frame is a user control which i need to populate with some data from within the containing page. The problem is the outer frame is rendered before i can get hold of the required data. How can i accomplish this?
I would use a JavaScript to accomplish this. You can put a Populate method in JavaScript in the parent page with whatever signature you want.
function PopulateControls(param1, param2, param3)
{
document.getElementById('<%=this.Control1.ClientID%>');.value = param1;
document.getElementById('<%=this.Control2.ClientID%>');.value = param2;
document.getElementById('<%=this.Control3.ClientID%>');.value = param3;
}
Then in the child frame, make sure the page has a built in JavaScript to pass it back up.
window.onload=SendToParent;
function SendToParent()
{
window.opener.PopulateControls('<%=this.Field1.ClientID%>','<%=this.Field2.ClientID%>',<%=this.Field3.ClientID%>');
}
You might have to tweak the code above, but the gist is that the parent page controls will end up waiting until the child page is fully loaded before it's done. If you want to use labels and whatnot, you can use the InnerHTML property on divs instead.
Couldn't you just create a public Populate sub in your user control and call it from the outer frame when required data is loaded? Populate would take the data as arguments and populate the control with this data.
Related
Following these two threads:
How can I create an Array of Controls in C#.NET?
Cannot Access the Controls inside an UpdatePanel
I current have this:
ControlCollection[] currentControlsInUpdatePanel = new ControlCollection[upForm.Controls.Count];
foreach (Control ctl in ((UpdatePanel)upForm).ContentTemplateContainer.Controls)
{
currentControlsInUpdatePanel.
}
currentControlsInUpdatePanel does not have an add or insert method. why does the first link i post allow that user to .add to his collection. This is what I want to do, find all the controls in my upForm update panel. but i dont see how i can add it to my collection of controls.
I don't think this code makes sense. You are creating an array of ControlCollection objects and trying to store Control objects in it. Furthermore, since currentControlsInUpdatePanel object is an array, there will not be an Add() method available on that object.
If you want to use the Add() method, try creating currentControlsInUpdatePanel as a List object.
Example:
List<Control> currentControlsInUpdatePanel = new List<Control>();
foreach(Control ctl in ((UpdatePanel)upForm).ContentTemplateContainer.Controls)
{
currentControlsInUpdatePanel.Add(ctl);
}
If you want to continue to use an array to store the Control objects, you will need to use the index value to set your objects in the array.
Example:
Control[] currentControlsInUpdatePanel = new Control[((UpdatePanel)upForm).ContentTemplateContainer.Controls.Count];
for(int i = 0; i < upForm.Controls.Count; i++)
{
currentControlsInUpdatePanel[i] = ((UpdatePanel)upForm).ContentTemplateContainer.Controls[i];
}
The UpdatePanel's child controls collection is a special collection that contains only one child control: its template container. It is then that control that contains all the child controls of the UpdatePanel (such as a GridView or Button).
As it is noted in the other questions linked to in the question, walking the child control tree recursively is the best way to go. Then, when you've found the spot to which you need to add controls, call Controls.Add() in that place.
My suggestion would be a different approach: place an <asp:PlaceHolder> control in the UpdatePanel and give it a name and add controls to that. There should be no particular advantage to accessing the controls collection of the UpdatePanel itself, and then you wouldn't have to dig through implementation details of the controls (which, while they are unlikely to change, can make code much more difficult to read).
Try to use
ControlCollection collection = ((UpdatePanel)upForm).ContentTemplateContainer.Controls;
This gives you all the controls in that control collection. From there you can use CopyTo to copy it to the array you need:
Control[] controls = new Control[collection.Length];
collection.CopyTo(controls , 0);
When you create a Data Extender for a CME list – for instance to add a column for the Schema as in this example – it all works fine and dandy whenever you do actions that force a List reload.
However, some actions don’t force a list reload (like editing a component in a folder, then saving & closing) and it looks like Anguilla is loading the data for the item that changed using a different mechanism that loads only the data for the item in question (which makes sense).
If I would want my extended list view to behave properly and also load my additional attributes whenever a given item changes (instead of only when the list view is reloaded) what else do I need to do?
I found how Anguilla takes care of this. When you implement a Data Extender, you are extending the information regarding the items displayed in the list, which basically means that you are extending the Data (Model) behind the item in question.
Each Item in Tridion has its own class in the Anguilla Framework, for example a Component has its own Tridion.ContentManager.Component javascript "class".
Having said this, and going back to the example that shows the schema name of the component, we are not actually extending the model, since that information is already available in the component. However, we need to overwrite the methods exposed on each used for displaying information in the lists the item is in, in this case a Component.
So, when we deal with a Data Extender, if we want a full implementation of this functionality, we not only need to define the data extender:
<ext:dataextender
name="IntelligentDataExtender"
type="Com.Tridion.PS.Extensions.IntelligentDataExtender,PS.GUI.Extensions">
<ext:description>Shows extra info</ext:description>
</ext:dataextender>
But also we need to define what's the column we are adding:
<ext:lists>
<ext:add>
<ext:extension name="IntelligentColumnExtender"
assignid="IntelligentDataColumnExtender">
<ext:listDefinition>
<ext:selectornamespaces/>
<ext:columns>
<column
xmlns="http://www.sdltridion.com/2009/GUI/extensions/List"
id="IntelligentData"
type="data"
title="Additional Info"
selector="#ExtendedInfo"
translate="String"/>
</ext:columns>
</ext:listDefinition>
<ext:apply>
<ext:view name="DashboardView" />
</ext:apply>
</ext:extension>
</ext:add>
</ext:lists>
Once we have this, the GUI will display the column we just added: "Additional Info"
Well, now we need to achieve the list refreshing when the item is edited/checked-out and in, etc...
For that, we need to extend the model and implement a few methods in the Object we are extending. In this example I am extending the Page object, so whenever a page is edited, the row in the list we want to update gets refreshed, together with the rest of the cells in the table.
To extend the model we need to define what types are we extending, in this example I am going to use the "Page" class as an example. First of all you need to define the model extension in the config file of your Editor:
<cfg:group name="Com.Tridion.PS.Extensions.UI.Model"
merger="Tridion.Web.UI.Core.Configuration.Resources.DomainModelProcessor"
merge="always">
<cfg:domainmodel name="Com.Tridion.PS.Extensions.UI.Model">
<cfg:fileset>
<cfg:file type="script">/Scripts/PSPage.js</cfg:file>
</cfg:fileset>
<cfg:services />
</cfg:domainmodel>
</cfg:group>
and
<ext:modelextensions>
<cfg:itemtypes>
<cfg:itemtype id="tcm:64" implementation="Com.Tridion.PS.Extensions.UI.PSPage" />
</cfg:itemtypes>
</ext:modelextensions>
As you can see I am extending the Page by using the "Com.Tridion.PS.Extensions.UI.PSPage" class that is defined in the Javascript file "/Scripts/PSPage.js".
The only method that handles the row refreshing is the following:
Com.Tridion.PS.Extensions.UI.PSPage.prototype.getListItemXmlAttributes
= function PSPage$getListItemXmlAttributes(customAttributes) {
var attribs = {};
var p = this.properties;
if (customAttributes) {
for (var attr in customAttributes) {
attribs[attr] = customAttributes[attr];
}
}
//This adds my custom column back when the item is updated
attribs["ExtendedInfo"] = p.extendedInfo;
return this.callBase(
"Tridion.ContentManager.Page",
"getListItemXmlAttributes",
[attribs])
};
As you can see I am implementing the "ExtendedInfo" attribute which is the one displayed in my additional column.
There's more than just adding a Data Extender when dealing with adding a column to our lists. I will write a post in my blog here to provide with a fully working example.
I hope it makes sense.
Well, Jaime correctly described how CME updates changed items in Lists. But I want to add some additional information on how List controls, domain model List and Items are interact with each other. This might help you building your own extension with similar functionality.
Most of the domain model List items inherit from Tridion.ContentManager.ListTcmItems class. On the moment when any List item, based on mentioned class, is loaded it will be registered in Lists Registry (and un-registered when the List is unloaded). This will allow Model to use registered Lists as source of static data for Items and to update changed Items data in these Lists.
Update Item static data
For example, we have loaded ListCategories and there is only one Category in the List:
var pub = $models.getItem("tcm:0-1-1");
var list = pub.getListCategories();
list.load();
// After list is loaded
list.getXml();
That getXml() returns an XML like (simplified):
<tcm:ListCategories>
<tcm:Item ID="tcm:1-4-512" Type="512" Title="Keys" />
</tcm:ListCategories>
After that, if you try to get some static data for Category "Keys" it will be already set:
var category = $models.getItem("tcm:1-4-512");
category.isLoaded(); // return false
category.isStaticLoaded(); // return false
category.getTitle(); // return undefined
category.getStaticTitle(); // return "Keys"!
That is possible because $models.getItem call will do two things: it will return an existing (or create a new) domain model object and call $models.updateItemData method with it. This method call will go through all registered Lists in the Lists Registry and for all Lists whose TimeStamp bigger than Item's Last Update TimeStamp will call list.updateItemData with the model object.
The updateItemData method will check if the passed Item is in the list and if it is, then the Item will be updated with the static data that is available from the List.
Updating data of changed Items in the List
When a domain model Item is modified (updated, removed, created new) one of these methods is called:
$models.itemUpdated
$models.itemRemoved
These methods will go through the Lists in Lists Registry and call list.itemUpdated (or list.itemRemoved). These methods will check is passed Item is contained in their List and if so they will update the List xml from the Item data.
For that purpose there is a getListItemXmlNode method in the Tridion.ContentManager.Item class. This method will build List xml node based on the array of attributes, provided by getListItemXmlAttributes method on the Item. That's what Jaime mentioned in his answer.
If the List xml was updated, one of these events will be fired on List object:
itemadd
itemupdate
itemremove
Listening to these events on a List object in your view will allow you to timely update your List Control.
So if you want this mechanism to work with your extension, stick to these rules:
If you are creating new domain model List object - it should inherit Tridion.ContentManager.ListTcmItems class or it should implement the getId(), itemUpdated(item), itemsUpdated(item), itemRemoved(item) and updateItemData(item) methods
If you want to see changes in List control - attach handlers to corresponding events on the domain model List object and update your List control
If you are creating new domain model Item - it should inherit Tridion.ContentManager.Item class and you should improve getListItemXmlAttributes method to return correct array of attributes for the List
The CME will indeed update the items in the list dynamically after the save occurs, without going to the server.
To do so, it calls a method named "getListItemXml" which returns the update XML element for the list. It will then update or add this element, which will update or add the item in the list view.
getListItemXml is a method of the different Model objects.
So how do you take advantage of this? I'm not sure.
Perhaps you could overwrite the method (or maybe getListItemXmlAttributes is best) with your own to add the additional data?
There is also an "itemupdate" event fired whenever an item is updated in the list.
You can hook into that by doing something like this:
var myEventHandler = function(event)
{
$log.message("Item updated. TridionEvent object passed: " + event);
}
var view = $display.getView();
var list = view.getListObject("uri-of-Folder");
list.addEventListener("itemupdate", myEventHandler);
I suppose you could use that to update the list entry for the item after the fact.
Be sure to call removeEventHandler at some point too.
None of this is optimal, obviously.
But I don't know of any extension point that would solve this particular problem.
I think I would (attempt to) implement this by monitoring the items in a folder periodically and updating that list after this polling mechanism had detected a change in that folder.
For example, I would write some javascript timeout or interval that runs in the background and checks the items in the current folder. If it detects a change, it triggers the update of the list.
Alternatively, you could also try to intercept the action that changed your list (e.g. the creation of a new item), maybe by means of an event system, and as such update your list. I don't think this is much different than the first approach, as I think it still implies some level of polling from the GUI side.
I have a list that I need to bind to a List I get from an API. The list looks like this:
struct DataItem { int level; string name; Guid key };
List<DataItem> myList = API.GetList();
ListView1.DataSource = myList;
ListView1.DataBind();
All this works fine for display. However, the table must edit the level value. I am unsure how to make that happen. I have tried event handlers on the listView, but they are never called. I have tried a text box for the level field (with both Bind and Eval) and an event handler OnTextChanged, but the event handler is never called. (I have tried with various combiniations of AutoPostBack and ViewState enabled.)
How can I programatically edit this data structure?
Two way data binding you are trying to implement here won't work like this - List doesn't implement INotifyPropertyChanged (someone correct me if I'm wrong).
You may consider using a plain old DataTable which can be two-way-bound out-of-the-box. If performance is not a highly critical issue, converting your List to a DataTable (and back, depending on what you want to do with the modified data) is simple enough, rather than struggling with custom implementations of list types.
I have a WebPart custom control (Composite) in .Net, which gets created on page load to show a Chart using 'Dundas Charting Controls' (this is created by a user control inside the page). I get the properties for this control from the database.
I have another control, which is a Filter (outside webpart) and based on data of this filter control which the user selects and which I would get on postback after click of button, I have to show the filtered chart results. The problem is CreateChildControls() gets called before the postback data is available (which would be available only after the Page_Load event fires).
I'm unable to get this data in time to pass on the parameters for filtering the Chart Results.
The implementation os like this ...
Webparts
Page > User Control > Webparts > Composite Control/Chart
Filter
Page > User Control > Composite Control [I get this data on Postback]
It sounds like you are running into an event ordering issue. I always try to make my controles relatively dump - so they don't really know how they are being used.
Consider creating a method in your chart control to force an update of its data:
public void UpdateChart(-- arguments as needed --)
then create an event in your composit control (that has your filters) like
public event Eventhandler FiltersChanged;
Assign this to an event hander on parent page:
filterControl.FiltersChanged += new EventHandler(Filter_OnChange)
Then create an event handler that tells your chart control about the change
Filter_OnChange(Object sender, EventArgs e)
{
// get whatever data you need from your filter control
// tell the chart about the new data and have it reload/redraw
myChart.UpdateData( - filter options here -}
}
In doing so, you let the page direct the order of operations and do not rely on the order in which the child controls Load methods are called.
James - Thanks for your answer, but this does not seem to work in my scenario or rather I couldn't make it work, when I tried it. The controls seems to be doing too much and is getting data from every where, it has its own constructor implementation, Load() override etc so a single UpdateChart() function may not have done the trick in this case.
This is what I did, finally.
I fire an Ajax request with Filter Data and set the value in a Session Variable before page does a Postback, this way I get the data at all places/events, and pass on the same as parameter where required. I know it may seem weird way to implement this, but it saved additional Database calls (which in this case are many to create the controls again) even though it comes at the cost of an additional Server HTTP ajax request.
Let me know this implementation can have any negative impact.
in asp.net suggest me a best way for storing and binding datatable to grid to avoid rebinding on each pageload
thanks
Grid values will be stored in ViewState automatically. You shouldn't need to be rebinding on every postback unless you're changing the data.
If you have a dataset that you're retrieving different "views" from each time, like custom paging or sorting, then you should evaluate using the Cache to store your dataset.
If viewstate is enable then you do not need to rebind.
If something is changing and you need to rebind you have a couple of options. Store your datasource in the viewstate or session. Then you don't need to hit the database each time you need to add a filter or paginate etc.
Eg.
// Load up date initially
public void LoadYourData()
{
// This gets your data however you are doing it, in this example I am returning
// a list of objects.
List<YourObjects> yourData = GetYourData();
// You then have the option to save it in the session or viewstate, I will
// demonstrate both. Make sure if you are using viewstate that your objects are
// serializable. You should probably also create helper classes to deal with
// getting and setting data from the ViewState object or Session.
Session["YourData"] = yourData;
ViewState["YourData"] = yourData;
}
// Then make a bind function that gets the data out of whatever store you have it in
public void BindYourGrid()
{
// I will assume you used session
grdYourGrid.DataSource = (List<YourObjects>)(Session["YourData"]);
grdYourGrid.DataBind();
}
So when you need to use it in your Page_Load you could just use the following:
GetYourData();
BindYourGrid();
If you need to apply something a filter you just need to pull the data out of your store (session or viewstate) and perform your manipulation and store it again. Then just call BindYourGrid();.
This way you are never actually hitting the DB unless you need to. There are other methods for caching your datasource's data as well and they all have pros and cons. Go with whatever works in your case though as I have only shown two methods.