Telerik: radControls: Resizable Splitter

For my current  project, I am working on what is quite a complex user interface. I need to develop an interface that has splitter controls and can load one or more modules within each panel. Sort of like Microsoft Outlook three-panel interface. Sort of like the example below:

Splitter Screenshot

The screenshot shows how 3 "modules" have been loaded, one in each Pane. 

I am using Telerik's RadSplitter control, which is part of their RadControls for ASP.NET AJAX and provide a great suite of components you can just drop into your project for rapidly prototyping and developing applications for the web. 

The Splitter control itself is able to resize itself intelligently, so expanding the left column will automatically resize the content in the two right-hand panes. But there are two things it does not do:

  • It does not (by default) operate within a "100% browser" environment - you need to encourage it to do so
  • While resizing content within adjacent panes to a resized pane is pretty good, some intelligence may need to be applied when resizing richer controls such as Grids, where the data itself may be effected.

To solve the first problem, you just need to make sure your RadSplitter is set to use the full browser dimensions as such:

<telerik:RadSplitter ID="MainSplitter" runat="server" Height="100%" Width="100%" HeightOffset="35" Orientation="Horizontal" Skin="Outlook">

    <!-- definition -->

</telerik:RadSplitter>

According to the established W3C guidelines, 100% is only 100% of a physically defined height of the calculated height of all containers of the item. The Splitter works exactly the same, so unless you are able to pixel-perfect resize your Splitter, you need to apply some hackery to get your Spliter to behave. Telerik show how to achieve this so I won't repeat them, but if you are having problems, make sure that EVERY container is able to resolve to a pixel-defined height. I have found that across browsers, it isn't always consistent. Therefore, I have the following jQuery to try and force the issue:

function stretchSplitter() {
       var newWindowHeight = $(window).height() - 110;
        $("#formPage").css("height", newWindowHeight);
        $("#<%=radSplitter.ClientID %>").css("height", newWindowHeight); }

$(document).ready(function() {
      $(window).bind("resize", resizeWindow);
      function resizeWindow(e) {
          stretchSplitter(); }
      });
stretchSplitter();

This is using the standard jQuery onLoad trick to resize the splitter once the DOM is ready. I'm taking the 110 pixels off to adjust to the current offsets I need to take account of. This could be used to tweak designs across browsers.

The next problem is how to have the contents of each pane resize according to the pane's new size once resized. Mostly, this isn't an issue. Designing your content around a liquid layout should allow content to resize using standard CSS flowing. If displaying data, or particularly complex designs within one of the pane areas, however, it becomes necassary to finely control positioning. 

In the screenshot above, I have a radGrid, which requires resizing to allow its VirtualScrolling to operate correctly. I'm actually trying to create a similar effect illustrated at the older radGrid demo. So resizing the pane will cause the grid to require resizing, which itself needs to go back to the server to rebind its rows. What I needed to do, however, was separate the functionality so that the usercontrol loaded into the pane doesn't know of its position in the Splitter. It may be loaded once (or more) in any Pane/location in the Splitter. So I couldn't "bind" myself to any events exposed by the host Splitter.

Thanks to jQuery (and my understanding of it as opposed to pure JavaScript), this was possible, if a little hacky.

I decided to use an <A> tag to act as an event proxy. This <A> tag would recieve a standard "onclick" event, and repeat it to any subscribers. I created a single <A> tag:

<a id="eventProxy" class="eventProxy" href="#" title="Event Proxy" style="display: none"></a>

Then, I attached an event to the RadSplitters OnClientResized event:

 

function radSplitter_OnClientResized(sender, args)  { 
       var v=$('a#eventProxy').trigger( 
            type:'click',
            subType:'splitterPaneResize',
            resizedPanelID:sender.get_id(),
            oldWidth:args.get_oldWidth(),
            oldHeight:args.get_oldHeight(),
            newWidth:sender.get_width(),
            newHeight:sender.get_height()  } );

 

When the RadSplitter pane is resized, this event is called, which creates an 'event' object and Triggers the event with type on the element found by the jQuery 'a#eventProxy'. As type is 'click', it will essentially call any handlers attached to the onclick event of the <A> tag.

