I have a UserControl A which contains
a dropdown
a placeholder
At runtime the placeholder will be populated with some UserControl B.
There are certain times which I need to trap the javascript onchange event in the dropdown to call a javacript function in B (to do a clientside update of B). What is a good design/practice for how to do this?
The naive way is to make the asp:dropdownlist a public member then send it into a public method of B:
// In controlling code
...
userControlB.Initialize(userControlA.TheDropDownList);
...
// In usercontrol B
public void Initialize(DropDownList dropdownFromA)
{
dropdownFromA.Attributes.Add("onchange", "myBfunction()");
}
But something smells bad with this approach. I would like to keep A and B as loosely coupled as possible. Any better ideas?
To keep them really decoupled you could introduce an observer pattern such as described here. I would have the drop down register it's own change event which would notify the observer that a change occured. Then if any other controls on the client are interested they will enlist to be notified when a specific observation is made.
Edit
Well you could name the variables based on a logical name for the observation, then you'd just check to make sure the observer exists before registering with it.
A different system I have seen used, would be more like an event dispatcher, essentially when you subscribe you provide a name for the event, then you fire you would include two arguments the name of the event, and the data for the event. In this model you would only have a single dispatcher on a page.
Within the DOM I'm fairly sure that all controls are accessible so making the dropdown public isn't really relevant, particularly as control a and control b are members of the same page and so are accessible to each other in your code-behind.
I'd think a more loosely coupled approach would be to have control B register it's method as an event listener on control a's change event. I'm not sure how much more loosely coupled you could make two controls where one depends on changes from another.
Related
I am currently using databinding on a group of spark form elements and want to know if the data class I am bound to is dirty.
It might be nice if spark "Form" elements would trigger a change event that bubbled up to and was caught by a spark "Form". Then I could just add an event listener to the form. But I don't see something like that.
So besides comparing a copy of the original data class to the bound data class OR adding a change event to each form element and capturing that event, what's an easy way of knowing a class object has changed?
Make an extension of TextInput or whatever other input elements you'll have in your form then in your components creation complete register a listener for the change event, in the handler dispatch a bubbling event, then in your document that contains the form use your custom elements and add a listener via AS3 to the Form for the event you dispatched and stop it's propagation at that point.
Otherwise I think your second solution of change handler on each wouldn't be too bad. If you have a lot of elements in the form or it's dynamic you could write a function that steps through the form items of the form and checks their children then you just have to write a switch that deals with each class type (you can use http://www.actionscriptdeveloper.co.uk/getting-the-class-of-an-object-in-as3/on each of the child elements to determine what type it is, then if it's a TextInput or whatever register the appropriate event to call your commonly used function, just be sure it has a generic Event as it's parameter since all other Events will be sub-classes thereof).
So far as I know there is no easy way to listen for changes to the underlying data from the ArrayCollection or other wrapper ListCollectionView data structures. Part of the problem is the elements added a collection aren't required to implement any sort of interface to allow for listeners to be registered. That is, the data elements aren't necessarily event dispatchers, the only other way for this information to be communicated to the List would be if the data elements had a handle on all lists that contain that element and they mark something on the lists to indicate "dirty" when any property is set. These are all achievable within the constraints of the language but aren't provided out of the box as the usage for them is probably limited and could potentially unnecessarily bloat the cpu usage of the ListCollectionView in other cases.
In doing a lot of ASP.NET pages (.NET 2.0), my codebehind is typically packed with event handlers on page objects. GridView_RowCommand, Button_Click, etc. All the usual suspects. One thing all EventHandler derived things have in common is that their first argument is an object, typically labeled "sender".
In ASP.NET codebehind, I really don't see the point of it. If I have GridCustomers_RowCommand and I need to do something to GridCustomers, I can just access it from the codebehind instead of worrying about casting sender to a gridview and then working with it.
I feel like I must be missing a very important design consideration here. Am I doing something stinky to my code? I kind of can see that using direct references this way is falling prey to global objects, but that's just how ASP.NET works! What am I not seeing here? Is there some superb book or tutorial that shows how to use ASP.NET the "right way?" The clean, agile, "real coder" way?
You might have one event handler for DRY coding, yet 20 things that use that event, for example:
protected void AddClass(object sender, EventArgs e) {
((WebControl)sender).CssClass += " myNewClass";
}
In this case you're writing the code once, but it can be used by many WebControls (this is an example, the point is not specific to WebControls at all).
Disclaimer: Do I use sender every day? no not even close, is it useful? yes it can be :)
Yes, if you have an event handler that is called by several controls, and you need to know which control called the event handler.
It seems like a backwards way to do it, but it can eliminate repeated code if the event handler is the same for most controls, except for a few minor differences.
That said, I've only ever seen it in real code once, and I thought it hurt readability. I like to have one event handler per control/event.
The sender parameter of an event signature is provided so that you can establish the context of the event.
This is useful say, if you have several nested controls in an ASP.NET control hierarchy, whereby they are all executing the same event handler for a specific event, the sender parameter allows you to distinguish between the different controls.
Sometimes, the subclassed EventArgs parameter provides the context for you in a neater fashion, sender is still useful for abstract event handlers.
I'm sure this has been answered but I cannot find it...perhaps because the terms are used for so many differing questions...anyway.
how is the best way to store information about building dynamic controls on postback etc in the init event...
e.g. take the classic "questionnaire" scenario...question.aspx?id=1
get the id from the querystring
load the questions from where ever and build controls dynamically,
or dynamically add each question
user control for how ever many
questions etc
page posts back (user presses submit I guess!)
in the init event...where should you have already stored the id to
recreate the question controls?
things i've tried/thought of so far:
I initially stored the value in
viewstate as it's relevant to the
page, but the viewstate value isn't
available in page init (or is it??)
a hidden variable and then accessing the value in the form collection
session - I don't want to use session, seems problematic, e.g. what if the user views two questionnaires?
the querystring again? but that seems too easy to be changed and the dynamic controls just wouldn't make sense...
in the controls themselves? e.g. disregard the questionnaire id all together and simply go off the question id perhaps?? assuming that control ids would be in some format like controlQ# e.g. controlQ1, controlQ2. this means that each question id must be unique
any help/guidance etc much appreciated!!!
The query string will still be available during a postback, so you could take the ID from there.
If you are concerned about users manipulating the query string parameters, then maybe you should add some additional parameters which will allow you to verify the correctness of the query string during a postback. As a simple example, you could add a MD5 hash of the ID parameter and the current session's ID, and recreate/compare that checksum during a postback.
try to create/load the usercontrols in the page OnLoad.
if you want to load inside a usercontrol other usercontrols use the OnInit event.
use the viewstate for the persistence of the information you will need to re-instantiate the usercontrols during the postback. to map the postback data to the controltree you have to ensure that you have the same control-tree. after that mapping you could change your controltree (load some other controls e.g.)
contols-id: yes, you have to give them unique ids, otherwise it will not work. if you store your questions in a database, there should not be any problem to use the database-primary key for this, so name them e.g. question-1, question-2 (the controls)
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.
alt text http://img3.imageshack.us/img3/1488/advancedsearch.png
I'm building an "Advanced Search" interface in an ASP.NET application. I don't need SO to write this thing for me, but I'm stuck on a specific problem regarding dynamic controls and ViewState. I would like some direction for how to approach this. Here's my situation:
Ingredients:
A serviceable set of API objects representing entities, fields, and searches, which handles constructing a search, generating SQL, and returning the results. So that's all taken care of.
ASP.NET 3.5
Desired Interface Functionality:
(1) On initial page load, the interface gets a preconfigured Search object with a set of SearchCriterion objects. It binds them into a set of controls (see image above.)
Some search items are simpler, like:
Field (DropDownList) | Operator (DropDownList) | Value (TextBox)
Search Criterion controls for some field types have important information stored in viewstate, like:
Field (DropDownList) | Operator (DropDownList) | Value (DropDownList) where the "Value" dropdownlist is populated by a database query.
Some fields are lookups to other Entities, which causes a chain of field selectors, like:
Field (DropDownList) Field (DropDownList) | Operator (DropDownList) | Value
(2) The user modifies the search by:
Adding and Removing search criteria by clicking respective buttons
Configuring existing criteria by changing the Field, Operator, or Value. Changes to Field or Operator will require the control to reconfigure itself by changing the available operators, changing the "Value" input control to a different type, or adding/removing DropDownLists from the "Fields" section if Lookup-type fields are selected/unselected.
(3) Finally, the user hits "Search" to see their results.
The Problem:
As you probably already know if you're answering this question, controls added dynamically to the page disappear on postback. I've created a UserControl that manipulates the control collection and neatly accomplishes step (1) above as you can see in the attached image. (I'm not concerned about style at this point, obviously.)
However on Postback, the controls are all gone, and my Search API object is gone. If I could get the dynamically generated control collection to just play nice and stick in ViewState, I could examine the controls on postback, rebuild the Search object, then handle control events neatly.
Possible Solutions
I could make the Search object serializable and store it in viewstate. Then on page load I could grab it and reconstruct the control collection at page load time. However I'm not sure if this would play nicely with controls raising events, and what happens to the viewstate of Drop-down lists that contain data from the database - could I get it back? It's highly undesirable for me to have to re-query the database on every postback.
I could develop a custom server control (see this link) for this kind of thing... but that is a new topic for me and would involve some learning, plus I'm not totally sure if a custom server control would work any more nicely with non-fixed control collections. Anybody know about that?
I was thinking that I might be able to accomplish this using databound controls - for example I could bind my criterion collection to a repeater which has a fixed control collection (maybe hide the non-used "value" controls, use an inner repeater for the "Field" drop-down lists). Then all the information would stay in ViewState... right?
Any new ideas would be greatly appreciated.
thanks for your help.
b.Fandango
I've been coding for about a day and I got this working beautifully using the third option I suggested in my question - old-school databound controls. Actually I only thought of the idea when I was forced to write out the question in detail - doesn't that just happen to you all the time?
I put my SearchCriterionControl into an asp:Repeater and bound it to my object collection. For the Field Chooser I put an asp:DropDownList inside a nested asp:Repeater and bound the Field array to that. Everything works beautifully, keeps state, actually required very little code. So I never had to dynamically add controls to the page, thank goodness.
Thanks for your suggestions, Ender, Matt and andrewWinn.
Since no one else has taken a stab at this for 2 hours, I'll throw my hat in the ring with a solution that does not rely on viewstate at all (or the ASP.NET model of postbacks).
What if you grabbed all the input values with jQuery and instead of doing a post-back did a post against the page (or a new results.aspx page)? Or, you could make the entire thing asyncrhonous and do an Ajax request against a web method, get fed the results, and populate on the client side as needed?
The unfortunate thing here is you have to reconstruct which type of controls were used to figure construct your search query since that data wont be passed with the viewstate. But I imagine you were already going to have to do some kind of translation of your input data into a query form anyway.
Read here for more information about using jQuery to hit an ASP.NET page method. Remember - page methods must be static (it's an easy oversight).
I'm not sure what you're doing server side to construct your query - but I would highly recommend LINQ. I did a similar "advanced search" function previously, and after a few different attempts found that LINQ was a wonderful tool for this problem, regardless of whether I was hitting SQL with LINQtoSQL or just hitting an in-memory collection of objects.
This worked so well because 1) LINQ is deferred execution and 2) A LINQ query returns another queryable object. The implication here is that you can chain your LINQ queries together as you construct them from your input, instead of having to do a single massive clause translation to SQL or whatever backstore you are using (one of my attempts was constructing SQL clauses with strings, but still passing input data via SQLParameters for SQL injection protection - it was messy and complicated when hand crafted LINQ was orders of magnitude easier to understand and implement).
For example:
List<string> data; // or perhaps your a DB Context for LINQtoSQL?
var query = data.Where(item => item.contains("foo"));
if( {user supplies length search option} )
query = query.Where(item => item.Length < 5);
// etc, etc.
// LINQ doesn't do anything until the query is iterated, at which point
// it will construct the SQL statement without you worrying about details or parameter binding
foreach(string value in query)
; // do something with the results
Because of deferred execution and the queryable return type, you can concatenate LINQ queries to this expression all day long and let it worry about the implementation details (such as converting to a SQL query) at execution time.
I can't provide you with the exact steps that you will need to do, but I HIGHLY suggest looking into asp.net page life cycle. I created a user control as a DLL one time. I had to capture postback data at specific steps in the lifecycle and recreate and rebind the data at other steps. Additionally thinkgs like viewstate are only available at certain points also. I know that I had to override On_init, On_prerender and some other methods.
Sorry I couldn't be more help, but I don't have the code with me (its with an old employer). I hope this helps.
If you are adding controls to the controls tree dynamically, you need to add them on postpack as well. Just call the method that builds the control on Page_Load or Page_Init and the controls should stay on the page on postback.