Tuesday, February 10, 2015

Angular Dialog

If you're using Angular you've probably come across a situation where you wanted to take some action on one scope from a different scope.  In the specific situation I was faced with today I had one scope (scope A) launching a jQuery UI dialog and the contents of the dialog contained a second scope (scope B).  My problem was that the dialog contents included a Close button that called a function that existed on scope B and I needed that event to also get called if the built-in close button (the X in the upper right-hand corner of the dialog) was used to close the dialog.  Eventually I realized that I also needed that event to get called if the user pressed the <Esc> key to close the dialog.

Although I'm sure there are lots of ways to do this, I ended up using jQuery to retrieve scope B and invoke the desired function.  Some sample code:

$scope.OpenDialogWithChildScope = function () {
    $("#dialogWithChildScope").dialog({
        width: 800,
        height: 750,
        title: "Child Scope Dialog",
        modal: true,
        closeOnEscape: true,
        open: function (event, ui) {
            // store the current object (before any changes) in a separate object
            repository.OriginalItem = angular.copy(repository.CurrentItem);
        },
        close: function () {
            // get the scope of the dialog
            var scope = angular.element("[ng-controller='childScopeController']").scope();
            // clear the errors from the dialog so it's clean the next time it opens
            scope.ClearDialogErrors();
            // check whether the contents of the dialog need to be refreshed
            // (because some change was made and that change should be reflected on scope A)
            if (repository.ShouldRefresh) {
                // refresh scope A
                repository.refresh().then(function(results) {
                    // set the item on scope A, reset the pre-changed object, reset the refresh variable
                    repository.CurrentItem = results;
                    repository.OriginalItem = null;
                    repository.ShouldRefresh = false;
                });
            } else {
                // if the contents don't need to be refreshed, replace the contents with the pre-changed object
                // in order to undo any changes that were made but not saved while the dialog was open
                repository.CurrentItem = angular.copy(repository.OriginalItem);
                repository.OriginalItem = null;
            }
        },
        position: {
            my: "center center",
            at: "center center",
            of: $("#body"),
            collision: "fit fit"
        }
    });
};

Thursday, February 5, 2015

Dating the XML Serializer

Something came up at work yesterday that proved to be a tough nut to crack: when you use the .NET XMLSerializer to serialize an object that contains a DateTime property, the serializer will convert it to an offset based on your time zone.  That means that DateTime.Now will end up getting serialized into something like 2015-02-05T08:23:31.0858835-07:00.  While that's not necessarily bad by itself, it may cause problems depending on why you're serializing the object in the first place.  We serialize our objects and pass the resulting XML as a parameter to a stored procedure in SQL Server.  Then we use SQL Server's built-in XQuery support to get the values out that we need.

The problem we ran into was that when SQL Server retrieves that DateTime value (which, remember, is now an offset), and you convert it to DATETIME in SQL it converts the value to UTC time, which probably isn't what you intended.  So the sample I used above would get converted to '2015-02-05 15:28:59.903'.  If you didn't intend to store a UTC date (which, if you did, you probably should have just set the UTC date in .NET) that value is going to be wrong.  There's a simple solution for this that took me a while to figure out.  Retrieve the value from the XML as a DATETIMEOFFSET, then convert the resulting value to a DATETIME.  Check out the sample code below.


   1:  public class SomeDateTimeContainingObject
   2:  {
   3:      public DateTime CreationDate { get; set; }
   4:   
   5:      public int Id { get; set; }
   6:   
   7:      public string AsXml(bool shouldRemoveNull = true)
   8:      {
   9:          var xmlResult = "";
  10:   
  11:          var settings = new XmlWriterSettings();
  12:          settings.Encoding = new UnicodeEncoding(false, false);
  13:          settings.Indent = true;
  14:          settings.OmitXmlDeclaration = true;
  15:          var xmlSerializer = new System.Xml.Serialization.XmlSerializer(this.GetType());
  16:          using (var stringWriter = new StringWriter())
  17:          {
  18:              using (var xmlWriter = XmlWriter.Create(stringWriter, settings))
  19:              {
  20:                  xmlSerializer.Serialize(xmlWriter, this);
  21:              }
  22:   
  23:              //Strip out namespace info
  24:              xmlResult = stringWriter.ToString().Replace("'", "''").Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", "").Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); //This is the output as a string
  25:          }
  26:   
  27:          //Load the XML doc
  28:          var xdoc = new XmlDocument();
  29:          xdoc.LoadXml(xmlResult.Replace("xsi:nil", "nullable"));
  30:   
  31:          if (shouldRemoveNull)
  32:          {
  33:              //Remove all NULL values 
  34:              foreach (XmlNode node in xdoc.SelectNodes("//*[@nullable]"))
  35:              {
  36:                  node.ParentNode.RemoveChild(node);
  37:              }
  38:          }
  39:   
  40:          return xdoc.OuterXml;
  41:      }
  42:  }


var someObject = new SomeDateTimeContainingObject
{
    CreationDate = DateTime.Now,
    Id = 123456
};
 
var xml = someObject.AsXml();


DECLARE @XML XML =
'<SomeDateTimeContainingObject><CreationDate>2015-02-05T08:23:31.0858835-07:00</CreationDate><Id>123456</Id></SomeDateTimeContainingObject>'

-- Wrong result
SELECT id.node.value('(CreationDate/text())[1]', 'DATETIME')
FROM   @XML.nodes('SomeDateTimeContainingObject') id(node)

-- Right result
SELECT CONVERT(DATETIME, id.node.value('(CreationDate/text())[1]',
                         'DATETIMEOFFSET'))
FROM   @XML.nodes('SomeDateTimeContainingObject') id(node)