Pages

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!