What I did with my RadControls

6 posts, 0 answers
  1. Dan
    Dan avatar
    739 posts
    Member since:
    Mar 2009

    Posted 23 Mar 2008 Link to this post

    It's Easter Sunday and it's snowing; so no gardening for me. The dopey UK SUnday trading laws also mean that all the shops are shut and, because the TV companies all have the closest to a captive audience that they're ever going to get, there's nothing on the TV.

    Being the slightly sad muppet that I am, I thought I'd make some notes about the project I've been working on and how RadControls featured in it.

    The project itself is one that's been running for a while and I don't intend to bore you with all of the details, but here's a quick summary. My company writes software for the property management market. The software has been in the market place since the 70s, so, as you can imagine, there is a lot of knowledge and expertise built in. The company has decided to make this knowledge available via the web and that's were I came in. The project has a number of modules, each one containing many forms for various functions and a LARGE number of reports. When looking at how to get all of this functionality on to a web-enabled platform, the company had the option of rewriting/modifying all of the forms/reports to work with a thin-client. This, ideal, solution was simply not practicable 'cos of the hugh cost in time and resources. Another option was to find a way of getting the output that the existing routines generated to be used to generate something that could be used in a browser.

    As we're primarily interested in the way RadControls were used in the project, I'll concentrate now on the forms that the application uses. A decision was taken to keep things as simple as possible wrt form layout; in days past - certainly in the 'old' days of DOS-based systems with 80x25 screens - a huge amount of effort was made to get as much out of the usable screen space as possible. In these, more modern, days, people are used to scrolling forms - especially on the internet - so it was decided that layout should happen automagically, with a each 'line' on the for representing an input and each input appear below its predecessor.

    We then had to thing about what type of inputs we needed and how to get them from the backend to the browser. Dealing with the latter item first, it'll come as no surprise to learn that we decided to describe the form and its inputs as XML, especially as this information was to be transmitted to the web application via a Web Service. The input types were, for the most part, the sort of thing that you would find in any application, numbers, strings and dates. Additionally we had choices based upon either database look-ups or programmer-supplied lists.

    Designing the input controls to be used involved an amount of work based on requirements related to or driven by the specific nature of our application's environment and are of little interest in of themselves but how the issues raised were solved may be of interest to one or 2 of you, so I'll detail them each in their own post following this one.

    Before I get on to that though, I just wanted to take a moment to thank all of the team at telerik towers how have yet to be fazed by some of my more stupid sounding questions. Thanks guys, this app would have taken much longer to get to this point than it did if it wasn't for your help, support and insight.
  2. Dan
    Dan avatar
    739 posts
    Member since:
    Mar 2009

    Posted 25 Mar 2008 Link to this post

    OK, dates. No biggie, you would have thought. I'd agree with you except for the fact that our application has a strange notion when it comes to dates; it is possible to enter what our application calls an 'n/a' (for Not Applicable) date. Now this isn't the same as not entering a date; it's NOT a null date, it is a valid value.

    Now, obviously, RadCalendar and RadDatePicker don't know an 'n/a' date from a hole in the ground and I didn't want to have to write my own, but then I discovered (thanks to Steve @ telerik towers) that RadDateInput (on which RadDataPicker is based) has a neat little property called EmptyMessage. So, if I set my input's EmptyMessage property to 'n/a' I'm partway there. What happens though, I thought, if the input contains a valid date and the user wants to enter 'n/a'. Obviously, the system'll puke on it 'cos it's not a valid date. A little testing showed that if I entered an invalid value (including 'n/a') then the system would mark the input as invalid (using the RadInvalid_* css class) and the value displayed was changed to 'n/a', ie my EmptyMessage value. So, what I needed to do was stop the system from switching the class if the user entered 'n/a'.

    I reasoned that the place to catch this behaviour was in the OnError client-side event of the input. A little playing with it proved to me that there was more going on than I thought, so I looked again to telerik and, once again, Steve rode to the rescue. It seems that the system sets the input's css class after the OnErrro code has finished executing, so anything I did to set it to a particular value was being undone by the system. I needed to be able to influence the choices the system made when setting the css class. What I ended up with was this ...

    function OnDateError(sender, e) { 
      var t = e.InputText; 
      if (t.toLowerCase() == 'n/a' || t.toLowerCase() == 'na' || t.toLowerCase() == 'n a') {   
          sender.Clear(); 
          return false;   
      } else {         
        var input = sender; 
        input.Invalid = true
        input.UpdateCssClass(); 
        return true;   
      } 
    }   
     


    The above code is called by the OnError event of the RadDatePicker input. The 'if' test is self-explanitory. if the test is passed (the user has entered 'n/a) then we clear the input (and, hence, display the EmptyMessage value) and return false, which indicated that there is no error. Otherwise we set the inputs Invalid client-side property to true and tell the system to update the input's css class, before returning true to indicate that the input is, in fact invalid.

    OK, so none of this is rocket science, but I know from experience that mine is not the only industry that used a non-standard value to represent dates so I thought I'd share.

    --
    Stuart
  3. Dan
    Dan avatar
    739 posts
    Member since:
    Mar 2009

    Posted 25 Mar 2008 Link to this post

    Numeric Inputs
    This is a no-brainer, right?

    Not so. Well, that's a little unfair, it is as long as you appreciate the fact that although the RadNumericInput is based on the decimal datatype the input range for this input is limited to 2^46. This limitation comes from the limited range of the JaveScript functions parseFloat and parseInt. See if you can guess whether or not my app is happy with those limits.

    So, what to do?

    The only thing I could do in this case was roll my own.

    Being the lazy type, I started off with a RadTextbox and built around that.

    First off I added a few properties based on the double datatype. These included the actual, numeric value of the input, and a min/max value pair.

    The code for setting the (decimal) value is straightforward. I think that there are a number of ways to approach this issue; I took a nice easy one - I overrode the RadInput's Text property...

    public override string Text { 
      get { return base.Text; } 
      set { 
        try {} 
          Value = Convert.ToDecimal(value); 
          base.Text = Value.ToString(Format); 
        } 
        catch (ValueOutOfRangeException ex) { 
          throw ex; 
        } 
        catch { 
          throw new Exception(String.Format("Input has a value of '{0}' which cannot be converted to a valid decimal value", value)); 
        } 
      } 
     
     


    Here 'Value' is the decimal property that holds the true value of the input. Setting the Value property could generate an ValueOutOfRangeException exception if the value is not in the range MinValue <= value <= MaxValue and so we just pass it on if it does.

    I wanted to validate the numeric values client-side wherever possible, so had to write a validation routine to test the user-entered value against the upper and lower limits defined for the input. Now, as I've said, there a limit on the ranges for parseInt and parseFloat that prevents the use of these JavaScript functions for doing these comparisons. I came up with a range independant method for testing a number against a limit which can be tabulated like this ...

      The validity of the value against the limit is calculated as:
     
      In both the case of Minimum and Maximum values, if value (as a string) is equal to limit (as a string) the value is valid
     
      Against MinimumValue            Against MaximumValue
     
        limit  +ve | zero  | -ve       limit  +ve | zero  | -ve
      value                          value
        +ve    len | true  | true      +ve    len | false | false
       zero  false | true  | true     zero   true | true  | false
        -ve  false | false | len       -ve   true | true  |  len

     
      Where "len" indicates that first the length of the value as a string is compared against the length of the limit as a string. Only If the 2 lengths are the same is a character-by-character comparison done, starting with the Most Significant Digit.

    So, my EvaluateIsValid function for my NumericInput looks like this ...

    function EvaluateIsValid(val) { 
        // val is the validator object. 
         
        var minValueIsGood = false
        var maxValueIsGood = false
         
        // I've taken a bit of a liberty with this example and removed all the guff 
        // of getting the actual value of input; you can assume that sval holds the 
        // entered value. 
          
        var sval;   
        var limit; 
        var limitIsNegative; 
        var limitIsZero; 
        var valueIsNegative = sval.charAt(0) == "-"
        var valueIsZero = sval == "0"
         
        // First we're going to test the Minimum value... 
        limit = val.minvalue; 
        limitIsNegative = limit.charAt(0) == "-"
        limitIsZero = limit == "0"
         
        // If both the limit and value are equal as strings then the value is inside the limit 
        if (limit == sval) { 
          minValueIsGood = true
        } else { 
          if(limitIsNegative) { 
            if (!valueIsNegative) minValueIsGood = true
            else minValueIsGood = CompareByLength(sval, limit); 
          } else if (limitIsZero) { 
            minValueIsGood = !valueIsNegative 
          } else { // Limit is +ve 
            if (valueIsNegative || valueIsZero) minValueIsGood = false
            else minValueIsGood = CompareByLength(sval, limit); 
          } 
        } 
         
        // Now we're going to test the Maximum value... 
        limit = val.maxvalue; 
        limitIsNegative = limit.charAt(0) == "-"
        limitIsZero = limit == "0"
         
        // If both the limit and value are equal as strings then the value is inside the limit 
        if (limit == sval) { 
          maxValueIsGood = true
        } else { 
          if(limitIsZero) { 
            maxValueIsGood = (valueIsZero || valueIsNegative); 
          } else if (limitIsNegative) { 
            if (valueIsNegative) maxValueIsGood = CompareByLength(sval, limit); 
            else maxValueIsGood = false
          } else { // Limit is +ve 
            if (valueIsZero || valueIsNegative) maxValueIsGood = true
            else maxValueIsGood = CompareByLength(sval, limit); 
          } 
        } 
      } 
      if (!(minValueIsGood && maxValueIsGood)) { 
        input.Invalid = true
        input.UpdateCssClass(); 
        return false
      } 
      return true
     
     


    Right at the end we set the Invalid property of the input and tell system to update the input's css class.

    The function CompareByLength is simply this ...

    function CompareByLength(value, limit) { 
      // This function is only ever called for number of the same sign, so we 
      // we only ever have to test to see if the ABSOLUTE value of the limit is > 
      // the ABSOLUTE value of the number. 
      for(var i=0; i<value.length; i++) { 
        if(limit.substring(i, 0) > value.substring(i, 0)) return false
      } 
      return true


    So, there you go a numeric input that /isn't/ limited to the +/- 2^46 range that the standard control is.

    Maybe this is useful to you. Maybe not.

    --
    Stuart
  4. Dan
    Dan avatar
    739 posts
    Member since:
    Mar 2009

    Posted 25 Mar 2008 Link to this post

    Boolean inputs.

    Not really a RadControls one this, but worth mentioning if for no other reason than I learned something I didn't know.

    My web app communicates with the back-end via a web service. The reports that generate the forms sometimes have server-side validation associated with individual inputs. This means that we have to pass the user-entered values back to the backend before accepting any more input. In the majority of cases though we don't need to both the server until the form is submitted. So, all of my inputs have an equivalent of the TextChanged event. What happens when an input's value mis changed is that the TextChanged (or equivalent) is fired and a record is stored on the form control, when all of the input changes have been recorded, the form turns the change records in to a format that the web service understands and passes them all back to the back end app for processing.

    This worked a treat. That is until I put CheckBoxes on my form to hold boolean values and this input needed to fire off the server-side validation. I couldn't figure out why the CheckChanged event wasn't fired until someone at telerik towers pointed out that a CheckBox doesn't implement the IPostBackHandler interface.

    The solution was simple; create a new control based on the Checkbox that does implement the necessay interface. The nice thing is, it doesn't have to do anything different, so to get CheckBox that will force a PostBack, you just have to say ...

     
      public class BooleanInput : CheckBox, IPostBackEventHandler { 
        public BooleanInput() 
          : base() { 
          CheckedChanged += new EventHandler(BooleanInput_CheckedChanged);   
        } 
         
        public void RaisePostBackEvent(string eventArgument) { } 
     
        void BooleanInput_CheckedChanged(object sender, EventArgs e) { 
          // This is your equivalent of the TextChanged event used in the other 
          // inputs. 
        } 
      } 


    I'm sure I'm one of the last people on the planet to discover this, but just in case there's another one out there, here you go.

    --
    Stuart
  5. Blaize
    Blaize avatar
    88 posts
    Member since:
    Oct 2006

    Posted 28 Mar 2008 Link to this post

    Hey Stuart,

    I just wanted to stop by and pass my best regards! Thanks for sharing your story with us and keep it coming! You're just doing something I thought of before, but never did. Congrats!

    Cheers,
    Blaize

    P.S. What's your experience with the other controls?
  6. Dan
    Dan avatar
    739 posts
    Member since:
    Mar 2009

    Posted 28 Mar 2008 Link to this post

    Blaize,

    You're welcome. I'm planning to add more to this in the near future, including some of the stuff I've done (and plan to do) with grids.

    Right now though, the boss is insisting on me doing some proper work! 8-)

    --
    Stuart
Back to Top