Development of client-server infrastructure in javascript (part 1 - client)

    image

    What is this article about. I want to share the experience of developing a mobile application on phonegap. The result was a whole software package with a RESTfull server, clients, and even hosted on PaaS. Therefore, I will separately describe the architecture of the client application (html5 single page app wrapped in phonegap), server (nodejs with swagger-node-express + node-orm2), and how to put all this on openshift PaaS.

    Development of client-server infrastructure for javascript (part 2 - server and hosting)

    For the impatient:
    Project page
    Source code

    I think the site will immediately fall from the habra effect. This is a trial account with just over two slots for a node. But more about that at the end.

    I want to clarify right away - the project is not finished, but it is already working stably, plus you can show the main parts and architectural solutions.

    Let's start with the most important thing - the client. I used (ex) twitter bootstrap, I understand that the template is not very good yet - but the main thing is js logic. The application itself is built on require.js, although I myself do not mind minimizing the entire project. The fact is that files on the phone will be quickly loaded, and for the site, in fact, a separate application is planned in the future. I chose marionette as the javascript framework.
    Now two main modules are implemented: Auth and Conferences.
    Disabled auto start:

    this.startWithParent = false;
    

    And I manually start them after initializing the main module.

    require(
        [
            'css!bootstrap_css',
            'bootstrap',
            'app/modules/conferences',
            'app/modules/auth',
        ],
        function () {
            app.start();
        }
    );   
    

    MyConference.addInitializer(function(options){
        mainLayout = new MainLayout;
        MyConference.mainView.show(mainLayout);
        var headerView = new HeaderView;
        headerView.MyConference = MyConference;
        mainLayout.header.show(headerView);
        MyConference.Conferences.start();
        MyConference.Auth.start();
    });
    

    Auth - registration / authorization. I want to pay attention to social authorization. I always implement authorization myself and do not use third-party aggregators; I don’t know if this is good or bad. Implemented by Google, LinkedIn, Facebook, Twitter, you can just take my code if you need to implement something similar. The essence of social authorization is that I use js on the client to receive the Api key, and then transfer it to the server for verification. For example facebook:

    var afterInit = function(){
        var sendAccessToken = function(response){
            $.post(
                cfg.baseUrl + 'auth.json/facebook',
                {FacebookKEY: response.authResponse.accessToken},
                function(data, message, xhr){
                    process_social_resporce(model, data, xhr);
                },
                "text"
            );
        }
        FB.getLoginStatus(function(response) {
            if(response.status == "not_authorized" || response.status == "unknown"){
                FB.login(function(response, a) {
                    if (response.authResponse) {
                        sendAccessToken(response);
                    } else {
                        console.log(response, a)
                    }
                }, {scope:'email'});
            }else{
                sendAccessToken(response);
            }
        });
    }
    window.fbAsyncInit = function() {
        FB.init({
          appId      : cfg.facebookAppId, // App ID
          status     : true, // check login status
          cookie     : true, // enable cookies to allow the server to access the session
          xfbml      : true  // parse XFBML
        });
        afterInit();
    };
    // Load the SDK asynchronously
    (function(d){
         var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
         if (d.getElementById(id)) {return afterInit();}
         js = d.createElement('script'); js.id = id; js.async = true;
         js.src = "//connect.facebook.net/en_US/all.js";
         ref.parentNode.insertBefore(js, ref);
    }(document));
    

    For posts / likes, I do not need this key, just find out what kind of user this is. So, if you need to use offline access, then this method may not work due to the fact that you need to get a separate key that js does not give to clients.
    A separate story from twitter. It does not have browser-based client authorization, so I implemented server-side authorization. A window opens, there the user logs in and then the parent window reads the response from the child. This may not work in all browsers, so you will probably have to change it a bit. But in android application it works fine.

    var childWin = window.open(cfg.baseUrl + 'auth.json/twitter/'+Storage.get('API_KEY'), 'Twitter Auth', "height=640,width=480");
    childWin.onunload = function(){
        var check = function(){
            if(childWin.document){
                var body = childWin.document.getElementsByTagName("body")[0];
                if(!model.isNew() || body.textContent.length > 0){
                    process_social_resporce(model, body.textContent);
                    childWin.close();
                }else{
                    setTimeout(check, 100);
                }
            }else{
                setTimeout(check, 100);
            }
        }
        setTimeout(check, 100);
    }
    

    Now let's move on to the main module - Conferences. Here, in fact, everything is very simple. I describe the controller with routes.

    var ConferencesController = Marionette.Controller.extend(new function(){
        return {
            main: function(){
                MyConference.mainView.currentView.header.currentView.setHeader('Conferences');
                var conferencesCollection = new ConferencesCollection;
                var spinnerView = new SpinnerView();
                spinnerView.render();
                conferencesCollection.fetch({
                    error: function(){
                        console.log('error');
                    },
                    success: function(collection){
                        var mainView = new MainView;
                        mainView.collection = conferencesCollection;
                        MyConference.mainView.currentView.content.show(mainView);
                        spinnerView.remove();
                    }
                })
            },
            conference: function(id){
                var conferenceModel = new ConferenceModel;
                conferenceModel.set('id', id);
                conferenceModel.fetch({
                    error: function(){
                        var conferenceNotFoundView = new ConferenceNotFoundView;
                        MyConference.mainView.currentView.content.show(conferenceNotFoundView);
                    },
                    success: function(conference){
                        var conferenceFullView = new ConferenceFullView;
                        conferenceFullView.model = conference;
                        MyConference.mainView.currentView.content.show(conferenceFullView);
                    }
                });
            },
            streams: function(conference_id){
                ShowStreams(
                    conference_id,
                    function(){
                        ShowStream(streams.at(0).get('id'));
                    }
                );
            },
            stream: function(id){
                ShowStream(id);
            }
        }
    });
    var MainRouter = Backbone.Marionette.AppRouter.extend({
        appRoutes: {
            "conference/:id": "conference",
            "conferences": "main",
            "": "main",
            "streams/:conference_id": "streams",
            "stream/:id": "stream"
        },
        controller: new ConferencesController
    });
    

    As you can see, the usual Bacon model / collection is created, the data is received and transmitted to the view. The list of conferences is a regular CollectionView. I will dwell in more detail for the View of a detailed description of the conference. Support for OpenStreetMap and GoogleMaps is implemented manually. You can, of course, use Leaflet, but I'm not sure that Google likes direct access to their pictures. Also there a picture / pdf is displayed or a link to the file, if any. And at the top right is a link to the list of reports.
    If the user is logged in, he sees three buttons, "go" to the conference, to favorites and refuse. I did not inherit the detailed description from ItemView, but from Layout, so I just determined the block where to render these three buttons.

    regions: {
        decision: "#decision"
    },
    

    And depending on the status of the user, I show this or that view.

    if(MyConference.Auth.getUser().isNew()){
        var view = new GuestDecisionView;
    }else{
        var desisionModel = new DecisionModel(view.model.get('decision'));
        var view = new LoggedInDecisionView({model: desisionModel});
        view.parent = this;
    }
    this.decision.show(view);
    

    It remains only to collect and run the project on the phone. If you need to finish the platform, perform "cordova platform add android"

    cordova platform add android
    

    Later

    cordova build android
    

    For assembly or

    cordova run android
    

    To watch on your phone.

    The ultimate goal is to host applications on Google play, the Apple App Store and the Windows Store. But my main activity is web, and not mobile, development, so I have not yet registered as a developer in any of these stores.

    I hope someone will find this article helpful. I tried not to inflate her very much, but to pay attention to all the main points. I will be glad to criticize, wishes, pull requests to the repository . Due to the fact that a lot of material came out, I broke it into two articles - client and server. In the next article, I will describe the creation of a restfull server on nodejs with orm auto-documentation and memcached. And how I deployed all of this on PaaS from RedHat - openshift .

    Also popular now: