Written on 8/28/2014 in Web development

The poet part 4 - NancyFx


The poet NancyFx AngularJS

After this, there wasn't anything too complicated or frustating1 left to do.

Nancy modules and views

I set up 2 nancy modules, an API to communicate JSON and a regular module for routing the views:

public class ApiModule : NancyModule
{
    // Repository does nothing fancy, uses azure storage api for .net to get and post 'veryPairs'
    private readonly ThePoetRepository _repo;

    public ApiModule()
    {
        _repo = new ThePoetRepository();

        Get["/api/very-pair/list/{input}"] = _ => { return GetPossibleVeryPairs(_.input); };
        Get["/api/very-pair/single/{veryWord}"] = _ => { return GetVeryPair(_.veryWord); };
        Post["/api/very-pair"] = _ => { return PostVeryPair(); };
    }

    private dynamic GetVeryPair(string veryWord)
    {
        return Negotiate
            .WithStatusCode(HttpStatusCode.OK)
            .WithModel(_repo.GetVeryPair(veryWord));
    }

    private dynamic GetPossibleVeryPairs(string input)
    {
        return Negotiate
            .WithStatusCode(HttpStatusCode.OK)
            .WithModel(_repo.GetVeryPairsByInput(input));
    }

    private dynamic PostVeryPair()
    {
        var veryPair = new VeryPairViewModel();
        this.BindTo(veryPair);
        _repo.SubmitSuggestion(veryPair.VeryWord, veryPair.BetterWord);
        return Negotiate
            .WithStatusCode(HttpStatusCode.OK);
    }
}

public class GetModule : NancyModule
{
    public GetModule()
    {
        Get["/"] = _ => { return View["index.cshtml"]; };
        Get["/about"] = _ => { return View["about.cshtml"]; };
        Get["/suggest"] = _ => { return View["suggest.cshtml"]; };
    }
}

And the views:

index.cshtml:

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "The poet";
}
<div ng-controller="indexController">
    <h1>The poet</h1>
    <p>Avoid saying:</p>
    <p id="very-text">Very <autocomplete ng-model="veryWordInput" data="veryWords" on-type="updateVeryWords" on-select="getBetterWord" attr-placeholder="tired"></autocomplete></p>
    <p>Rather say:</p>
    <p id="better-text">{{betterWord}}</p>
</div>

suggest.cshtml:

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "The poet - Suggest a combination";
}
<div ng-controller="suggestController">
    <h1>The poet</h1>
    <h2>Suggest a combination</h2>
    <p>Avoid saying:</p>
    <p id="very-text">Very <input type="text" placeholder="tired" ng-model="veryWordInput" /></p>
    <p>Rather say:</p>
    <p id="better-text"><input type="text" placeholder="Exhausted" ng-model="betterWordInput" /></p>
    <p><button ng-click="addSuggestion()">Submit</button></p>
</div>

AngularJS controllers and service

Again, nothing complicated here, I have 2 controllers for the main page and the suggestion page, a service for making HTTP calls and a directive for autocompleting a text field.

Controllers:

indexController:

ThePoet.controller('indexController', ['$scope', 'AppHttpService', function ($scope, AppHttpService) {
    $scope.veryWordInput;
    $scope.veryWords = [];
    $scope.betterWord = "exhausted";

    $scope.updateVeryWords = function (tempInput) {
        if (tempInput && tempInput.length > 0) {
            AppHttpService.get("/api/very-pair/list/" + tempInput)
            .success(function (data) {
                while ($scope.veryWords.length > 0) {
                    $scope.veryWords.pop();
                }
                for (var i = 0; i < data.length; i++) {
                    $scope.veryWords.push(data[i].veryWord);
                }

                for (var i = 0; i < data.length; i++) {
                    if (data[i].veryWord === tempInput) {
                        $scope.getBetterWord(tempInput);
                    }
                }
            });
        }
    };

    $scope.getBetterWord = function (tempInput) {
        AppHttpService.get("/api/very-pair/single/" + tempInput)
            .success(function (data) {
                $scope.betterWord = data.betterWord;
            });
    };
}]);

suggestController:

ThePoet.controller('suggestController', ['$scope', 'AppHttpService', function ($scope, AppHttpService) {
    $scope.veryWordInput;
    $scope.betterWordInput;
    $scope.feedbackType;
    $scope.feedbackMessage;

    $scope.addSuggestion = function () {
        if ($scope.veryWordInput && $scope.betterWordInput && $scope.veryWordInput.length > 3 && $scope.betterWordInput.length > 3) {
            AppHttpService.post("/api/very-pair", { veryWord: $scope.veryWordInput, betterWord: $scope.betterWordInput })
            .success(function () {
                $scope.feedbackType = "success";
                $scope.feedbackMessage = "Your suggestion was added!";

                $scope.veryWordInput = "";
                $scope.betterWordInput = "";
            }).error(function () {
                $scope.feedbackType = "error";
                $scope.feedbackMessage = "Snap, Something went wrong! You could try again, but I can't promise it will work.. :(";
            });
        } else {
            $scope.feedbackType = "error";
            $scope.feedbackMessage = "The suggestion was not added because it is too short";
        }
    };
}]);

Service:

ThePoet.service('AppHttpService', ['$http', function ($http) {
    this.basePath = "/ThePoet"
    this.loading = { counter: 0 };

    this.get = function (url) {
        var loading = this.loading;
        loading.counter++;
        var promise = $http.get(this.basePath + url, {
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });

        promise
            .success(function () {
                loading.counter--;
            })
            .error(function () {
                loading.counter--;
            });

        return promise;
    };

    this.post = function (url, data) {
        var loading = this.loading;
        loading.counter++;
        var promise = $http.post(this.basePath + url, data, {
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });

        promise
            .success(function () {
                loading.counter--;
            })
            .error(function () {
                loading.counter--;
            });

        return promise;
    };

}]);

Autocomplete: Autocomplete

Deploying to an existing azure website

I deployed the website on the existing website for my blog. So I created a virtual directory using the project properties. I changed the url's to reflect the correct path to '/ThePoet/...' and added the configuration for SquishIt. Then make sure you add a virtual directory to the azure website as described here. In the publish profile, you have to change the following:

Publishing to a virtual directory

Newer Older Top
blog comments powered by Disqus