I have built a multiplage application in Flex with different user roles. I use a View Stack with a Menu Bar to navigate between the different pages.
However - each time a page gets opened, I need to do some database calls, apply User Role settings, etc. in an init() function. This init function may reference some UI elements of this page.
I tried to load it with the "creationComplete" event, but this one gets only triggered once (since the page is not rebuilt each them the view stack shows it).
Now I have put it on the "show" event, but this seems not to get triggered consistently, or before the page is fully created the first time.
Whats the best practice for this case?
* I have to pass in data
* Call init functions (database calls)
* Manipulate components
Ideally I would need a way to call init each time the page is loaded and after all components are created.
Thanks for your help,
Martin
Just an idea but why not trying to do your remote call on ViewStack change effect. You could use an interface for this that your views implements.
Try updateComplete event.
Dispatched when an object has had its commitProperties(), measure(), and updateDisplayList() methods called (if needed).
This is the last opportunity to alter the component before it is displayed. All properties have been committed and the component has been measured and layed out.
I had the same kind of problem when I wrapped the s:Group with wrapper class to put inside TabNavigator. The result, show event is not being called. Finally I changed all my s:Group with mx:Canvas, directly put them into TabNavigator(The same thing goes for ViewStack too). After that creationComplete event was called once and subsequent page visits call show of mx:Canvas
Related
I need something like the viewRender event in order to persist the user's state. I'm building a UI where users will frequently jump in and out of the calendar, so preserving their view/range is essential for a pleasant experience. Does this exist in v5? The last mention I can find of it is from v3.
The only workaround I can think of right now is a direct click handler on every view control element, or a very heavy-handed MutationObserver. This is a React app so either one is going to be super awkward.
Thank you!
Edit 2021-02-11:
I looked at the available view render hooks but none of them address my problem. What I need is an event that will fire whenever the view state changes, including clicking between weeks/months/etc., so that I can persist the date range the user most recently viewed as well as the view they had selected.
viewDidMount is the closest to what I need, but it does not fire when the date range changes.
Edit 2021-05-26:
Another problem with using viewDidMount is that using it to enact side-effects is a bit overeager. The hook gets called whether or not the user has actually done anything, and the default view always gets passed as view inside the View Object. So there's no way to tell whether this mount event contains data I should persist or not.
Despite working with WebForms for years I still find myself getting confused about the event lifecycle from time to time. This isn't so much a problem that needs resolving, as hoping to get a better understanding of why things work the way they do.
Suppose you have a form:
Default.aspx:
<form>
<MyControls:UserControl1 runat="server">
</form>
UserControl1:ascx:
<MyControls:UserControl2 runat="server">
The OnInit events occur in this order:
UserControl2_OnInit
UserControl1_OnInit
Default_OnInit
Isn't this just bass-ackwards? Shouldn't the Init code be run in the order that controls are created? Shouldn't a parent control be able to initialize properties of a child before its OnInit runs? That is, while you can initialize properties of subcontrols in markup, there's no direct way to have a parent control be able to dynamically set properties of the child control that will be available to its OnInit event.
What I've ended up doing is stuff like this:
override void UserControl2_OnInit()
{
NamingContainer.OnInit += new EvenHandler(UserControl1_ActualInit);
}
protected void UserControl2_ActualInit(..) {
// do actual init code here, which will occur before OnLoad but after it's parent
// OnInit
}
So it's not an insurmountable problem. I just don't understand why it's a problem in the first place.
I realize that perhaps you might want to be able to have all your child controls initialized in your OnInit code. So fine, you should be able to call base.OnInit first, instead of after, your own initialization code, which should cause all the child control OnInit events to get run. But the event lifecycle doesn't work that way. The Init events are not chained recursively, they seem to run independently the parent events, and the innermost one always gets run first. But seems life would be a lot easier if they were simply chained recursively so you could either call the base event (or not) before you do your thing in any given situation. Is there something I'm missing that makes this seemingly counterintuitive situation desirable or even necessary?
This document should be the main source of truth for your lifecycle questions.
Basically, OnInit fires after a control's internal initialization in finished. Since the page control is the first control initialized, and during it's internal initialization it initializes all sub-controls (perhaps in the order that the Designer.cs file gives), then it makes sense for the Page's OnInit event to be the last one called, since it's not finished initializing until all it's sub-controls are initialized and their OnInit events fired. Graphically, it looks like this:
Page internal init
Sub-control1 internal init
Sub-sub-control3 internal init
Sub-sub-control3 init finished / OnInit fired
Sub-control1 init finished / OnInit fired
Sub-control2 internal init
Sub-control2 init finished / OnInit fired
Page init finished / OnInit fired
So order of inits in this case is:
Sub-sub-control3 OnInit
Sub-control1 OnInit
Sub-control2 OnInit
Page OnInit
Load also work similarly. In general you should treat most of the events as though the control will go through it's internal process first (which includes calling the same event on all sub-controls), and then fire your custom event handling code afterwards.
Most examples you find use Page_Load specifically because that should be the last event called in that phase (and it's after post back data is loaded). It wouldn't work very well for Page_Load to be called first and risk having controls not in a fully loaded state for your custom event handling code.
The mindset for asp.net parent & child controls is:
Parents know all about their children, but children know nothing about their parent.
This mindset makes sense for re-usable server controls. Re-usability needs the custom child control making no assumptions about the page it gets used on.
The snippet you give makes me guess that your child user controls are not aimed at re-usable as such; but rather are specialized controls which you use to break down the complexities of a large & tricky UI?
In this case I would still try to work with the 'children known nothing about their parent' mindset. Think http://www.google.co.uk/search?q=gof+mediator+pattern where the parent page is the mediator between your children (the wikipedia page is good).
But your children still need to know something about the parent right, because they are doing complex UI interactions? You can address this with interfaces. Each child depends not on the parent, but on an interface that defines exactly what the children need access to. As http://en.wikipedia.org/wiki/SOLID puts it, 'depend on abstractions, not on concretions'. DO one interface per child control: 'many client specific interfaces are better than one general purpose interface'
But it all ends up looking over-engineered, doesn't it? It turns out that a componentised UI where the components must interact, just is complex, and the components may turn out big n clunky. This was, imho, one of the reason for MS web forms ajax controls losing out to jQuery &c. even before MVC came along.
Add to this that web forms ui is very hard to unit test; and your confidence in your software quality dives.
I recommend:
If you can, escape to a rewrite in MVC.
If you can't, consider abandoning server-side controls which do clientside behaviour, and use jQuery instead.
If you can't do that, simplify simplify simplify the UI. Even if that makes it less functional.
If you don't want that, then your choices are: pay the expense of engineering the UI well; or pay the expense of not engineering it well.
I have a ScriptControl (requires ScriptManager) with JavaScript to handle client-side interactions and ICallbackEventHandler to communicate back and forth. Everything works perfectly with one or multiple instances of the control on a page. I placed the control inside a GridView with sorting and it still works. However, I place the GridView in an UpdatePanel and now whenever I sort I get the following error for each instance:
Sys.InvalidOperationException: Two components with the same id 'GridView_ctl02_MyControl' can't be added to the application.
Can someone point me in the right direction on how to solve this? I am assuming ScriptManager is not disposing of the old Sys.UI.Control objects before trying to $create() the new ones with the same ID. I thought the UpdatePanel/ScriptManager combination would automatically take care of disposing objects that would be replaced?
Edit: This page appears to support what I thought: http://msdn.microsoft.com/en-us/library/system.web.ui.scriptmanager.registerdispose.aspx
Use the RegisterDispose method to
register dispose scripts for controls
that are inside an UpdatePanel
control. During asynchronous
postbacks, UpdatePanel controls can be
updated, deleted, or created. When a
panel is updated or deleted, any
dispose scripts that are registered
for controls that are inside the
UpdatePanel are called. In typical
page development scenarios, you do not
have to call the RegisterDispose
method.
Just to double check I placed an alert("dispose " + this.element.id) inside my JavaScript dispose() function. Every single instance alerts dispose GridView_ctl02_MyControl, but afterwards I get the error that two components can't have the same name GridView_ctl02_MyControl. I'm at a loss...
When the page unloads, my component's dispose() method is called and Sys.Application.removeComponent() is also called. When the UpdatePanel reloads, only dispose() method is called. For now I have solved this by putting Sys.Application.removeComponent(this); inside the dispose(). I didn't find a shortcut such as $remove (similar to $create), implying you aren't expected to need this often.
This seems logical in that you can keep a component loaded even after its related DOM elements (if any) have been replaced by the UpdatePanel. This way you have more control over the component's life. I can't imagine a use case, but I'm sure you could come up with one.
If I am way off and there is a better approach, please let me know!
I'm writing an asp.net web app. and i've hit a bit of a brick wall.
basically i have 2 pages, the main page with a text box in and a popup that contains a treeview.
My problem is this. when i select a treeview item i want the program to perform some database transactions using asp.net and then pass the value retrieved from the database into a javascript function that passes the data back from the popup page to the parent page. My problem is that i cannot find any way of calling a javascript function from asp.net. I've tried assigning attributes to controls on page load, but this does not work as when the page loads the data has not been retrieved from the database.
Have a look at the ClientScriptManager class. You can register scripts from code-behind that will run when the HTML page loads. Those scripts can call other javascript functions on the page.
There are many tutorials and examples on the Web. Here's one I found that may help but there are many more.
How to use the client script manager
You hit the nail on the head when you said "I've tried assigning attributes to controls on page load, but this does not work as when the page loads the data has not been retrieved from the database." You just need to discover when you're pulling the data from the database, and then assign the values after that. Without looking at your code, there's no way to know for sure, but Page_PreRender is probably a good bet to assign your values...it's probably after you're pulling information from the db...it's pretty much the last place that you can make things happen before the html is generated for the client.
You can invoke a function resided in the Main Page and call that function in the Main Page from the Child Page which is your pop up window.
Please refer to these links for references
http://chiragrdarji.wordpress.com/2007/03/10/call-parent-windows-javascript-function-from-child-window-or-passing-data-from-child-window-to-parent-window-in-javascript/
http://www.webmasterworld.com/forum91/2957.htm
http://hspinfo.wordpress.com/2008/01/12/call-parent-windows-javascript-function-from-child-window/
This one helps with retrieving popups from values using javascript
http://www.eggheadcafe.com/articles/20060117.asp
This one shows how to fire a postback using javascript, and manage it in the codebehind.
http://weblogs.asp.net/mnolton/archive/2003/06/04/8260.aspx
If you put them together, and use Control.ClientID to find the actual "html name" of your asp.net controls, you'll be able to set that up in no time.
Might not be the prettiest way to do it in town, and incidentally make little baby Jesus cry, but anyway, it works.
[edit]Oh. I just saw that it seems I answered the question the other way around, or "how to trigger codebehind from Javascript". I think the method I suggest may help you, if you use it right.
The javascript of the popup should pass the information to the parent window, and the parent window function should call a postback when it receives the information.
The javascript of the popup window should be only registered on a postback with the correct information retrieved, so that when the postback occurs on the popup because of the selection of the right information, the window closes and passes the information to the parent page.
The parent page, triggering postback, does the thingies you need it to, and the app resumes "normally" from there on, doing whatever you need it to, outside of the popup page.
I am creating a series of client side component controls on the fly that are nested inside a update panel. The first time I create the controls, everything works as desired, however, when i trigger an update on the update panel and it does a partial postback the controls come back with several javascript errors describing how the control is already registered on the page.
I get a series of errors that say something about like:
"Error: Sys.InvalidOperationException: Two components with the same id "master_ctl40_CCB_PALETTES" can't be added to the application"
Any ideas anyone?
Try these tricks:
On Page_Load put uxFailedControl.ID = DateTime.Now.ToString(); It will ensure your control has unique ID every time page reloads (fully or partially), so theoretically you shouldn't see anymore "same id" errors.
If you display your control in Modal Popup: Every time you hide the popup from the server, remove the control from it's container (Panel, Page, Control, etc.) Use uxModalPopupPanel.Controls.Clear(); or uxModalPopupPanel.Remove(uxFailedControl);
When you are done with debugging set ScriptMode property of your ScriptManager to "Release". It will prevent internal AJAX exceptions to be bubbled up to the browser.
In which event are you adding the components to the update panel? I.e. have you placed them inside the page load event without a postback check or have you placed them inside the update panel load event? etc...
Looks like your client object is being created more than once.
If you want your client side controls to be replaced when the update panel they're in refreshes, they should inherit from Sys.UI.Control, which takes takes an element in its constructor. When that element is replaced by the update panel, the client object will be disposed and then re-created. If you're currently using a ScriptComponentDescriptor on the server side to define the client control instance, you'll want to switch to a ScriptControlDescriptor.
By the sounds of it, your client objects just inherit from Sys.Component, which will hang around until they're manually disposed, which is why you're getting an error about having more than one component with the same ID.
I would advise against using a new ID every post back - this will just keep creating new client objects without ever cleaning up the old ones.