Any handlers attaching to the onclick event would be dynamically built, so I couldn't just add them to the onclick="" attribute of the <A> tag. Again, I used jQuery to achieve this. In every Module that was interested or concerned with having to resize itself according to the new size of the content, I added the following jQuery:

 

$(document).ready(function() {
      $('a#eventProxy').click(
            function(event) {
            switch (event.subType)
            {
                  case 'windowClosed':
                       var ajaxMgr=$find("<%=AjaxManager.ClientID %>");
                       if (ajaxMgr) {
                              ajaxMgr.ajaxRequest("RebindUsersGrid");
                       } 
                      break;
                  case 'splitterPaneResize':
                       if (event.resizedPanelID=='<%=ParentRadPane.ClientID %>') {
                              sizeUsersModule(event.newHeight); }
                       break;
            }
       });
});

 

Again, I'm using the jQuery onLoad technique to ensuring my DOM is ready. Note that I have a switch, which shows that I can re-use the same <A> tag for multiple types of event. I have two cases here, one for closing windows and one for performing the resizing of the Users Module - determined by splitterPaneResize. Note that I am checking to ensure the pane I am going to resize is the one I am interested in - otherwise, your panes will interact with each other. The sizeUsersModule is shown below:

 

function sizeUsersModule(newHeight) {
       var ajxMgr=<%=AjaxManager.ClientID %>;
       if (ajxMgr!=null && ajxMgr.ajaxRequest!=null)
              ajxMgr.ajaxRequest('resizegrid_Height='+newHeight);
       }

This simply takes the new height and calls the ajaxRequest client-side method of the RadAjaxManager. This causes a callback to the server, which runs the server-side C#:
Hook up my event in Page_Load():
AjaxManager.AjaxRequest += new RadAjaxControl.AjaxRequestDelegate(AjaxManager_AjaxRequest);
... and the event:
private void AjaxManager_AjaxRequest(object sender, AjaxRequestEventArgs e)
{
         if (e.Argument.StartsWith("resizegrid_Height="))
         {
                   string newHeightAsString=e.Argument.Substring("resizegrid_Height=".Length);
                    if (newHeightAsString.EndsWith("px")) newHeightAsString = newHeightAsString.Substring(0, newHeightAsString.Length - "px".Length);
                    int newHeight=int.Parse(newHeightAsString);
                    radGridUsers.Height = newHeight- 65;
          }
}
In this code, I am parsing out the value (removing the 'px' which browsers such as Firefox include) and again using a known offset to be able to accurately size the grid. The grid is rebound and posted back.

 

Note that you would need an AjaxManager on the page to be able to achieve the AJAX elements of this, which I define programmatically:

 

RadCodeBlock radCodeBlock = new RadCodeBlock();
radCodeBlock.ID = "radAjaxManagerCodeBlock";
RadAjaxManager _radAjaxManager = BuildRadAjaxManager();
radCodeBlock.Controls.Add(_radAjaxManager);
Form.Controls.Add(radCodeBlock);

It's in a RadCodeBlock because it modifies the DOM so avoids the Exceptions associated with this.
And in each Module that is interested in Resizing, I have to hook up some AJAX settings (AjaxManager below is the same as _radAjaxManager above):
AjaxManager.AjaxSettings.AddAjaxSetting(AjaxManager, radGridUsers);
AjaxManager.AjaxSettings.AddAjaxSetting(radGridUsers, radGridUsers);

 

This just sets up the grid (radGridUsers) to use AJAX with the AJAX Manager and itself (as it tells itself to rebind).

I'd be interested to know if I have gone way out on the wrong path on this one, but it seems to be hanging together very well across all the browsers. This is very much thanks to the cross-browser functionality of Telerik's controls and jQuery.

 

Comments

# re: Telerik: radControls: Resizable Splitter

Tuesday, August 10, 2010 9:24 AM by xxx

thanks

Leave a Comment

(required) 
(required) 
(optional)
(required) 
Please add 7 and 1 and type the answer here: