Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Thursday, August 1, 2013

WinJS and RequireJS

I had been looking at improving my JavaScript skills and learning WinJS at the same time.  One of the key things I wanted to do was work better with larger projects and larger numbers of JavaScript files. 

RequireJS is great for that, but I had considerable trouble getting it to work with WinJS.  When I did get it working I then had trouble getting it to work properly. 

RequireJS is expected to be initialised with an include reference in the page html with a data-main tag, but this doesn't really work with the WinJS app lifecycle, so it took a bit of work to get it behaving the way I expected.

Eventually I got it working, and working with async initialisation code which was a key part of the initialisation process in my sample app.

So the basics of getting RequireJS working in WinJS is the following - I have probably made a million big JavaScript no-no's here, but as I said, still learning.

default.html - add
    <script src="/js/require.js" ></script>
before default.js

default.js - initialise requireJS and include whatever dependencies you want to use in your startup code.

app.addEventListener("activated", function (args) {
        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize
                // your application here.
            } else if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.closedByUser) {
                // TODO: This application has been launched after a requested shutdown. Initialize
                // your application here.
            } else {
                // TODO: This application has been reactivated from suspension.
                // Restore application state here.
            }
 
            if (app.sessionState.history) {
                nav.history = app.sessionState.history;
            }
 
            //RequireJS Initialisation here
            require.config({
                baseUrl: '/js'
            });
 
            //this creates a promise that calls complete() when the initAsync promises finish
            //all this code happens within the require context so we are guaranteed that the dependencies are resolved before the promise is resolved
            //the initAsync methods return promises that load data from internal json data files
            //persistence.loadState is synchronous code so doesn't return a promise
            var loadPromise = new WinJS.Promise(function (complete) {
                require([
                    'persistence',
                    'game/staticdata/itemClassStore',
                    'game/staticdata/itemStore',
                    'game/gamestate/world',
                    'game/gamestate/characters',
                    'game/classes/character'
                ], function (persistence, itemClassStore, itemStore, world, characters, character) {
 
                    itemClassStore.initAsync()
                        .then(function () { return itemStore.initAsync(); })
                        .then(
                            function () {
                                var data = persistence.loadState();
                                world.fromJson(data);
                                complete();
                            }
                        );
 
 
                });
            });
 
            //args.setPromise is a function on the activate event which allows you to wait for promises to complete before continuing
            args.setPromise(
                loadPromise
                    .then(function () { return WinJS.UI.processAll(); })
                    .then(
                        function () {
                            if (nav.location) {
                                nav.history.current.initialPlaceholder = true;
                                return nav.navigate(nav.location, nav.state);
                            } else {
                                return nav.navigate(Application.navigator.home);
                            }
                        }
                    )
            );
        }
    });

xPage.js - resolve your dependencies in the page ready function with a call to require(), and perform your page initialisation as per normal. 

///  
/// 
/// 
/// 
/// 
 
(function () {
    "use strict";
 
    WinJS.Namespace.define("CharacterSelect", {
        CharacterSelectViewModel: WinJS.Class.define(
            //the viewmodel constructor takes the injected requireJS dependency from the page ready code.
            function (world) {
                this._charactersModule = world.characters;
                this._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters);
                this._inventoryDataSource = new WinJS.Binding.List(null);
            },
            {
                _charactersModule: null,
                _itemsDataSource: null,
                _inventoryDataSource: null,
                _selectedCharacterId: 0,
                selectedCharacterId: {
                    get: function () {
                        return this._selectedCharacterId;
                    },
                    set: function (value) {
                        this._selectedCharacterId = value;
                        if (value == 0) {
                            this._inventoryDataSource = new WinJS.Binding.List(null);
 
                        } else {                            
                            this._inventoryDataSource = new WinJS.Binding.List(this._charactersModule.get(value).inventory.items);
                        }
                        this._getObservable().notify("selectedCharacterId", value);
                        this._getObservable().notify("inventoryDataSource", this._inventoryDataSource);
                    }
                },
 
                listDataSource: {
                    get: function () {
                        return this._itemsDataSource;
                    }
                },
                
                inventoryDataSource: {
                    get: function () {
                        return this._inventoryDataSource;
                    }
                },
 
                addNew: function () {
                    WinJS.Navigation.navigate("/pages/newCharacter/newCharacter.html");
                },
 
                deleteCharacter: function (that) {
                    that._charactersModule.deleteCharacter(this.selectedCharacterId);
                    that._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters);
 
                    that._getObservable().notify("listDataSource", that._itemsDataSource);
                }
 
            },
            {}),
 
    });
 
    WinJS.UI.Pages.define("/pages/characterSelect/characterSelect.html", {
 
        // This function is called whenever a user navigates to this page. It
        // populates the page elements with the app's data.
        ready: function (element, options) {
            require(
                [
                    'persistence',
                    "appbar",
                    "game/gamestate/world"
                ],
                function (persistence, appbar, world) {
                    //normal page initialisation code - all dependency modules are initialised at this point
                    //create view model (passing in resolved dependencies) and bind to relevant events
                    //(WinJS only has 1-way bindings and can't declaratively bind to button events that i can see)
                    var section = element.querySelector("section");
 
                    var viewModel = new CharacterSelect.CharacterSelectViewModel(world);
                    var observableviewModel = WinJS.Binding.as(viewModel);
                    WinJS.Binding.processAll(section, observableviewModel);
 
                    document.getElementById("cmdCreate").addEventListener("click", observableviewModel.addNew, false);
                    document.getElementById("cmdDeleteCharacter").addEventListener(
                        "click",
                        function(){
                            observableviewModel.deleteCharacter(observableviewModel);
                            persistence.saveState();
                        }
                        ,
                        false
                        );
 
                    document.getElementById("characters").winControl.onselectionchanged = function (ev) {
                        var selection = document.getElementById("characters").winControl.selection;
                        if (selection.getItems()._value.length > 0) {
                            viewModel.selectedCharacterId = selection.getItems()._value[0].data.id;
                            appbar.characterSelectSelected();
                        } else {
                            viewModel.selectedCharacterId = 0;
                            appbar.characterSelectDeSelected();
                        }
 
                    };
                    appbar.characterSelectInit();
 
 
                }
            );
 
 
        },
 
        unload: function () {
            // TODO: Respond to navigations away from this page.
        },
 
        updateLayout: function (element, viewState, lastViewState) {
            /// 
 
            // TODO: Respond to changes in viewState.
        }
    });
})();

Tuesday, March 13, 2012

MVC4 Mobile and Desktop Web

As LoB applicatons are what I do on a daily basis, I have been looking at how we can use our existing expertise to build mobile applications.

This is the third component of my analysis (but only the second to be blogged), my android analysis can be found here.

As MVC4 has some improved web applications development features, such as Single Page Application templates, Mobile Application Templates, and the Web API, I decided to use this. I already blogged how the initial project templates were slightly confusing, so this time I decided to start with a standard template, and build from there.

Steps I took to getting up and running


  • Set up the Framework and Business Logic - this is becoming second nature

  • Set up a plain MVC Project

  • Create my page Controller and default view

  • Create my ApiController for the get/post operations

    • I recommend firefox for testing this, IE doesn't do JSON very well and firefox made debugging much easier.

    • Firefox (with the user agent spoofing plugin) also makes it easy to test the mobile enhancements.



  • Create a script for the KnockoutJS ViewModel

    • This included the ajax calls to the ApiController to load and update data. I was unpleasantly surprised to see that while firefox worked immediately, IE10 (win8) required a lot of tweaking and in the end I had to switch from $.getJSON() to $.ajax() jquery calls.

    • As I am creating a multi-page application I defined this at the View level.

    • At this point I also had to modify the 'bundle' in global.js to include KnockoutJS - the default bundle definition did not include this. As a side note, I completely removed the default bundling later on, and created the bundles explicitly. This was necessary to ensure the mobile and desktop script bundles were used correctly.



  • Update my View to use the KnockoutJS bindings



Once this was done, I had a functional website that I wanted to convert to a mobile friendly solution.


  • Install (Nuget) JQuery Mobile

  • Create a new script bundle to include JQuery Mobile js and css files

  • Create a new _Layout.cshtml called _Layout.mobile.cshtml - this should use the mobile script bundle in the script header.

    • This is a cool feature in MVC4, when a mobile agent is detected, the ".mobile" content is rendered, not the standard view



  • Update _Layout.mobile.cshtml to use the JQuery mobile data-role attributes for layout



We now have a mobile styled page and a desktop styled page using a single codebase. The next step would be to update the View to provide better mobile styling features, but I won't go into that here.

Thursday, March 8, 2012

MVC4 Single Page Basics

I recently highlighted that I had a bit of an issue with the MVC4 tutorials that are available here as I do not believe the standard application structure for the default apps is adequately documented.

I also note that there are some unnecessary differences between the base templates that can further confuse the issue, predominantly in the View definitions, that make it difficult to discern the important differences between the template types e.g. Single Page Applications / Mobile applications / Web API.

For example, the Login.cshtml Header in the mobile template is defined as
@section Header {
    @Html.ActionLink("Cancel", "Index", "Home", null, new { data_icon = "arrow-l", data_rel = "back" })
    <h1>@ViewBag.Title</h1>
}

while in the SPA application it is defined as
<hgroup class="title">
    <h1>@ViewBag.Title.</h1>
    <h2>Enter your user name and password below.</h2>
</hgroup>

Now there is a realistic need for the actions to be defined in the mobile header and not in the SPA header, BUT I think the differing use of hgroup and @section Header should have been standardised.



As for a lack of basic plumbing documentation, I think the SPA application is very lacking in this area. The tutorials are very good at showing how the data access and data binding works, but one thing that really stood out for me was the lack of information on how the login page was loaded in a popup and how the actions were resolved as Ajax actions instead of standard actions.

Looking into the code it wasn't actually that difficult to identify where things were happening, for example the line
        <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>

in the _layout file loads all the javascript code in the scripts folder, which in turn loaded the AjaxLogin script file.
The code below then overrides the onclick of the login links to render the link content in a popup instead of a new page.
// List of link ids to have an ajax dialog
    var links = ['#loginLink', '#registerLink'];

    $.each(links, function (i, id) {
        $(id).click(function (e) {
            var link = $(this),
                url = link.attr('href');

            if (!dialogs[id]) {
                loadAndShowDialog(id, link, url);
            } else {
                dialogs[id].dialog('open');
            }

            // Prevent the normal behavior since we use a dialog
            e.preventDefault();
        });
    });

The loadDialog function sets an additional query parameter (content=1) which the Controller accepts and renders a PartialView instead of a View (in ContextDependentView(), and prefixes "Json" to the action property in the ViewBag. The View itself uses this 'action property' as the post action, which ensures that the correct Action is called (either JsonAction or just Action) is called on the HttpPost for the View.

Yes once you know what is going on it is easy to follow, but all this detail is very important to the way Single Page Applications work, as much if not moreso than how to use upshot and knockout, and it is just left to developer to figure it out.

Anyway, this is Beta, but I just thought that for something so central to the development experience with Mvc4 there should be more emphasis on the basics to ensure that people understand what is happening rather than just guiding them directly to loading data.

MVC4

I have avoided really learning JavaScript for web development for a little while now. Or at least I have always had other things come up that has taken precedence. Despite one fairly large ExtJS project, and a smattering of JQuery controls, I have not spent much time learning JavaScript, HTML5, or CSS3.

With the release of MVC4 beta I decided to really try and get stuck into JavaScript and at the same time work on Mobile application development for rich internet apps.

One thing that I think is really lacking in the MVC javascript demos however is an explanation of the basic components of the projects. All the demos highlight how easy it is to load and manipulate data through the web api, and bind to elements on the page, but there is very little on the application plumbing, how pages are rendered, how navigation works, how the partial views are rendered to popups, etc. All of this requires diving into the code to work it out.

So hopefully in the coming weeks I'll post a few guides on these 'basics' as I start to build up my own knowledge.

Monday, November 14, 2011

Javascript standards

Html5 and css3 are well on their way to becoming standardised and you can be fairly sure that when using these technologies your knowledge can be reused time and time again. Why then are there so many different bloody javascript libraries, all with their own syntax and base functionality. Every time I start looking to re-learn Javascript for web app development I feel like I am starting all over again.

Granted jquery seems to be leading the pack, but even libraries built on jquery decide to do their own thing with data sources and other core features more often than not.

Of course other languages are not immune to this, with a plethora of frameworks and tools in .net alone, but at least the basic syntax its the same and with .net you get the benefit of an excellent dev environment to help manage the differences. Each tool and library can usually work regardless of the other tools and libraries you are using as well, whereas in javascript if you find a nice calendar control for jquery and you are using yui you are SOL.

Ok rant over, I should go check out some javascript libraries to see if I like them enough to learn since I am lagging a bit in my skills. Twitterverse is all over #kendoui at the moment, and the demos of #knockoutJS I ran through a month or so ago were nice, so maybe I should start there.