1

I have a simple login form that after a correct login slides to the next view. A combination of two examples I found. I want to divide this login over several files using the MVC pattern. In many examples I have seen on the web this is a piece of cake in Sencha Touch 2. No matter what I try, I can't get it working. I'm used to program in Flex, Java, ActionScript, Php but the Sencha jSon is a whole other ballgame. Anyone could help me with this? I would like to have a Model, Store, View and Controller package in the recommended folder structure:

-app
  -controller
    -Controller.js
  -model
    -Model.js
  -store
    -Store.js
  -view
    -Main.js
    -Login.js
    -SecondView.js
-app.js
-app.css
-index.html

This is the current app.js that contains all the logic in one file.

Ext.require([
'Ext.form.Panel',
'Ext.form.FieldSet',
'Ext.field.Text',
'Ext.field.Password',

'Ext.data.Store'
]);

Ext.define('User', {
extend: 'Ext.data.Model',

config: {
    fields: [
        {name: 'name', type: 'string'},
        {name: 'password', type: 'string'}
    ]
}
});

Ext.setup({
icon: 'icon.png',
tabletStartupScreen: 'tablet_startup.png',
phoneStartupScreen: 'phone_startup.png',
glossOnIcon: false,
onReady: function() {
    var form;

    var formBase = {
        url: 'login.php',
        standardSubmit: false,
        title:"Login",
        items: [
            {
                xtype: 'fieldset',
                title: 'MyCompony',
                instructions: 'Log in with username and password.',
                defaults: {
                    required: true,
                    labelAlign: 'left',
                    labelWidth: '40%'
                },
                items: [
                    {
                        xtype: 'textfield',
                        name: 'name',
                        label: 'Name',
                        value: 'user',
                        autoCapitalize: false
                    },
                    {
                        xtype: 'passwordfield',
                        name: 'password',
                        label: 'Password',
                        value: 'test'
                    }
                ]
            },
            {
                xtype: 'toolbar',
                docked: 'bottom',
                items: [
                    {xtype: 'spacer'},
                    {
                        text: 'Reset',
                        handler: function() {
                            form.reset();
                        }
                    },
                    {
                        text: 'Login',
                        ui: 'confirm',
                        handler: function() {
                            if (formBase.user) {
                                form.updateRecord(formBase.user, true);
                            }
                            form.submit({
                                waitMsg: {message: 'Submitting'}
                            });
                        }
                    }
                ]
            }
        ],

        listeners: {
            submit: function(form, result) {
                console.log('success', Ext.toArray(arguments));
                view.push({
                    title: 'Second View',
                    padding: 10,
                    items: [
                        {
                            html: 'Second view'
                        },
                        {
                            xtype: 'button',
                            text: 'Pop this view!',
                            width: 200,
                            handler: function() {
                                view.pop();
                            }
                        }
                    ]
                });
            },
            exception: function(form, result) {
                console.log('failure', Ext.toArray(arguments));
            }
        }
    };

    if (Ext.os.deviceType == 'Phone') {
        Ext.apply(formBase, {
            xtype: 'formpanel',
            autoRender: true
        });
    } else {
        Ext.apply(formBase, {
            xtype: 'formpanel',
            autoRender: true,
            padding: 100
        });
    }
    form = Ext.create('Ext.form.Panel', formBase);
    var view = Ext.create('Ext.navigation.View', {
        fullscreen: true,
        items: [
            form
        ]
    });

}
});

This is a simple php script (login.php) to answer the login request.

<?php
$pw = $_REQUEST['password'];
header('Content-Type: application/json');
if($pw == 'asdf'){
    echo '{"success":true, "msg":'.json_encode('This User is authorized').'}';
}else{
    echo '{"success":false, "msg":'.
        json_encode('This User is NOT authorized').
        ', "errors" : { "password" :'.json_encode('Password is required').
        '}'.
        ', "pwd" :'.json_encode($pw).'}';
}

Anyone?

Olivier de Jonge
  • 1,454
  • 2
  • 15
  • 31

2 Answers2

5

Okay after some research (days) here's the answer:

index.html:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Map</title>
    <link rel="stylesheet" href="../../resources/css/sencha-touch.css" type="text/css">
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
    <script type="text/javascript" src="../../builds/sencha-touch-all-debug.js"></script>
    <script type="text/javascript" src="app.js"></script>
</head>
<body>
</body>
</html>

login.php:

<?php
$pw = $_REQUEST['password'];
header('Content-Type: application/json');
if($pw == 'test'){
    echo '{"success":true, "msg":'.json_encode('This User is authorized').'}';
}else{
    echo '{"success":false, "msg":'.
        json_encode('This User is NOT authorized').
        ', "errors" : { "password" :'.json_encode('Password is required').
        '}'.
        ', "pwd" :'.json_encode($pw).'}';
}
?>

app.js:

Ext.Loader.setConfig({ enabled: true });
Ext.application({
    name: 'AddressBook',

    icon: 'resources/images/icon.png',
    tabletStartupScreen: 'resources/images/tablet_startup.png',
    phoneStartupScreen: 'resources/images/phone_startup.png',
    glossOnIcon: true,

    models: ['WorkOrder'],
    stores: ['WorkOrders'],
    views: ['Main','workorder.Map'],
    controllers: ['AppController'],

    launch: function() {
        Ext.Viewport.add({
            xclass: 'AddressBook.view.Main'
        });
    }
});

werkorders.json

[
    {
        "id": "14",
        "lastName": "Franklin",
        "latitude": "52.370216",
        "longitude": "4.895168"
    },
    {
        "id": "2",
        "lastName": "Johnson",
        "latitude": "52.370316",
        "longitude": "4.895868"
    },
    {
        "id": "8",
        "lastName": "Vanderburg",
        "latitude": "52.370516",
        "longitude": "4.895968"
    }
]

app/view/Main.js:

Ext.define('AddressBook.view.Main', {
    extend: 'Ext.navigation.View',
    xtype: 'mainview',

    requires: [
        'AddressBook.view.Login',
        'AddressBook.view.workorder.Map',
        'AddressBook.view.workorder.Edit'
    ],

    config: {
        autoDestroy: false,

        navigationBar: {
            ui: 'sencha',
            items: [
                {
                    xtype: 'button',
                    id: 'editButton',
                    text: 'Edit',
                    align: 'right',
                    hidden: true,
                    hideAnimation: Ext.os.is.Android ? false : {
                        type: 'fadeOut',
                        duration: 200
                    },
                    showAnimation: Ext.os.is.Android ? false : {
                        type: 'fadeIn',
                        duration: 200
                    }
                },
                {
                    xtype: 'button',
                    id: 'saveButton',
                    text: 'Save',
                    ui: 'sencha',
                    align: 'right',
                    hidden: true,
                    hideAnimation: Ext.os.is.Android ? false : {
                        type: 'fadeOut',
                        duration: 200
                    },
                    showAnimation: Ext.os.is.Android ? false : {
                        type: 'fadeIn',
                        duration: 200
                    }
                }
            ]
        },

        items: [
            { xtype: 'loginForm' }
        ]
    }
});

app/view/Login.js:

Ext.define('AddressBook.view.Login', {
    title: "Luminizer Login",
    extend: 'Ext.form.Panel',
    xtype: 'loginForm',

    config: {
        url: 'login.php',
        standardSubmit: false,
        title: 'Login Luminizer',
        layout: 'vbox',
        items: [
            {
                xtype: 'fieldset',
                title: 'MyCompony',
                instructions: 'Log in with username and password.',
                defaults: {
                    required: true,
                    labelAlign: 'left',
                    labelWidth: '40%'
                },
                items: [
                    {
                        xtype: 'textfield',
                        name: 'username',
                        id: 'username',
                        label: 'Name',
                        value: 'user',
                        autoCapitalize: false
                    },
                    {
                        xtype: 'passwordfield',
                        name: 'password',
                        id: 'password',
                        label: 'Password',
                        value: 'test'
                    }
                ]
            },
            {
                xtype: 'toolbar',
                docked: 'bottom',
                items: [
                    {xtype: 'spacer'},
                    {
                        text: 'Reset',
                        handler: function() {
                            var form = this.parent.parent;
                            form.reset();
                        }
                    },
                    {
                        text: 'Login',
                        ui: 'confirm',
                        handler: function() {
                            var form = this.parent.parent;

                            var username = form.getValues().username;//form.down('#username').getValue();
                            var password = form.getValues().password;//down('#password').getValue();
                            form.fireEvent('login', username, password);
                        }
                    }
                ]
            }
        ]
    },

    resetForm: function() {
        var view = this.parent;
        view.down("#username").setValue("");
        view.down("#password").setValue("");
    }
});

app/view/workorder/map.js:

Ext.define('AddressBook.view.workorder.Map', {
    extend: 'Ext.Panel',
    xtype: 'map-show',
    requires: [
        'Ext.Map'
    ],
    config: {
        title: 'Information',
        layout: 'card',
        items: [
            {
                xtype: 'map',
                mapOptions : {
                    zoom : 15,
                    mapTypeId : google.maps.MapTypeId.ROADMAP,
                    navigationControl: true,
                    navigationControlOptions: {
                        style: google.maps.NavigationControlStyle.DEFAULT
                    }
                }
            }
        ]
    },

    centerMap: function(newRecord) {
        if (newRecord) {
            this.down('map').setMapCenter({
                latitude: newRecord.data.latitude,
                longitude: newRecord.data.longitude
            });
        }
    },

    setMarkers: function(markers) {
        var mapPanel = this;
        var map = this.down("map")._map;
        mapPanel.markerDataDict = {};
        mapPanel.markerDict = {};


        if(!mapPanel.infowindow) {
            mapPanel.infowindow = new google.maps.InfoWindow({
                content: 'Sencha HQ'
            });
        }
        for(var i = 0 ;i < markers.length; i++) {
            var markerData = markers[i];
            map.setZoom(15);
            var latitude = Number(markerData.data.latitude);
            var longitude = Number(markerData.data.longitude);
            var position = new google.maps.LatLng(latitude, longitude);
            mapPanel.markerDataDict[position] = markerData;
            var marker = new google.maps.Marker({
                position: position,
                title : markerData.data.id,
                map: map
            });
            mapPanel.markerDict[position] = marker;

            google.maps.event.addListener(marker, 'click', function(mouseEvent) {
                var lat = Math.round(mouseEvent.latLng.lat() * 1000000)/1000000;
                var lng = Math.round(mouseEvent.latLng.lng() * 1000000)/1000000;
                var id = mapPanel.markerDataDict[mouseEvent.latLng].data.id;
                mapPanel.infowindow = new google.maps.InfoWindow();
                mapPanel.infowindow.setContent(['Werkorder: '+ id +'<br/>',
                                                'lat.:' + lat + '<br/>',
                                                'long.:' + lng ].join(""));
                if(mapPanel.oldInfowindow)
                    mapPanel.oldInfowindow.close();
                mapPanel.oldInfowindow = mapPanel.infowindow;
                mapPanel.infowindow.open(map, mapPanel.markerDict[mouseEvent.latLng]);
                mapPanel.infowindow.setPosition(mouseEvent.latLng);
                mapPanel.fireEvent('markerClicked', mapPanel.markerDataDict[mouseEvent.latLng])
            });
        }
    }
});

app/view/workorder/Edit.js:

Ext.define('AddressBook.view.workorder.Edit', {
    extend: 'Ext.Container',
    xtype: 'contact-edit',

    config: {
        title: 'Edit',
        layout: 'fit',

        items: [
            {
                xtype: 'formpanel',
                items: [
                    {
                        xtype: 'fieldset',
                        defaults: {
                            labelWidth: '35%'
                        },
                        title: 'Information',
                        items: [
                            {
                                xtype: 'textfield',
                                label: 'First Name',
                                name: 'firstName'
                            },
                            {
                                xtype: 'textfield',
                                label: 'Last Name',
                                name: 'lastName'
                            },
                            {
                                xtype: 'textfield',
                                label: 'Title',
                                name: 'title'
                            }
                        ]
                    },
                    {
                        xtype: 'fieldset',
                        defaults: {
                            labelWidth: '35%'
                        },
                        title: 'Contact Information',
                        items: [
                            {
                                xtype: 'textfield',
                                label: 'Telephone',
                                name: 'telephone'
                            }
                        ]
                    },
                    {
                        xtype: 'fieldset',
                        title: 'Address',
                        defaults: {
                            labelWidth: '35%'
                        },
                        items: [
                            {
                                xtype: 'textfield',
                                label: 'City',
                                name: 'city'
                            },
                            {
                                xtype: 'textfield',
                                label: 'State',
                                name: 'state'
                            },
                            {
                                xtype: 'textfield',
                                label: 'Country',
                                name: 'country'
                            }
                        ]
                    }
                ]
            }
        ],

        listeners: {
            delegate: 'textfield',
            keyup: 'onKeyUp'
        },

        record: null
    },

    updateRecord: function(newRecord) {
        this.down('formpanel').setRecord(newRecord);
    },

    saveRecord: function() {
        var formPanel = this.down('formpanel'),
            record = formPanel.getRecord();

        formPanel.updateRecord(record);

        return record;
    },

    onKeyUp: function() {
        this.fireEvent('change', this);
    }
});

app/controller/AppController.js:

Ext.define('AddressBook.controller.AppController', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            main: 'mainview',
            editButton: '#editButton',
            contacts: 'contacts',
            loginForm: 'loginForm',
            showMap: 'map-show',
            editContact: 'contact-edit',
            saveButton: '#saveButton'
        },

        control: {
            main: {
                push: 'onMainPush',
                pop: 'onMainPop'
            },
            editButton: {
                tap: 'onContactEdit'
            },
            contacts: {
                itemtap: 'onMarkerSelect'
            },
            loginForm: {
                login: 'onLogin'
            },
            showMap: {
                markerClicked: 'onMarkerSelect'
            },
            saveButton: {
                tap: 'onContactSave'
            },
            editContact: {
                change: 'onContactChange'
            }
        }
    },

    onMainPush: function(view, item) {
        var editButton = this.getEditButton();

        if (item.xtype == "map-show") {
            this.getLoginForm().reset();

            this.showEditButton();
        } else {
            this.hideEditButton();
        }
    },

    onMainPop: function(view, item) {
        if (item.xtype == "contact-edit") {
            this.showEditButton();
        } else {
            this.hideEditButton();
        }
    },

    onMarkerSelect: function(record) {
        var editButton = this.getEditButton();

        if (!this.showEdit) {
            this.showEdit = Ext.create('AddressBook.view.workorder.Edit');
        }

        // Bind the record onto the show contact view
        this.showEdit.setRecord(record);

        // Push the show contact view into the navigation view
        this.getMain().push(this.showEdit);
    },

    onLogin: function(username, password) {
        var controller = this;
        Ext.Ajax.request({
            url: 'login.php',
            method: 'POST',
            disableCaching: true,

            params: {
                username: username,
                password: password
            },

            success: function(response) {
                console.log("Login successful for {username}");
                var editButton = controller.getEditButton();
                if (!controller.showContact) {
                    controller.showContact = Ext.create('AddressBook.view.workorder.Map');
                }

                var store = Ext.getStore("WorkOrders");
                store.load({callback: function(records, operation, success) {
                                if(success) {
                                    console.log(records);
                                    controller.showContact.centerMap(store.data.items[0]);
                                    controller.showContact.setMarkers(store.data.items);
                                    // Push the show contact view into the navigation view
                                    controller.getMain().push(controller.showContact);
                                    controller.getLoginForm().resetForm();
                                }
                            },
                            scope: this
                });

            },

            failure: function(response) {
                console.log("Login failure for {username}");
            }
        });

    },

    onMarkerClicked: function(markerData) {

    },

    onContactEdit: function() {
        if (!this.editContact) {
            this.editContact = Ext.create('AddressBook.view.workorder.Edit');
        }

        // Bind the record onto the edit contact view
        this.editContact.setRecord(this.getShowContact().getRecord());

        this.getMain().push(this.editContact);
    },

    onContactChange: function() {
        this.showSaveButton();
    },

    onContactSave: function() {
        var record = this.getEditContact().saveRecord();

        this.getShowContact().updateRecord(record);

        this.getMain().pop();
    },

    showEditButton: function() {
        var editButton = this.getEditButton();

        if (!editButton.isHidden()) {
            return;
        }

        this.hideSaveButton();

        editButton.show();
    },

    hideEditButton: function() {
        var editButton = this.getEditButton();

        if (editButton.isHidden()) {
            return;
        }

        editButton.hide();
    },

    showSaveButton: function() {
        var saveButton = this.getSaveButton();

        if (!saveButton.isHidden()) {
            return;
        }

        saveButton.show();
    },

    hideSaveButton: function() {
        var saveButton = this.getSaveButton();

        if (saveButton.isHidden()) {
            return;
        }

        saveButton.hide();
    }
});

app/model/WorkOrder.js:

Ext.define('AddressBook.model.WorkOrder', {
    extend: 'Ext.data.Model',

    config: {
        fields: [
            'id',
            'lastName',
            'latitude',
            'longitude'
        ]
    }
});

app/store/WorkOrders.js:

Ext.define('AddressBook.store.WorkOrders', {
    extend: 'Ext.data.Store',

    config: {
        model: 'AddressBook.model.WorkOrder',
        autoLoad: false,
        sorters: 'id',
        proxy: {
            type: 'ajax',
            url: 'werkorders.json',
            reader: {
                type: 'json'
            }
        }
    }
});
Olivier de Jonge
  • 1,454
  • 2
  • 15
  • 31
  • Thanks for this work! It saved me some time. Here is a couple 7z files of your work here. I added the touch .js and .css files and tested it! unix line-endings https://onedrive.live.com/redir?resid=EAF1A9897959A658!67874&authkey=!AN5FGmwv9U6-PK8&ithint=file%2c7z. windows line-endings https://onedrive.live.com/redir?resid=EAF1A9897959A658!67873&authkey=!AIIDGSbdpJPO5ck&ithint=file%2c7z – kodybrown Nov 05 '14 at 21:49
-1

The whole point of MVC is to break down the logic. I cant say what is wrong with keeping all logic in app.js in the question. But after looking at various examples, I learned that app.js is a short file, not a big one. Move the logic part to various other files(Controller logic to controller.js and all) and try again.
One example I can point out quickly is that the following code

var formBase = {
    url: 'login.php',
    standardSubmit: false,
    title:"Login",
    items: [
        {
            xtype: 'fieldset',
            title: 'MyCompony',
            instructions: 'Log in with username and password.',
            defaults: {
                ...
            },
            items: [
                {
                    xtype: 'textfield',
                    ...
                },
                {
                    xtype: 'passwordfield',
                    ...
                }
            ]
        },
        {
            xtype: 'toolbar',
            ...
            items: [
                {xtype: 'spacer'},
                {
                    ...
                },
                {
                    text: 'Login',
                    ...
                    handler: function() {
                        ...
                    }
                }
            ]
        }
    ],

is supposed to be in view.js. This is what MVC means. Hope it answers your question.

Olivier de Jonge
  • 1,454
  • 2
  • 15
  • 31
Rakesh Reddy
  • 136
  • 9
  • Yes thank you, but No it did not answer my question. And btw the code you point out here would not be my View.js because it also contains Controller parts. I know the advantages of MVC. That's why I take the effort to get it answered. Keeping all the logic in app.js would drive me crazy. To me the biggest advantage is that all the data is stored on one place (Model) which gives you allways a consistant representation throughout the app. Secondly I'd say that being able to reuse code and get rid of redundancy (Controller) makes it less error-prone and thirdly it helps working in a team. – Olivier de Jonge Sep 22 '12 at 07:49