Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

C# and .NET Tips and Tricks

Quests in programming in .NET
An introduction to creating scalable Single Page Applications with Knockoutjs and SASS

Everyone who develops web applications (or websites in general) knows that the basic tools for creating simple "Single Page Applications" are HTML5, CSS3, JQuery and a server side framework (such as ASP.NET MVC) for serving data to AJAX requests. Although those are sufficient for implementing SPAs, they tend to lead to implementations that violate basic software design principles, leading to designs that are not easily scalable, difficult to debug and overly complicated.

In this post we will see how we can take advantage of the Knockoutjs and Syntactically Awsome StyleSheets - SASS frameworks in order to design SPAs that respect:

  • The "Single Responsibility Princile - SRP" that states that each software module should be responsible for only one thing in your code. In our case, we will implement the Model-View-View-Model pattern using the Knockoutjs framework to clearly separate the logic that drives the workings of UI from the logic that provides the data/behavior for the UI
  • The "Do not Repeat Yourself Principle - DRY" that states that there should not be any repetition in the implementation of any behavior/logic in your code (or for the fun of it, your implementation should be dry). We will see how "wet" CSS3 stylesheets are becoming and we will use the SASS Framework in order to make them as dry as possible

We will implement a simple SPA of a calculator that performs basic operations (+,-,/,*) and also has one extra operation that requests the calculation to be performed "server side" thus needing the use of an AJAX request in order to get the results.

The initial implementation

Our HTML5 will be as follows:

<div class="container">
    <div class="header">
        <div class="text">
            Demo Calculator Engine
        </div>
    </div>
    <div class="display">
        <input class="operand1 operand" type="text" value="0" />
        <div class="operator">+</div>
        <input class="operand2 operand" type="text" value="0" />
        <div class="result">0</div>
    </div>
    <div class="actions">
        <button class="addition actionButton">+</button>
        <button class="subtraction actionButton">-</button>
        <button class="division actionButton">/</button>
        <button class="multiplication actionButton">x</button>
        <button class="serverOp1 actionButton">o</button>
        <button class="equals actionButton">=</button>
    </div>
    <div class="footer">
        <div class="text">
            (c) 2013 - Ioannis Panagopoulos
        </div>
    </div>
</div>
				

We have separated the "container" div into four zones. The first has the header of the SPA, the second holds the two input boxes for the operands ("operand1","operand2"), shows the operator ("operator") and the result ("result"). The third zone has the buttons that define the operations and change the "operator" in the UI accordingly and the "=" button that performs the operation and updates the result. The last zone is the footer. The CSS3 that beautifies the HTML above is as follows (only some fragments are shown here):

.container{
    width:500px;
    height:280px;
    font-family:Helvetica, Arial, 'DejaVu Sans', 'Liberation Sans', Freesans, sans-serif;
    font-size:14px;
}
.container .header{
    height:60px;
    background-color:#cccccc;
}
.container .header .text{
    font-size:24px;
    text-align:center;
    padding-top:13px;
    color:black;
    font-weight:700;
}	
...		
.container .footer{
    border-top:1px solid #cccccc;
    padding-top:5px;
}
.container .footer .text{
    text-align:center;
}	
...
				

The logic expressed in jQuery is as follows:

$(document).ready(function () {
    $(".container").css('font-size', '30px');
    var operation = undefined;
    var operand1 = 0;
    var operand2 = 0;
    var result = 0;
    function selectOperation(op) {
        operation = op;
        $(".operator").html(operation);
    }
    $(".addition").click(       function () {selectOperation("+");});
    $(".subtraction").click(    function () { selectOperation("-"); });
    $(".division").click(       function () { selectOperation("/"); });
    $(".multiplication").click( function () { selectOperation("x"); });
    $(".serverOp1").click(      function () { selectOperation("o"); });

    $(".equals").click(function () {
        operand1 =parseFloat($(".operand1").val());
        operand2 = parseFloat($(".operand2").val());
        if (operation == "+") result = operand1 + operand2;
        if (operation == "-") result = operand1 - operand2;
        if (operation == "x") result = operand1 * operand2;
        if (operation == "/") result = operand1 / operand2;
        if (operation == "o") {
            $.get('@Url.Action("SOp1","Demo")', {operand1:operand1,operand2:operand2}, function (data) {
                result = data;
                $(".result").html(result);
            });
        }
        $(".result").html(result);
    });
});				

