Pages

Thursday, December 4, 2014

YOW 2014

Thanks to my company, stratton, I was able to attend the YOW! conference in Melbourne. It was a 2-day software conference and it was amazing :). I really liked everything, especially as it was my first big conference.
For now I want to leave some notes about sessions I visited.

Day 1:

1) User story mapping: Discover the whole story by Jeff Patton. It was good especially as Jeff is a great presenter. But the content was a little bit childish - too easy and nothing special.
2) Make impacts, not software by Gojko Adzic. Second session was not very deep as well and I suppose nobody would expect to get some out-of-the box solutions; so from that session I was able to get few ideas at least like how to piss of scrum masters :) Or why we don't actually need all that test coverage.
3) Cool things about D - why and how we use it at Facebook by Andrei Alexandrescu. Finally that was a great speech. Andrei not only covered the simple usage of D (in a great way! so I started to plan when I will download its compiler) but as well talked about purity and some general programming concepts. It was useful and interesting. I would say that it was one of the best sessions from today.
4) Groovy: the awesome parts by Paul King. I wanted to get into groovy a little and the session started with an idea that groovy is the same thing for java as D for C++ but, unfortunately, this topic was not of the same quality as previous one. Frankly, it was just boring... Basic examples and no real value. At least for me.
5) Programming in the large: Architecture and experimentation by Mark Hibberd. Mark did a great job of general description of the general programming myths with a real examples of what his team doing. He even admitted that they could do it better! It was fun to listen to him and it was really motivational. Once again - it made me think of what and how I would do in the future. Thank you Mark.
6) Functionally obvious and succinct by Edward Kmett. This session was hard. Really hard. First of all - I had never seen haskel code before and secondly - it was all about optimization so a lot of "o(log(n))". And during the session I almost constantly had a question "wat" in my head. But later on the way home I rethought what I heard and it comes that this topic is interesting for me and I would like to try to implement the same data structure with C# (and will post about it soon).

Day 2:

1) Reactive, message driven and scalable by Todd L. Montgomery. Todd was talking about the past, present and future of the http protocol. Interesting topic but it was pretty much summarized by his own words that it is likely that most deevlopers wont notice any difference - it is too low level.
2) The scaling dilemma by Mary Poppendieck. Another session about the "agile way". This time on how to scale the agile approach for the enterprise level. Frankly, just another agile talk - nothing new. Maybe those agile coaches just aiming for a real general things?
3) How we went from 1 million to 1 billion events without throwing everything away by Julian Giuca. That was just boring - Julian was speaking about their approach to frameworks, made a lot of examples and he is a good speaker. But the topic was just bad, nothing specific and nothing useful - just a bunch of general ideas that everyone can produce.
4) How to undo almost anything with Git by Peter Bell. Finally, that was the only session on that conference (for me) where the speaker was not only showing slides but actually type something. During that speech I was enjoying the real console and actual usage of the Git. That was really fun!
5) Agility at the essence of software architecture by Simon Brown. Luckily, that session was not a common agile talk. From that one I was able to catch some new ideas and approaches, I am very pleased that I have seen it. Sketching is great :)
6) Pippi's book of the dead trading cards by Elizabethe Kramer. Well, the last session of the conference was the worst one. It was more about psychology then development and it was really bad. Elizabeth told us how she was able to settle one unpleasant situation  for one company. But why I would like to know it - I don't know :)

So that was all. Unfortunately, I was not able to network at all, my social skills need a lot of improvements! 

Thursday, November 27, 2014

Rhino mocks and number of executions

Yesterday I found annoying flaw in Rhino mocks. I expected that when you said Repeat.Twice() the expectation is that the method would be executed exactly 2 times and if no - expectation should be failed. But looks like that it saying "at least twice". (Despite the fact that there is as well Repeat.AtLeastOnce())

So here is my setup:
public class Foo
{
 public virtual void DoStuff()
 {
  Console.WriteLine("DoStuff");
 }
}

public class Bar
{
 public Foo foo { get; set; }

 public void CallFoo(int max)
 {
  for (int i = 0; i < max; i++)
  {
   foo.DoStuff();
  }
 }
}

And that test is not failing unfortunately. You can even replace Times(2) with anything else - just the number of executions should be less then the actual number - test still would be green. And you can replace it with Once() or AtLeastOnce() - no difference at all!

[Test]
public void MockTest()
{
 var mock = MockRepository.GenerateMock();

 mock.Expect(x => x.DoStuff())
  .Repeat.Times(2);

 var bar = new Bar();
 bar.foo = mock;

 bar.CallFoo(3);

 mock.VerifyAllExpectations();
}

And here is how you can write that test to fail. Finally, checking for exact number of calls!

[Test]
public void MockTest2()
{
 var mock = MockRepository.GenerateMock();
 
 var bar = new Bar();
 bar.foo = mock;

 bar.CallFoo(3);

 mock.AssertWasCalled(x => x.DoStuff(),
       y => y.Repeat.Times(2));
}

Friday, November 21, 2014

jQuery FileUploader and WebForms

I`ve promised to write about my implementation of the async file uploader for the web forms application. The task was to implement it as a server control that can be added and reused on different pages (actually, in order to replace the old teleric control).

I decided to use this plugin. And the only problem was with a url to upload the file. As it should be available from the different pages I could not use the [WebMethod] so I`ve implemented a http handler to upload the file. Plus few additional changes to the client side - show the amount of files, add an option to delete uploaded file etc.

Here is my implementation:
 public class AsyncUploader : CompositeControl
 {
  private Panel _container;
  private Panel _uploadedFilesContainer;
  private HtmlInputFile _uploader;
    
  public string[] AllowedFileExtensions { get; set; }

  /// 
  /// Maximum file size in bytes
  /// 
  public int MaxFileSize { get; set; }

  /// 
  /// Gets or sets the virtual path of the folder, where RadUpload will automatically save the valid files after the upload completes.
  /// Note that existing files with the same name will be overwritten. As such it is best to append a unique identifier to the folder.
  /// 
  public string TargetPhysicalFolder { get; set; }

  public int MaxFilesCount { get; set; }
 
  protected string EscapedUniqueId { get { return Regex.Replace(UniqueID, "[$.\\s]", "_"); } }

  public List Files
  {
   get
   {
    EnsureChildControls();

    var targetFolder = TargetPhysicalFolder;

    var result = new List();

    foreach (var key in Page.Request.Form.AllKeys)
    {
     if (key.StartsWith(EscapedUniqueId + "savedFile_"))
     {
      var index = key.Replace(EscapedUniqueId + "savedFile_", "");
      var savedFileName = Page.Request.Form[key];
      result.Add(new UploadedFile
      {
       OriginalFileName = Page.Request.Form[EscapedUniqueId + "originalFile_" + index],
       SavedFileFullPath = Path.Combine(targetFolder, savedFileName),
       SavedFileName = savedFileName
      });
     }
    }
    return result;
   }
  }

  protected override void OnPreRender(EventArgs e)
  {
   if (_container.ClientIDMode != ClientIDMode.Static) throw new ArgumentException("ClientIDMode");

   var uniqueId = EscapedUniqueId;

   var options = new JavaScriptSerializer().Serialize(new
   {
    uniqueID = uniqueId,
    elementSelector = "#" + uniqueId,    
    sessionId = Page.Session.SessionID,
    maxFiles = MaxFilesCount,
    maxFileSize = MaxFileSize,
    extensions = AllowedFileExtensions != null && AllowedFileExtensions.Any()
     ? AllowedFileExtensions.ToDelimitedString("|")
     : "*",
    submitButtonsSelector = _submitButtonsToDisable
     .Select(x => "#" + x.ClientID)
     .ToDelimitedString(", ")
   });

   Page.Session[uniqueId + "_TargetFolder"] = TargetPhysicalFolder;

   var setUp = "".F(
    options);

   Page.ClientScript.RegisterClientScriptBlock(GetType(), "setup" + uniqueId, setUp);

   base.OnPreRender(e);
  }

  protected override void CreateChildControls()
  {
   _container = new Panel();
   _container.ClientIDMode = ClientIDMode.Static;
   _container.ID = EscapedUniqueId;

   _uploader = new HtmlInputFile();
   _uploader.Attributes.Add("class", "filePicker");
   if (MaxFilesCount > 1)
    _uploader.Attributes.Add("multiple", "true");

   _uploadedFilesContainer = new Panel();
   _uploadedFilesContainer.Attributes.Add("class", "uploadedFiles");
   _uploadedFilesContainer.ID = RandomString.Generate(20);

   var errorsContainer = new Panel();
   errorsContainer.Attributes.Add("class", "uploadErrors");
      
   _container.Controls.Add(_uploader);
   _container.Controls.Add(_uploadedFilesContainer);
   _container.Controls.Add(errorsContainer);

   this.Controls.Add(_container);

   base.CreateChildControls();
  }

  protected override void OnLoad(EventArgs e)
  {
   //Register resources (here I`ve used Peter Blum)
   ClientScriptLibrary
    .RegisterEmbeddedResource(
    typeof(AsyncUploader),
    "jquery.ui.widget.js",
    ClientDependencyType.Javascript);

   ClientScriptLibrary
    .RegisterEmbeddedResource(
    typeof(AsyncUploader),
    "jquery.fileupload.js",
    ClientDependencyType.Javascript);

   ClientScriptLibrary
    .RegisterEmbeddedResource(
    typeof(AsyncUploader),
    "AsyncUploadFormItem.js",
    ClientDependencyType.Javascript);

   ClientScriptLibrary
    .RegisterEmbeddedResource(
    typeof(AsyncUploader),
    "AsyncUploadFormItem.css",
    ClientDependencyType.CSS);

   base.OnLoad(e);
  }
 } 

 public class UploadedFile
 {
  public string OriginalFileName { get; set; }
  public string SavedFileName { get; set; }
  public string SavedFileFullPath { get; set; }
 }

var setupfileUpload = function (options) {

    var $filePicker = $(options.elementSelector + ' .filePicker');
    var $uploadedFiles = $(options.elementSelector + ' .uploadedFiles');
    var $uploadErrors = $(options.elementSelector + ' .uploadErrors'); 

    $filePicker.fileupload({
        url: 'fileUpload.axd',
        dropZone: $filePicker,
        add: function (e, data) {
            cleanExceptionsPanel();

            var count = $uploadedFiles.find('.sentFile').length;
            
            //client validation by file size and file type
            if (!isUploadLimit(options.maxFiles, count) ||
                !isFileValid(data.files[0].size, data.files[0].name)) return;

            //to support duplicated files the div id should be unique for different files - and that Id should be passed to the handler.
            var divId = 'id' + (new Date()).getTime();

            //submit the form with 2 additional parameters - where to save and file id
            data.formData = { targetFolder: options.targetFolder, fileId: divId, sessionId: options.sessionId, controlid: options.uniqueID };
            var jqXHR = data.submit();
            
            //append a div containing inputs for a given file
            var div = $('
'); div.append('
'); div.append('' + data.files[0].name + ''); var cancelButton = $('x'); cancelButton.on('click', function () { //delete uploaded file and/or cancel the upload process cleanExceptionsPanel(); jqXHR.abort(); var savedFile = $(this).parent().find('.savedFile').val(); if (savedFile) $.post('fileUpload.axd', { fileName: savedFile, deleteRequest: true, sessionId: options.sessionId, controlid: options.uniqueID }); $(this).parent().remove(); }); div.append(cancelButton); div.append(''); $uploadedFiles.append(div); }, done: function (e, data) { var res = jQuery.parseJSON(data.result); var div = $uploadedFiles.find('#' + res.FileId); //remove the progress bar and insert a 'complete' dot instead div.find('.progressbar').remove(); div.prepend('
'); var count = div.data('filenumber'); div.append(''); //manually hide the validation error var validationError = $(options.elementSelector + ' span.errors .errorMessage'); validationError.css('visibility', 'hidden'); validationError.css('display', 'none'); }, progress: function (e, data) { var p = parseInt(data.loaded / data.total * 100, 10); if (typeof p === 'number') { var div = $uploadedFiles.find('#' + data.formData.fileId); var progress = div.find('.progress'); progress.css('width', p + '%'); } }, fail: function (e, data) { var div = $uploadedFiles.find('#' + data.formData.fileId); //remove the progress bar and insert a 'fail' dot instead div.find('.progressbar').remove(); div.prepend('
'); } }); var cleanExceptionsPanel = function() { $uploadErrors.html(''); } var isUploadLimit = function(maxCount, currentCount) { if (maxCount <= 0) return true; if (maxCount == 1) { // replace existing file $uploadedFiles.find('.deleteUploadedFile').each(function() { $(this).click(); }); } else if (maxCount <= currentCount) { $uploadErrors.append('Maximum number of files is attached'); return false; } return true; }; var isFileValid = function (filesize, filename) { if (filesize > options.maxFileSize) { $uploadErrors.append('File is too large to be uploaded'); return false; } var pattern = '.+?\.(' + options.extensions + ')'; if (!filename.match(new RegExp(pattern, 'i'))) { $uploadErrors.append('File type is not supported and cannot be uploaded'); return false; } return true; }; var escapeFileName = function (fileName) { return fileName.replace(/ |\.|#/g, '_'); } if (options.submitButtonsSelector) { $(options.submitButtonsSelector) .prop('disabled', false) .each(function() { { $(this).attr('title', $(this).attr('data-oldtitle')); } }); } }; }

 public class FileUploadHandler : IHttpHandler, IReadOnlySessionState
 {
  private static readonly Logger _log = LogManager.GetCurrentClassLogger();

  private void DeleteFile(HttpContext context)
  {
   // Make sure to sanitise the filename by calling Path.GetFileName. This will
   // prevent deletions from folders other than the target folder (which is known only
   // by the server)
   var filename = Path.GetFileName(context.Request.Form["filename"]);
   var controlId = context.Request.Form["controlId"];
   var path = context.Session[controlId + "_TargetFolder"].ToString();
   
   var targetFilename = Path.Combine(path, filename);

   if (File.Exists(targetFilename))
    File.Delete(targetFilename);
  }

  private void UploadFile(HttpContext context)
  {
   Parse(context.Request.InputStream, Encoding.UTF8);

   var controlId = context.Request.Form["controlId"];
   var fileId = context.Request.Form["fileId"];
   
   var path = context.Session[controlId + "_TargetFolder"].ToString();
   var targetFolder = Directory.CreateDirectory(path).FullName;
   var targetFilename = Path.Combine(targetFolder, _filename);

   // Handle existing files by incrementing counter
   int counter = 1;
   while (File.Exists(targetFilename))
   {
    counter++;
    targetFilename = Path.Combine(targetFolder,
     Path.GetFileNameWithoutExtension(_filename) + counter + Path.GetExtension(_filename));
   }

   using (var file = File.Create(targetFilename))
   {
    file.Write(_fileContents, 0, _fileContents.Length);
   }

   context.Response.Write(new JavaScriptSerializer()
    .Serialize(new
    {
     OriginalFile = _filename, 
     SavedFile =  Path.GetFileName(targetFilename),
     FileId = fileId
    }));
  }

  public void ProcessRequest(HttpContext context)
  {
   var sessionId = context.Request.Form["sessionId"];

   if (context.Session == null || context.Session.SessionID != sessionId)
    throw new InvalidOperationException("Wrong session state during the file upload operation");

   if (context.Request.Form["deleteRequest"] != null)
   {
    DeleteFile(context);
   }
   else
   {
    UploadFile(context);
   }
  }

  private byte[] ToByteArray(Stream stream)
  {
   byte[] buffer = new byte[32768];
   using (MemoryStream ms = new MemoryStream())
   {
    while (true)
    {
     int read = stream.Read(buffer, 0, buffer.Length);
     if (read <= 0)
      return ms.ToArray();
     ms.Write(buffer, 0, read);
    }
   }
  }

  private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex)
  {
   int index = 0;
   int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex);

   if (startPos != -1)
   {
    while ((startPos + index) < searchWithin.Length)
    {
     if (searchWithin[startPos + index] == serachFor[index])
     {
      index++;
      if (index == serachFor.Length)
      {
       return startPos;
      }
     }
     else
     {
      startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index);
      if (startPos == -1)
      {
       return -1;
      }
      index = 0;
     }
    }
   }

   return -1;
  }

  private void Parse(Stream stream, Encoding encoding)
  {
   // Read the stream into a byte array
   byte[] data = ToByteArray(stream);

   // Copy to a string for header parsing
   string content = encoding.GetString(data);

   // The first line should contain the delimiter
   int delimiterEndIndex = content.IndexOf("\r\n");

   if (delimiterEndIndex > -1)
   {
    string delimiter = content.Substring(0, content.IndexOf("\r\n"));

    // Look for Content-Type
    Regex re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)");
    Match contentTypeMatch = re.Match(content);

    // Look for filename
    re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
    Match filenameMatch = re.Match(content);

    // Did we find the required values?
    if (contentTypeMatch.Success && filenameMatch.Success)
    {
     // Set properties
     this._contentType = contentTypeMatch.Value.Trim();
     this._filename = filenameMatch.Value.Trim();

     // Get the start & end indexes of the file contents
     int startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;

     byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
     int endIndex = IndexOf(data, delimiterBytes, startIndex);

     int contentLength = endIndex - startIndex;

     // Extract the file contents from the byte array
     byte[] fileData = new byte[contentLength];

     Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);

     this._fileContents = fileData;
    }
   }
  }

  private string _contentType;
  public string _filename;
  public byte[] _fileContents;


  public bool IsReusable { get { return false; } }
 }

Here I`ve used the Session to store the target folder for files as this is safer. Hope this will help someone :)

Wednesday, November 19, 2014

Be careful with Path.Combine

Yesterday I was pointed out that there is a security problem with my code. I was coding the async file uploader (will post it little later) and it is able to delete unused files as well. It is done with a post of a filename and that filename is stored in a hidden input. Everything is fine with that but the problem is in my usage of the Path.Combine method.

Here is a snippet:
 var p1 = "C:\\Test";
 var p2 = "C:\\NOT_A_TEST\\File.txt";
 var p3 = "File.txt";
 
 Console.WriteLine(Path.Combine(p1, p2)); //result is "C:\NOT_A_TEST\File.txt"
 Console.WriteLine(Path.Combine(p1, p3)); //all good - C:\Test\File.txt
 
 //and 2 safe methods:
 Console.WriteLine(Path.Combine(p1, Path.GetFileName(p2))); 
 Console.WriteLine(Path.Combine(p1, Path.GetFileName(p3)));

So if you want to use path combine - make sure that the last part of it is only a filename, not the whole path as it can overwrite the whole result! And as aa side-note
 Console.WriteLine(Path.GetFileNameWithoutExtension("C:\\Test\\test.txt")); 

Will return 'test', so it is as safe as Path.GetFileName.

Wednesday, October 22, 2014

The Simplest WPF Diagram Designer - Part 3

2 weeks ago I started a series of tips about WPF Diagram Designer (part1, part2). But as you know, there is nothing new under the sun. I have been pointed that there were few similar articles already posted on CodeProject so I`ve decided to post my next part of work here. I will add basic keyboard binding and RavenDB/XML save-load functionality. The source code can be found here: https://bitbucket.org/JleruOHeP/mywpfdesigner/src.

Keyboard commands
I have added 2 commands: Delete the current element and edit the text of the current element.
For the sake of simplicity I have changed the meaning of the Tag property for shapes. Now it stores the shape entity itself instead of just the Id. Because of that it became much faster to work with them. Because user can`t select the line we need to delete the connector, line and the opposite connector when someone selects the connector and presses Del. Now the Connector should know not only its host but as well its line. Ok. And the ChangeText command is really simple - as we already have all needed functionality.

RavenDB
I wanted to pick some embedded NoSQL database and the RavenDB was an obvious choice. I added wrapper class for that matter with a static EmbeddableDocumentStore DocumentStore and 2 methods - to Save the whole model or to load all objects of a specific type. With that implementation there is nothing complex about saving and for loading you have to clear the Canvas, reinitialize lines (because they do not store their coordinates - only connectors; connectors should be reconnected to their hosts), redraw the shapes, and finally recreate the Model class (assign proper commands and set it as a DataContext for a Window).

Also as the connectors are not used as a separate entity - I removed them from the model class. Now there are only 2 collections - for lines and shapes.

XML Serialization
It is really simple. Our Model class already has everything it needs - so we can easily use the XmlSerializer class.

 var serializer = new XmlSerializer(typeof(MyCanvasModel));
 using (var writer = new StreamWriter("data.xml"))
 {
  serializer.Serialize(writer, Model);
  writer.Close();
 }

The only thing we need to add - is [XmlIgnore] attribute for all the commands because they cant be serialized.

Sunday, September 7, 2014

WPF Save command

What can be easier then type

In WPF it is a designed way to interact with user. It is much simpler then binding to events as it gives you a better flexibility with CanExecute property and you code becomes reusable.

I wanted to implement that command and checked google. I was surprised to find that there were not any direct explanation in first 2-3 results. Even more, MSDN has a really bad explanation of what that command is and as an example there is a source code for Paste command. I am not sure why :)

So just in case - if you want to bind Save command you can CommandBinding to your own handlers like this:

XAML

    
        
        
    
    
        
        
    


C#
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true; //your logic
        }

        private void SaveCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Save!");
        }

        private void OpenCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void OpenCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Open");
        }
    }

Sunday, August 10, 2014

Visual studio shortcuts

Visual Studio as any other IDE has many keyboard shortcuts and other useful tricks. And, naturally, users can adjust them. However there are developers who are not using most of them at all and even more of developers use only small subset of them. Partly because those hidden shortcuts are not that useful in general and can be quite specific and partly because of their hidden nature.

Let me list some of those I found:
First of all - navigation ones.
CTRL + arrow left or arrow right: Cursor position would be moved one word (not character!) left or right;
CTRL + arrow up or arrow down: The whole screen would be moved by cursor position would be still the same.
CTRL + [ or ]: move to the corresponding opening or closing bracket. Especially useful if you have a long cycle or if statement.
CTRL + Tab: navigation through opened tabs. If you have 2 opened files - you can switch them.

Next - some formatting tricks:
CTRL + SHIFT + U: make the selection upper case. One of the applications I have used: database store list of string values. And those can be upper case or lower case, and you need to compare them against a list of predefined constants. If you were given that list of constants in lower case you can transform them quite easily!
CTRL + L: removes the whole line

Selection:
If you hold SHIFT during cursor movement - you can select that area. More interesting options - you can hold SHIFT with other combinations. Like CTRL + SHIFT + Arrow Left would select the whole previous word; CTRL + SHIFT + END would select the whole text from the current cursor position to the very end of the file;
If you hold ALT you can select rectangle in the text. And it doesn't needs to be in the beginning of the line! Try it - hold ALT + left mouse button + move the button! Even more interesting - you can replace the selection with typing or pasting and it will replace each of selected lines!

Build:
ALT + B, U: builds current project only. It is like a separate keystrokes: Alt to activate menu, B to access Build menu and U to build current project. It can be shortened but that shortcut will always work.

Hope this helped!


Wednesday, August 6, 2014

Developing: for money or for fun?

I was always interested in development - from the very childhood when I was able to build simplest programs using Basic and Commodore 64 through school years and Pascal into current time and C#. For me the development process is fun.
But it is not the only thing I care about development. Much more important side of development for me is my salary. Simply because without that I would not be able to eat :) Though, it messes a little with a fun factor. I have to solve problems that might not be that interesting and I have to spend time on those tasks. Moreover I have to spend my energy and quite often my brains just don`t want to do anymore thinking after work.
Well, mainly because of the last statement I am asking myself - what is more important for me? If it is money than I can easily find another job with a better payments and/or better working hours. And if it is programming itself then why not to start my own personal business?
I am sure that every developer had asked himself similar questions... What were the answers? As I stated in the beginning - I think that money is not that important for me. Therefore I should start something :) So I've decided to make a simple idea planer. I imagine it would be WPF application with an intuitive GUI and a very simple functionality. Why not? It can be fun and can evolve into something bigger!

Thursday, July 31, 2014

Elliptic context menu in WPF

Today I've decided to try build a simple elliptic context menu, something like in games. While you holding right mouse button you can pick something. As it is just a prototype I will build the simplest drawing application. With that menu user would be able to select a background color.

So, first thing first. Basic layout: just a canvas with a predefined background color and 2 events for right mouse up and down. OnDown should create context menu elements and OnUp should destroy them. And those elements should be created around mouse cursor.
The easiest way to create that circular shape is to use Path with ArcSegment. And the only trick here is trigonometry. We need to provide start and end points for the arc and they should be on a circle. Basically, x = cos and y = sin. Plus we should calculate the correct angle - in radians. Thats the all mathematics we will need!

Here is my method to create a menu segment:
private static Path CreateEllipticMenu(Point center, Brush color, int currentNumber, int maxNumber)
{
    var menu = new MyMenu {MyBrush = color};
    const int size = 50;
    var angleStart = currentNumber*2*Math.PI/maxNumber;
    var angleEnd = (currentNumber + 1)*2*Math.PI/maxNumber;

    var startX = center.X + size * Math.Cos(angleStart);
    var startY = center.Y + size * Math.Sin(angleStart);

    var endX = center.X + size * Math.Cos(angleEnd);
    var endY = center.Y + size * Math.Sin(angleEnd);

    var myPath = new Path();
    myPath.Stroke = menu.MyBrush;
    myPath.StrokeThickness = 20;
    myPath.HorizontalAlignment = HorizontalAlignment.Left;
    myPath.VerticalAlignment = VerticalAlignment.Center;
    var arc = new ArcSegment();
    arc.IsLargeArc = false;
    arc.Size = new Size(size, size);
    arc.SweepDirection = SweepDirection.Clockwise;
    arc.Point = new Point(endX, endY);
    var pathFigure = new PathFigure();
    pathFigure.StartPoint = new Point(startX, startY);
    pathFigure.Segments.Add(arc);
    myPath.Data = new PathGeometry(new List<PathFigure> {pathFigure});

    myPath.MouseEnter += (e, s) => { myPath.Stroke = Brushes.Red; menu.IsSelected = true; };
    myPath.MouseLeave += (e, s) => { myPath.Stroke = menu.MyBrush; menu.IsSelected = false; };

    myPath.Tag = menu;
    return myPath;
}

I have also defined mouse enter and mouse leave events so I can select active menu item and every menu item has MyMenu object associated with it. That class is just a helper:
public class MyMenu
{
    public Brush MyBrush { get; set; }
    public bool IsSelected { get; set; }
}

Well, thats all! Just as an addition here is an example of usage:

private void MyCanvas_OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    var center = new Point(e.GetPosition(MyCanvas).X, e.GetPosition(MyCanvas).Y);

    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumSlateBlue, 0, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumAquamarine, 1, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumVioletRed, 2, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.LightYellow, 3, 4));
}

private void MyCanvas_OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    int i = 0;
    while (i < MyCanvas.Children.Count)
    {
        var child = MyCanvas.Children[i];
        if (child is Path && (child as Path).Tag is MyMenu)
        {
            var menu = (child as Path).Tag as MyMenu;
            if (menu.IsSelected) MyCanvas.Background = menu.MyBrush;

            MyCanvas.Children.Remove(child as Path);
            continue;
        }
        i++;
    }
}

Tuesday, July 29, 2014

FTDI device monitor

As usual I want to describe one of the last things I've done. Yeah I have not written anything to the blog for a while but thats not because I have not done anything but because I am planing something new!

Still, for now I want to describe how to get the list of connected USB devices using FTDI drivers. It is quite easy. Everything is done through the FTDI class.
First of all - you can check the number of connected devices with a call:

var monitor = new FTDI();
UInt32 newDeviceCount = 0;
var status = monitor.GetNumberOfDevices(ref newDeviceCount);
if (status != FTDI.FT_STATUS.FT_OK) //something wrong!

The last check is there just for exceptions. Then you can get the actual list of connected devices. As this library follows c++ style - you have to pass an initialized array of FT_DEVICE_INFO_NODE:

var ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[newDeviceCount];
ftStatus = newDevice.GetDeviceList(ftdiDeviceList);
if (ftStatus != FTDI.FT_STATUS.FT_OK) //something wrong!

And finally we can iterate through devices and get some information about them:

for (var i = 0; i < newDeviceCount; i++)
{
    _devices.Add(new DeviceDTO(i, //_devices here is a List of a simple DTO class
    ftdiDeviceList[i].Flags,
    ftdiDeviceList[i].Type,
    ftdiDeviceList[i].ID,
    ftdiDeviceList[i].LocId,
    ftdiDeviceList[i].SerialNumber,
    ftdiDeviceList[i].Description));
}

And thats all! As a side node - for my app I had to implement this as a infinite loop - watching for a new connections. It was something like this:

 var deviceFinder = new BackgroundWorker();
 deviceFinder.DoWork += (ev, sn) =>
 {
    UInt32 prevDeviceCount = 0;
    var monitor = new FTDI();

    while (true)
    {
        UInt32 newDeviceCount = 0;
        var status = monitor.GetNumberOfDevices(ref newDeviceCount);
        if (status != FTDI.FT_STATUS.FT_OK) continue;

        var dDevice = newDeviceCount - prevDeviceCount;
        if (dDevice != 0)
        {
            UpdateList(); //updates that _devices List
        }

        prevDeviceCount = newDeviceCount;

        Thread.Sleep(5000);
    }
};
deviceFinder.RunWorkerAsync();

Wednesday, March 19, 2014

Website design

One of the most popular sites is google - 1 picture, 1 textbox and 2 buttons. One of those two is never used...

So why the hell they are continue to make those flashy websites with self-advertisments? Probably it is because of designers that came from the street world. The shops have to make a flashy showcase because customers see it in a first place and based on it make a choice - which shop to visit. But with websites it is completely different. The user have already typed your url. Or asked google for direction (and in google search results they are all the same!).
It is much better to make a good user experience design - design where it is completely clear where to read what you want and where to put your credit card number! I`m not sure, but if some site is too provoking, personally, I would try not to use it again...
Moreover, most of experienced users made a habit to ignore all the flashy stuff on the right, left and top of the page. So why to put your actual content into self-sliding panel? It just annoying!

Well what I want to say is - lets make a great user-friendly websites! :)

And a little picture related to this issue:

Tuesday, March 18, 2014

Google Custom Search and Firefox

Another thing I`ve worked on was an implementing of the google custom search for our website. We`ve decided to use two paged view. The script for search input was stored into a user control whichwas used on every page and the script for results was added to a separate page.

Clean and easy! Almost... This worked just fine for most of the browsers - Chrome (obviously), Opera, Safari... But the Firefox was an unpleasant exception. It was throwing google.search.Search.apiaryXXXXX is not a function, like:

TypeError: google.search.Search.apiary17868 is not a function

So this task will require something bigger then a copy-paste :)
And the answer was to implement our own control for search input, because the exception comes from 2 google scripts on a single page..  Moreover - implementing input string as a separate control will result in more flexible design!

The simplest implementation in WebForms - 1 TextBox, 1 Button and a handler for that button with redirect:

Response.Redirect(_searchResultsUrl + "?" + QuerystringParam + txt.Text);



Monday, March 3, 2014

Service runner

One of my recent tasks was to create a "service runner" - a small application that we can use to more easily test our service projects. That app was relatively simple yet useful so I`ve decided that it is worth posting on Git! (Yep, my very first repository on Git)

It is available here: ServiceRunner. And you can even see my photo there! :)

But speaking of that tool. As I`ve said it is quite simple - it is just a WinForms application with one referenced "FakeService". FakeService is there only for demonstration purposes. In order to use that you will have to reference your service project(s) to it, add a wrapper in the FormLoad event - in order to be able to choose from a dropdown and set breakpoints!

This helper will call OnStart and OnStop methods using corresponding buttons. In addition this app will try to load corresponding App.Config file, so your services might use that as you wish - using custom sections etc.

And I would like to announce some features I want to add in future.

  1. Logging. For now it is writing only Start|Stop events and it would be great to catch all the logging from the given service - somehow
  2. Dynamic service loader. It might be easier to use by only choosing the right path for the corresponding dll and app.config file.
  3. I`m not sure but other events like OnShutdown might be usefull for someone as well...

Friday, February 14, 2014

My approach to learn things

I was always eager to learn new things. First of all - it is just fun to know things! And from a developers perspective learning new stuff enlarges your horizons...

Recently I've accidently stumbled on that question at the programmers.stackexchange. I thought about adding my answer-opinion there but then realized that it can take a while (I mean phrasing of it). So I've decided to add a blog post about my approach :)

As a developer you have to learn new tricks almost every day. Every task means that you have to solve a problem, if that problem is not trivial then there is more than one possible solutions. Every of those solutions are better than others :)

But getting back to the question about learning things. Right now I am learning MongoDB + Python with a MongoDB courses and a Unity3d by myself. Both of those thing are completely optional for me, I just want to know more. Maybe I will use them later...

Interesting part about this is that I am learning them differently. For mongoDB I am watching online courses and for Unity I am playing all by myself. Probably, first approach is a bit more optimal because it was created by professionals; it is much easier to follow their way then reinventing the bicycle!

But for me the main part of the education is an independent study! Even with those courses I am trying to finish my homework before watching corresponding lessons. Just to make sure that I am able to do such things :) Reading documentation and searching for answers are the main skills of a professional. Yes, the true professional knows a lot, but no one can know everything!

So, overall, watching tutorials are ok to get an general impression about something or to find a solution for a specific task. But I would use them only occasionally. Because for now I am able to try solving problems with my existing experience. This, however, does not mean that I am against tutorials. I am using them as well. my schema is following: try something by myself and only then check the internet. I am checking external sources in both situations when i have successfully solved something or stuck.

For me especially nice to find out that I`ve done something the same way as a recognized professional or even better :)

To sum all up.
If it is something completely new for me, then I will try to find a "general-impression" tutorial;
If I've worked with a given technology or it's stack then I will try to do something by myself and only after it will check the external sources. If they are better then my result I am happy to learn from them!

Saturday, February 1, 2014

SignalR and MVC4

I had to use SignalR this week and it was quite funny experience :) And, fortunately, successful!

The main problem I`ve faced was about different versions of the SignalR and MVC. If you would try to install SignalR package with windowed NuGet interface you will find that it can install the latest version - 2.0.2. But the installation will fail, because it uses the latest version of OWIN.Security, which requires .Net 4.5...

So the solution is to use NuGet console! Use
install-package Microsoft.AspNet.SignalR -Version 1.1.3
and it will install everything! Then, as always create a new hub class, register it and add signalr.js and signalr/hubs scripts. (as in tutorial).

But for me it was little harder :) I`ve tried to add SignalR 2.0 and failed, but it have not rolled back correctly. The javascript files were left in Scripts folder... And when I`ve reinstalled the correct version they were not downgraded!

Because of that I was not able to connect to hub - my overridden OnConnect just was not called! I had to make parallel project and install everything in there in a right way to connect...

Hope this notes will help someone :)

Monday, January 27, 2014

DailyHobby.tk

Last week I was busy making a new website. My wife had the idea of creating a website dedicated to selecting the hobby for every evening. When you do have a free evening or day and don`t know what to do - try visiting that site and picking anything you want. And of course I'm open to constructive criticism :)

This site is quite simple and to illustrate this let me show the make log. I`ve been spending my spare time after work every day - it was like 1-2 hours every day.
Day 1: Discussion of the idea and choosing hosting;
Day 2: Two paged  book-like view... Failed :)
Day 3: Added database access and deployed that database to the hosting;
Day 4: Added one paged book-like view and dynamic loading of the content;
Day 5: Added stylysh navigation menu and alternative page change;
Day 6: I was lazy ^_^
Day 7: Added static pages and updated a live database with actual content.

And that`s all!

Take a look at this site: dailyhobby.tk

Thursday, January 23, 2014

Simple javascript loading snippet

For now I`m facing a lot of situations when I need to load a content of some dynamic element in a page with ajax call. Something like this:
<div id="contentArea"></div>
<button onclick="load()" value="Load content" />
<script>
    $('#contentArea').load("url_to_load");
</script>

It is fine but sometimes I have to access database on a server side to load data and this operation can take some time... So I`ve googled a little and found (here) the simplest way to show loading animation with css. Probably, this snippet is more for myself - to not google for it once again but in the end I have something like this:
<style>
    .loading:after {
        border-width: 0 3px 0 0;
        border-style: solid;

        border-color: rgba(0, 0, 0, .5);
        border-radius: 50%;
        display: block;
        height: 50px;
        left: 50%;
        position: absolute;
        top: 50%;
        width: 50px;

        content: "";

        animation: spin 1s infinite linear;
        -webkit-animation: spin 1s infinite linear;
    }
    @keyframes spin {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
    }
    @-webkit-keyframes spin {
        from { -webkit-transform: rotate(0deg); }
        to { -webkit-transform: rotate(360deg); }
    }
</style>

<script>
    $('#contentArea').html("<div class='loading'></div>");

    $('#contentArea').load("url_to_load");
</script>

Tuesday, January 21, 2014

Dynamic draggable tree in mvc with database backup

Not so far ago I asked on stackoverflow question about updating tree model from the mvc view. Unfortunately (or not?) no one helped me with that and I have to deal with this problem by myself. The most helpful article during that task was "Model binding to a list" by Phil Haack.

So now I want to describe my solution in details - it might be useful for someone...

My main goal was to read flat collection of nodes from the database and then transform it into tree-like structure in the MVC view. For the first half. The second one is the backward operation...

So let`s split problem into parts :)
1) Database access. I`m using NHibernate and here is my entity:
public class Bookmark : Entity
{
    public virtual string Href { get; set; }

    public virtual bool IsFolder { get; set; }

    public virtual Guid? ParentId { get; set; }

    private IList<Bookmark> _children = new List<Bookmark>();

    public virtual IList<Bookmark> Children

    {
        get { return _children; }
    }

    public virtual string Label { get; set; }
}

Mapping (fluent) is simple, the only thing about it:
HasMany(x => x.Children)
    .Access.CamelCaseField(Prefix.Underscore)
    .KeyColumn("ParentId")
    .Cascade.All();

Solved :)

2) Displaying that tree. As the first stage I`ve ended up with Editor template (as in question) but later I`ve moved to knockout.js template because of flexibility. 

<script type="text/html" id="bookmark-template">
<li draggable="true" data-bind="css: {draggableNode: true, 'folder': IsFolder}" ondrag="drag(event)" onclick="toggleFolder(event)">
<a class="bookmarkLink" data-bind="attr: {href: Href} , text: Label, visible: !IsFolder() && !EditableLabel()" target="_parent"/>
<a data-bind="attr: {href: Href} , visible: !IsFolder() && !EditableLabel()" target="_blank"><img src="/media/bookmarks/Arrow-turn-right-icon.png" style="width: 10px; height: 10px" /></a>
<span class="bookmarkLink" data-bind="text: Label, visible: IsFolder() && !EditableLabel()"/>
@if (Model.IsEditable)
{
  @: <input type="hidden" class="prefix" data-bind="value: dropPrefixValue"/>
  @: <input type="hidden" class="prefixIndex" data-bind="value: Id, attr: {name: prefixIndex}"/>

  @: <input type="hidden" class="prefixId"       data-bind="value: Id, attr: { name: prefixId }" />
  @: <input type="hidden" class="prefixParentId" data-bind="value: ParentId, attr: {name: prefixParentId}"  />
  @: <input type="hidden" class="prefixIsFolder" data-bind="value: IsFolder, attr: {name: prefixIsFolder}" />

  @: <input type="text"   class="prefixLabel" required placeholder="Put link label here" oninput="validateLength(this, 150);"
    @: data-bind="visible: EditableLabel, attr: {name: prefixLabel} , value: Label" />
  @: <input type="text"   class="prefixHref" placeholder="Put link address here" oninput="validateLength(this, 500);"
    @: data-bind="visible: Editable, attr: {name: prefixHref} , value: Href" />

  @: <a data-bind="click: ChangeEditable">edit</a>
  @: <a onclick="deleteRow(event)">delete</a>
}

<ul>
<!-- ko template: { name: 'bookmark-template', foreach: Children } -->
<!-- /ko -->
</ul>
</li>
</script>

Yeah, it looks quite ugly because of mixing razor syntax with java but thats life :)

Knockout model (I`m sorting labels on the client side...):

<script>

  function sortBookmarks(a, b) {
                if (a.Label < b.Label) return -1;
                if (a.Label > b.Label) return 1;
                return 0;
            }

        var CurrentBookmarksModel = null;
        function BookmarksModel(model) {
            var self = this;
            self.bookmarks = ko.observableArray(ko.utils.arrayMap(model.sort(sortBookmarks), function(bookmark) {
                var currentprefix = "[" + bookmark.Id + "].";
                var b = new Bookmark(
                    bookmark.Id,
                    bookmark.ParentId,
                    bookmark.Href,
                    bookmark.IsFolder,
                    bookmark.Label,
                    bookmark.Children,
                    currentprefix + "Children", //dropPrefix
                    "Index",
                    currentprefix + "Id",
                    currentprefix + "ParentId",
                    currentprefix + "IsFolder",
                    currentprefix + "Href",
                    currentprefix + "Label"
                );
                return b;
            }));

            self.add = function(isFolder) {
                var newId = Math.random();
                var currentprefix = "[" + newId + "].";
                var newLabel = isFolder ? 'new folder' : 'new leaf';
                self.bookmarks.push(new Bookmark(newId, null, null, isFolder, newLabel, null,
                    currentprefix + "Children", //dropPrefix
                    "Index",
                    currentprefix + "Id",
                    currentprefix + "ParentId",
                    currentprefix + "IsFolder",
                    currentprefix + "Href",
                    currentprefix + "Label"
                ));
            };

            self.addFolder = function() {
                self.add(true);
            };
            self.addLeaf = function() {
                self.add(false);
            };
        };

        function Bookmark(id, parentId, href, isFolder, label, children,
            dropPrefixValue, prefixIndex, prefixId, prefixParentId, prefixIsFolder, prefixHref, prefixLabel) {
            var self = this;
            this.Id = ko.observable(id);
            this.ParentId = ko.observable(parentId);
            this.Href = ko.observable(href);
            this.IsFolder = ko.observable(isFolder);
            this.Label = ko.observable(label);

            this.dropPrefixValue =  dropPrefixValue;
            this.prefixIndex = prefixIndex;
            this.prefixId = prefixId;
            this.prefixParentId = prefixParentId;
            this.prefixIsFolder = prefixIsFolder;
            this.prefixHref = prefixHref;
            this.prefixLabel = prefixLabel;
            
            this.Editable = ko.observable(false);
            this.EditableLabel = ko.observable(false);

   if (children != null) children = children.sort(sortBookmarks);
            this.Children = ko.observableArray(ko.utils.arrayMap(children, function(bookmark) {
                var currentprefix = self.dropPrefixValue + "[" + bookmark.Id + "].";
                var b = new Bookmark(bookmark.Id, bookmark.ParentId, bookmark.Href, bookmark.IsFolder, bookmark.Label, bookmark.Children,
                    currentprefix + "Children", //dropPrefix
                    self.dropPrefixValue + ".Index",
                    currentprefix + "Id",
                    currentprefix + "ParentId",
                    currentprefix + "IsFolder",
                    currentprefix + "Href",
                    currentprefix + "Label"
                );
                return b;
            }));

            this.ChangeEditable = function() {
                var prevValue = this.EditableLabel();
                this.EditableLabel(!prevValue);
                
                if (!this.IsFolder()) this.Editable(!prevValue);
            };

            self.getBookmarkFromChildren = function(id) {
                if (self.Id() == id) return self;   
                else {
                    var result = null;
                    ko.utils.arrayForEach(self.Children(), function(bookmark) {
                        var validBookmark = bookmark.getBookmarkFromChildren(id);
                        if (validBookmark != null) {
                            result = validBookmark;
                        }
                    });

                    return result;
                }
            };
        }
</script>


And the main form:

@model BookmarksViewModel


<div id="draggableArea">
@using (Html.BeginForm())
{
    <ul id="bookmarksTree">
    <!-- ko template: { name: 'bookmark-template', foreach: bookmarks } -->
    <!-- /ko -->
    </ul>
    if (Model.IsEditable)
    {
        <div id="rootAnchor">Drop here to add to the root level.. </div>
        <br />
        <a data-bind="click: addFolder" class="linkWithPointer">Add new folder</a>
        <a data-bind="click: addLeaf" class="linkWithPointer">Add new href</a>
        <br />
        <input type="submit" value="Save" class="btnSave"/>
    }
}
</div>

And icing on top of the cake:

  function toggleFolder(e) {
      if (e.target.classList.contains("folder")) {
          $(e.target).children('ul').toggle();

          if ($(e.target).children('ul')[0].style.display == "none") {
              $(e.target).css("background-image", "url('/media/bookmarks/folder-closed.png')");
          } else {
              $(e.target).css("background-image", "url('/media/bookmarks/folder-open.png')");
          }
      }
      e.stopPropagation();
  }

Now I have to make it little clear. As a model I`ve used class inherited from List<Bookmark> with one additional property - IsEditable. Some users of the website were able to edit bookmarks, some of them - not.

Finally, here is the entry point:

<script>

 $(function() {
            var data = @(Html.Raw(Json.Encode(Model)));
            CurrentBookmarksModel = new BookmarksModel(data);
            ko.applyBindings(CurrentBookmarksModel);
            
            @if (Model.IsEditable)
            {
                //assign handlers
                @: document.getElementById("bookmarksTree").ondrop = drop;
                @: document.getElementById("bookmarksTree").ondragover = allowDrop;
                @: document.getElementById("rootAnchor").ondrop = drop;
                @: document.getElementById("rootAnchor").ondragover = allowDrop;
                @: var draggableNodesList = document.getElementsByClassName("draggableNode");
                @: for (var i = 0; i < draggableNodesList.length; i++) {
                @:    draggableNodesList[i].ondrag = drag;
                @: }
            }
      
            //prevent form submition on Enter
            $(window).keydown(function(event) {
                if (event.keyCode == 13) {
                    event.preventDefault();
                }
            });
        });
</script>


Done! :)

3) Changing. Users should be able to edit tree by dragging nodes from one folder to another, by deleting them and by adding new elements. For drag-drop functionality I`ve used built-in HTML5 stuff.

<script type="text/javascript">

        function allowDrop(ev) {
            ev.preventDefault();
        }

        var draggedNode = null;

        function drag(ev) {
            draggedNode = $(ev.target).closest("li")[0];
        }

//drop a little bigger :)

        function drop(ev) {
            //this don`t work...
            //var data = ev.dataTransfer.getData("Text");
            var draggedParents = $(ev.target).closest(draggedNode);
            var target = $(ev.target).closest("li");

            if (!draggedParents.length &&
                (target.is(".folder") || ev.target.id == "rootAnchor")) {

                ev.preventDefault();

                var parentPrefix;
                var parentId;
                if (ev.target.id == "rootAnchor") {
                    target = $("#bookmarksTree");
                    parentPrefix = "";
                    parentId = "";
                } else {
                    parentPrefix = target.children(".prefix").val();
                    parentId = target.children(".prefixId").val();
                    target = target.children("ul");
                }

                draggedNode.querySelector(".prefixParentId").value = parentId;

                var draggedID = draggedNode.querySelector(".prefixId").value;
                var draggedBookmarkModel = null;
                
                ko.utils.arrayForEach(CurrentBookmarksModel.bookmarks(), function(bookmark) {
                    var validDraggedBookmark = bookmark.getBookmarkFromChildren(draggedID);
                    if (validDraggedBookmark != null) draggedBookmarkModel = validDraggedBookmark;
                });
                
                updateModelPrefixes(draggedBookmarkModel, parentPrefix);
                updateChildrenPrefixes(draggedNode, parentPrefix);

                target.append(draggedNode);
            }
        }

        function updateModelPrefixes(model, parentPrefix) {
            var prefixIndex = parentPrefix + (parentPrefix == "" ? "" : ".") + "Index";
            var newPrefix = parentPrefix + "[" + model.Id() + "]";
            var newPrefixDrop = newPrefix + ".Children";
            var prefixHref = newPrefix + ".Href";
            var prefixLabel = newPrefix + ".Label";
            var prefixId = newPrefix + ".Id";
            var prefixParentId = newPrefix + ".ParentId";
            var prefixIsFolder = newPrefix + ".IsFolder";

            model.prefixIndex = prefixIndex;
            model.dropPrefixValue = newPrefixDrop;
            model.prefixId = prefixId;
            model.prefixHref = prefixHref;
            model.prefixParentId = prefixParentId;
            model.prefixIsFolder = prefixIsFolder;
            model.prefixLabel = prefixLabel;
        }
        
        function updateChildrenPrefixes(child, parentPrefix) {
            var draggedId = child.querySelector(".prefixId").value;

            var prefixIndex = parentPrefix + (parentPrefix == "" ? "" : ".") + "Index";
            var newPrefix = parentPrefix + "[" + draggedId + "]";
            var newPrefixDrop = newPrefix + ".Children";
            var prefixHref = newPrefix + ".Href";
            var prefixLabel = newPrefix + ".Label";
            var prefixId = newPrefix + ".Id";
            var prefixParentId = newPrefix + ".ParentId";
            var prefixIsFolder = newPrefix + ".IsFolder";

            child.querySelector(".prefixIndex").name = prefixIndex;
            child.querySelector(".prefix").value = newPrefixDrop;
            child.querySelector(".prefixId").name = prefixId;
            child.querySelector(".prefixHref").name = prefixHref;
            child.querySelector(".prefixLabel").name = prefixLabel;
            child.querySelector(".prefixParentId").name = prefixParentId;
            if (child.querySelector(".prefixIsFolder") != null)
                child.querySelector(".prefixIsFolder").name = prefixIsFolder;

            var subChildren = $(child).children("ul").children("li");
            for (var i = 0; i < subChildren.length; i++) {
                
                var draggedBookmarkModel = null;
                
                ko.utils.arrayForEach(CurrentBookmarksModel.bookmarks(), function(bookmark) {
                    var validDraggedBookmark = bookmark.getBookmarkFromChildren(subChildren[i].querySelector(".prefixId").value);
                    if (validDraggedBookmark != null) draggedBookmarkModel = validDraggedBookmark;
                });
                updateModelPrefixes(draggedBookmarkModel, newPrefixDrop);

                updateChildrenPrefixes(subChildren[i], newPrefixDrop);
            }
        }
</script>


Deleting is easy:

function deleteRow(e) {
    if (confirm("Are you sure?"))
        $(e.target).closest("li").remove();
}

And creation of new ones are from Bookmarks model - they are addFolder  and addLeaf functions!
Few! Almost done!

4) Getting data back. As far as everything is under form, then I have to create POST variant of my view. All the magic work is already done in the drop handler. I have the correct parent ID, so it is really easy to rebuild tree once again.

[HttpPost]
[TransactionScopeActionFilter(IsReadOnly = false, EndTransactionOnActionExecuted = true)]
public ActionResult Bookmarks(List<Bookmark> model)
{
    if (model == null) model = new List<Bookmark>();
    QueryProvider.UpdateBookmarks(model);

    return RedirectToAction("Bookmarks");
}

Monday, January 13, 2014

Calculating Median value with NHibernate

NHibernate provides a lot of useful projections to make your life easier in case of statistics queries. But it does not provide you with median value, for example and this is what was needed for me recently. (Just in case here is a link for the definition of median)

To simplify the example, lets imagine that I`m querying over one table 'Sales' that have a reference to 'Employee' object (the person who made that sale) and a 'Volume' column.

The requirement is to produce a report with a total number of sales, average, total and median volumes for each employee.

As always, here is a code:

var sql = @"(SELECT AVG([Volume])
FROM (
  SELECT
    [Volume],
    ROW_NUMBER() OVER (ORDER BY [Volume] ASC, Id ASC) AS RowAsc,
    ROW_NUMBER() OVER (ORDER BY [Volume] DESC, Id DESC) AS RowDesc
  FROM Sales
  WHERE [Volume] > 0 and [EmployeeId] = ?1
  ) ra
WHERE RowAsc IN (RowDesc, RowDesc - 1, RowDesc + 1)
)";

var medianFunc = new SQLFunctionTemplate(NHibernateUtil.Double, sql);

var querry = Session.QueryOver(); //and join needed aliases

var resultList = querry
                  .SelectList(builder => builder
                                .Select(Projections.Group(s => s.Employee)).WithAlias(() => result.Employee)
                                .Select(Projections.Count(s => s.Id)).WithAlias(() => result.TotalSales)
                                .Select(Projections.Sum(s => s.Volume)).WithAlias(() => result.TotalVolume)
                                .Select(Projections.Avg(s => s.Volume)).WithAlias(() => result.AverageVolume)
                                .Select(Projections.SqlFunction(medianFunc, NHibernateUtil.Double, Projections.Property("Employee"))
                                     .WithAlias(() => result.MedianVolume))
                              )
                  .TransformUsing(Transformers.AliasToBean())
                  .List();

protected class Result
{
  public int TotalSales{ get; set; }

  public Money TotalVolume { get; set; }

  public double AverageVolume { get; set; }

  public double MedianVolume { get; set; }

  public Employee Employee { get; set; }
}

Friday, January 10, 2014

Nullable operations

Today I faced a funny and unexpected thing. What do you think is the result of this:

int? x, y, z;
z = 7;
x = y + z;

No, it`s not 7, as I expected, but x == null! So just be careful when working with nullable types!
And just as another reminder:
default(int?) == null;
default(int) == 0;

Probably this behavior comes from the SQL and its three-valued logic (here is a great article about it SQL and the Snare of Three-Valued Logic).

Saturday, January 4, 2014

Unity and Visual Studio

I`m just started playing with Unity, so this post might be not that serious. But when I installed Unity (with default parameters) it installed as well Mono Develop as a script editor. It is fine, but for me VS is much better!
By default when you double click on script it opens Mono and there is an option under context menu "sync  monodevelop project":
If you want (as me) to change it you have to go to Edit -> Preferences and change external script editor under external tools. That easy!

Thursday, January 2, 2014

New blog

I`ve decided to start a new blog. The new one will be about my idea to create games as that always was very exciting for me. Maybe I don`t have enough experience or there is only me and no one can support me with music, for example, but let`s see what it will become in about a year!
I hope that my hobby will turn into a serious business :)

So, I`m going to describe my progress and ideas there - http://jlergames.blogspot.com.au/, post complete code example somewhere as an open source project (most likely on the codeplex) and interesting algorithms (at least interesting for me) here.

Lets do it!