The problems

Is our CSS3 DRY? Not at all. First, the CSS rules repeat the class and id names again and again for each rule (see how the "container" class name is repeated again and again in the CSS). Also, if we want to have a specific property value that we want to use in many rules we have to repeat it in every rule (see the "#cccccc" color value in the CSS file of the example). Moreover, if you want to use ems instead of pixel units to make your view easily scalable, you need to have all your dimensions expressed in ems by using a calculator to convert every value expressed in pixels based on the current font size in each element. But this is obviously a nightmare since, first you are accustomed to work with pixels not ems and second because if you decide to change the font size you have to perform all the em calculations all over again! All the above statements prove that your CSS design, no matter how hard you try, will always be wet.

How about the logic expressed in javascript? Well here you have other issues. First, we will all agree that if someone reads the logic, it is not obvious right away tha this is a calculator. The fact that the user "clicks" a button is a UI concern. The operation performed when the user clicks the button is not a UI concern. But both are mixed in our implementation. Element class and id names are a UI concern and not the application logic's concern. If a designer changes a class name to suit his/her needs the logic will break. Also, we spend a lot of code getting and putting values to our UI elements. All the above also prove that our logic does not obey the SRP principle.

The solutions

To make our CSS stylesheed dry we just need to use the SASS framework. The three powerful features that will come handy for our needs are shown in the following SASS code:

$width:960;
$backcolor: #cccccc;
$baseFont:14;

@function em($target, $context: $baseFont) {
    @return ($target / $context) * 1em;
}

.container{
    font-family:Helvetica, Arial, 'DejaVu Sans', 'Liberation Sans', Freesans, sans-serif;
    font-size:$baseFont * 1px;

    width:em(500);
    height:em(280);

	.header{
		background-color:$backcolor;
		height:em(60);
		.text{
			font-size:em(24);
			text-align:center;
			padding-top:em(13,24);
			color:black;
			font-weight:700;
		}
	}
	...					
				

First, we can define variables for the property values that will get repeated within our CSS ($width,$backcolor) soliving the problem of repeating them again and again. Second, we can nest our rules avoiding the repetition of the selector names (.header is in the .container now so the .container selector is not repeated twice). Third we can define functions that will be executed to provide the property values. Here we have implemented the em function that gets a pixel value and converts it to em based on the supplied font size in pixels. Therefore we can keep working with pixel values with just enclosing them in the em function and the SASS will convert them to ems when needed.

Those three small changes have just made our CSS dry! But wait a minute. How is this converted to CSS? The conversion is not done online but during deployment therefore you do not need to worry about your client having to install something or about any impact in performance. A very useful addin that does that on the fly while you develop in VS2012 is Mindscape's Web Workbench. You write your rules say in a demo.scss file (the SASS code file), you include the demo.css file in your HTML and the tool does the conversion whenever you save the .scss file effectively recreating the actual .css file.

Now let's move to the logic of the application.

Knockoutjs is a framework that lets us implement the MVVM pattern. Therefore we can create a javascript object where all the logic of the UI will reside as follows:

	
var ipplos = ipplos || {};
(function () {
    "use strict";

    ipplos.calculatorViewModelFactory = function (viewModelParameters) {
        return {
            operation: ko.observable(''),
            operand1: ko.observable('0'),
            operand2: ko.observable('0'),
            result: ko.observable(0),
            selectOperation: function (op) {
                this.operation(op);
            },
            performOperation: function () {
                var operand1F = parseFloat(this.operand1());
                var operand2F = parseFloat(this.operand2());

                if (this.operation() == "+") this.result(operand1F + operand2F);
                if (this.operation() == "-") this.result(operand1F - operand2F);
                if (this.operation() == "x") this.result(operand1F * operand2F);
                if (this.operation() == "/") this.result(operand1F / operand2F);
                if (this.operation() == "o") {
                    var _this = this;
                    $.get(viewModelParameters.sop1ServiceURL, { operand1: this.operand1(), operand2: this.operand2() }, function (data) {
                        _this.result(data);
                    });
                }
            }

        }
    }
})();
				

We create a factory (calculatorViewModelFactory) in the "ipplos" namespace that returns an object which is our viewmodel. As you can see this model conveys much more clearly the purpose of the logic by defining the properties and the functions that are used. This factory is thus highly reusable. The only artifacts that we can detect in its code, are those strange ko.observable wrapper functions which are the knocknoutjs way of defining a property that will provide notifications for the UI that it has changed value in order for the latter to update itself.

Having defined that class, then we just need to create an object of that class passing some parameters if needed (usually the ones that will be created server side by some Razor syntax) and attach it to a toplevel element of our view (the HTML5 file) using the ko.applyBindings function as follows (the parameter in this example is the URL of the server side operation endpoint):

	
var viewModelParameters = {
    sop1ServiceURL: '@Url.Action("SOp1","Demo")'
};

var VM = ipplos.calculatorViewModelFactory(viewModelParameters);
ko.applyBindings(VM, $(".container")[0]);
				

Now from within our HTML we can specify "bound" elements to the properties of this object. For example:

<div class="display">
    <input class="operand1 operand" type="text" data-bind="value:operand1" />
    <div class="operator" data-bind="text: operation"></div>
    <input class="operand2 operand" type="text" data-bind="value: operand2" />
    <div class="result" data-bind="text: result"></div>
</div>
<div class="actions">
    <button class="addition actionButton" data-bind="click: function () { selectOperation('+'); }">+</button>
    <button class="subtraction actionButton" data-bind="click: function () { selectOperation('-'); }">-</button>
    <button class="division actionButton" data-bind="click: function () { selectOperation('/'); }">/</button>
    <button class="multiplication actionButton" data-bind="click: function () { selectOperation('x'); }">x</button>
    <button class="serverOp1 actionButton" data-bind="click: function () { selectOperation('o'); }">o</button>
    <button class="equals actionButton" data-bind="click: performOperation">=</button>
</div>
					
				

All the "data-bind" attributes tie a property of the element with the value of the property of the object viewmodel. For example "data-bind='value:operand1'" states that the value of the input element should reflect always the value of the property "operand1" of the viewmodel and vice-versa. Or the "data-bind='click:performOperation'" means that when the event click is produced by the element, the function "performOperation" should be called from the viewmodel.

And this is it! With just the use of the knockoutjs framework we have eliminated all element class,id names from our logic, have clearly specified the behavior of the system in a reusable javascript class and do not have to write any code to transfer data from or to the UI elements. Thus, we can safely say that our implementation now obeys the "Single Responsibility Principle".

And this is it. Two frameworks, the knockoutjs and the SASS framework have helped as reach a much more better implementation in terms of code quality. The resulting code is much more resuable, less prone to bugs and easier to change. The full code of this demo can be downloaded here.

Share
Posted: Τρίτη, 25 Ιουνίου 2013 8:05 μμ από το μέλος iwannis

Σχόλια:

Χωρίς Σχόλια

Ποιά είναι η άποψή σας για την παραπάνω δημοσίευση;

(απαιτούμενο)

(απαιτούμενο)

(προαιρετικό)

(απαιτούμενο)
ÅéóÜãåôå ôïí êùäéêü:
CAPTCHA Image

Ενημέρωση για Σχόλια

Αν θα θέλατε να λαμβάνετε ένα e-mail όταν γίνονται ανανεώσεις στο περιεχόμενο αυτής της δημοσίευσης, παρακαλούμε γίνετε συνδρομητής εδώ

Παραμείνετε ενήμεροι στα τελευταία σχόλια με την χρήση του αγαπημένου σας RSS Aggregator και συνδρομή στη Τροφοδοσία RSS με σχόλια