18 Sep

Cross Platform mobilosodás - 2. rész


Az előző részben kitárgyaltuk hogy is lehet könnyen hibrid mobilalkalmazásokat készíteni, aztán szó esett a RESTful webservicek készítéséről is, amivel aztán ezt ki is tudjuk majd szolgálni. Most ismét rajtunk a sor, hogy a kettőt valahogy összerakjuk!

Rockford_MergeSign_WEB

Az előző alkalmazásunk igencsak egyszerű volt, az adatokat lokálisan kezeltük, volt egy kezdeti tömb, ami minden induláskor használt az alkalmazás, tehát akármit módosítottunk rajta, ha újraindult az app, akkor mindig abból indultunk ki. Ez nem valami hatékony, hogy újraindításonként újra fel kell venni azokat. Az első lépés az volna, hogy egy perzisztencia réteget bevezessünk, tehát letároljuk az információt a mobilon.

Akkor most kezdjünk egy kicsit fabrikálni rajta.

Ugyebár az adatainkat amiket megjelenítünk a templateben a $scope változóban tároljuk. Ahhoz, hogy ide eljusson, a controllerünk meghívja az ezért felelős service-t, ami pedig szintén meghívja a szükséges transformert vagy éppen a $http service-t, $cache-t, ami épp kell, tehát egyfajta facade módjára elburkolja a dolgokat és egy viszonylag egyszerű interfészt biztosít. Akkor nézzük a mi service-ünk hogy is nézett ki a legutóbb:

angular.module('starter.services', [])

.factory('$todoService', function() {

        var todos = [{
            name : "Take out da trash",
            done : false
        },{
            name : "Buy WinRAR",
            done: true
        }];

        return {
            todos : todos
        };
    });

Nem az igazi ugye? Na most amire nekünk szükségünk lesz az angular $injector service, amivel a $todoService-be beleinjektálunk egy $http-t, amin keresztül el tudjuk majd érni a múltkor készített REST API-t. Persze fel kell készülnünk majd az offline működésre is, erről is szó esik majd. Akkor alakítsuk át kicsit a dolgot:
.factory('$todoService', ['$http', function($http) {

        var todos = [];

        return {
            todos : todos
        };
    }]);

Láthatjuk, hogy most egy üres todo tömbbel kezdünk. Ez még mindig nem az igazi, ha már bent van a $http service, használjuk, kérjük le a szerverről azokat a todokat!

Azonban mielőtt nekilátnánk, jöjjön egy apróság a backenden. Mivel a kéréseket JS által végezzük, egy másik domainről, ezért szükségünk lesz a szerveroldalon némi beállításra, hogy a kliensünk lelkivilága nyugodt legyen. Ez a dolgot Access-Control-Allow-Origin header sora végzik, ami kiköti, hogy mely domainekről engedünk lekéréseket kiszolgálni. Ahhoz, hogy ezt megtegyük, szükségünk lesz egy header bejegyzésbe a .htaccess-ben és leszarhatjuk a többit egy composer csomagra, valamint némi Zend style hegesztésre, úgyhogy védőszemüvegre fel!

Először is telepítsünk a zfr/zfr-cors csomagot:
composer require "zfr/zfr-cors:1.*"

Ha ez megvolt, akkor az új modulunkat vegyük fel a config/modules.config.php-ben:
 return [
    // többi Zendes csomag

   "ZfrCors",
];

Ha ez is megvolt, akkor a config/autoload mappába másoljuk át a vendor/zfr/zfr-cors/zfr_cors.global.php.dist fájlt (és természetesen a .dist kiterjesztést vágjuk le a végéről). Ebben a fájlban minket legfőképp az allowed origins rész fog érdekelni, itt állítsuk be a teszteléshez a http://localhost:8100-at, az ionic itt fut, valamint az engedélyezett headerekhez adjuk hozzá amiket használni fogunk:
return [
    'zfr_cors' => [
         /**
          * Set the list of allowed origins domain with protocol.
          */
          'allowed_origins' => ['http://localhost:8100'],

         /**
          * Set the list of HTTP verbs.
          */
         'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],

     ...
];

Ha ezzel megvoltunk, akkor már nem fogjuk a fejünket vakargatni a console hibák láttán. Vissza a frontendre hát!

Eddig ugye fix datasettel dolgoztunk, amit habár a service-ből kaptunk, statikus volt. Az angular-ui routingja alapesetben cache-eli a controller instancejainkat, tehát minden controllert csak egyszer példányosít majd. Ez lehetővé teszi azt, hogy bevezessünk egy inicializáló metódust, anélkül hogy aggódni kelljen amiatt, hogy újra és újra inicializálja azt, ezáltal felesleges terhelést okozva a szerver felé. Hozzuk létre hát a metódust a controllerben:
.controller('TodoCtrl', function($scope, $todoService, $ionicPopup, $ionicListDelegate) {

        function init() {
            $todoService.fetchPage().then(function(response) { // meghívjuk a service-t és amikor végzett, a visszatérő eredményt felhasználjuk

               $scope.todos = response;
            });
        }
        init(); // meghívjuk az init-et, amikor példányosítjuk a controllerünket.

Most, hogy ezzel megvagyunk, a service-ben is vezessük be a megfelelő hívást:
.factory('$todoService', ['$http','$q', function($http, $q) { // beinjektáljuk a $http és a $q service-t, 

  // előbbivel adatainkat tudjuk lekérni, utóbbi pedig az aszinkron feldolgozásban segít nekünk


        var resourceLink = "http://todo.localhost.hu/todo"; // a resource alap URL-je, minden innen indul ki, a többi elérési utat majd medialinkekből kapjuk meg


        function fetchPage() {
            var deferred = $q.defer(); // létrehozunk egy deferred objektumot

            $http.get(resourceLink).then(function(response){ // meghívjuk GET-el a linket, a visszatérési értékből pedig kikérjük az adatokat és visszatérünk velük

                deferred.resolve(response.data._embedded.todo);
            });
            return deferred.promise; // visszaadunk belőle egy thenable-t

        }

Ezzel már sikerült is elérnünk, hogy a controller létrejöttekor meghívja a szervert és visszaadja a taskokat, amiket felvettünk. A deployolt API-t a todo.localhost.hu-ra mappeltem, de használhatjuk a PHP beépített szerverét is, rögtön az APIgility-ből. Viszont a gond az, hogy amit ezután végzünk velük, azt továbbra is csak lokálisan tesszük, így hiába adunk hozzá egy újat, a változásaink elvesznek az újraindításkor. A komplett CRUD-on végig kell tehát vezessük a módosításokat.

A következő ilyen a hozzáadás lesz. Ezt egy prompt ablakkal oldottuk meg, ami hozzáadott a tömbhöz, amivel dolgoztunk. Fontos megjegyezni, hogy mindig a szerver által visszaadott értékekkel dolgozzunk, mert lehet teljesen más adatok lesznek benne, függően filterektől, auto increment, stb. értékektől. Az új forma a controllerben az alábbi módon néz ki:
$scope.newTask = function() {
    $ionicPopup.prompt({
        "title" : "New Task",
        "template" : "Enter description:",
        "inputPlaceholder" : "Rule the world",
        "okText" : "Create task"
    }).then(function(res) {
       if (res) $todoService.add(res).then(function(response) { // ha nem üres a stringünk, akkor meghívjuk a service-t, átadjuk neki a nevet, amit megadtunk.

           $scope.todos.push(response); // A visszatérő elemet pedig hozzáadjuk lokálisan is a listánkhoz

       });
    });
};

Akkor most nézzünk el a servicebe is, az új add metódust miként is implementáljuk:
function add(name) {
    var deferred = $q.defer(); // deferred objektum ismét

    $http.post(resourceLink, {"name" : name, "done" : 0}).then(function(response) { // ugyanazon a linken történik, csak most POST kérés. A done alapból 0 lesz.

        deferred.resolve(response.data) // a visszatérő értéket pedig visszaadjuk a hívónak, ami már maga a todo reprezentációja lesz

    });
    return deferred.promise;
}

Bumm, ennyi lett volna a hozzáadás is. Ha kipróbáljuk, akkor láthatjuk, hogy a listához hozzáadásra kerül, de ez eddig is ment. Frissítsük az oldalt és itt jön a differencia, ugyanis az újonnan felvett értékek immáron megmaradnak. Itt jön az, ahol fontossá válnak azok a bizonyos linkek. Emlékszünk még hogy is néznek ki a todo reprezentációi?
{
 "id":"3",
 "name":"4234234",
 "done":"1",
 "_links": {
  "self":
    {
      "href":"http:\/\/todo.localhost.hu\/todo\/3"
    }
  }
}

Nos, valahogy így. A lényeg, hogy benne van a konkrét elérési út is, így nem nekünk kell kézzel összerakni azt, ugyanis ezen az elérési úton tudunk módosítani és törölni is entitásokat. Akkor jöjjön valami egyszerűbb, pipáljunk ki egy taskot! Ennek az első része a templateben lesz, mégpedig az ng-click mostmár nem szimplán egy boolean értéket fog kapcsolgatni, hanem meghív egy metódust és átadja neki az aktuális todo reprezentációt.
<ion-item
        class="item-icon-right"
        ng-repeat="todo in todos"
        ng-click="toggleState(todo)"
        ng-class="(todo.done == 1) ? 'completed' : ''"

Az ng-class feltétele eddig szimplán az értéken múlt, mostmár azt egy numberrel vetjük össze. Ez azért kell, mert a backend felől nem booleanként érkezik a dolog, hanem tinyint-ként.

Ahhoz, hogy ez ne okozzon problémát, a done mezőnkre az API-ban húzzunk rá egy boolean casting filtert:Selection_021

Ha ezzel megvoltunk, akkor nézzük az emlegetett toggleState metódust:
$scope.toggleState = function( todo) {
    var modifiedTodo = todo; // egy ideiglenes változóba tesszük

    modifiedTodo.done = (todo.done == 1) ? 0 : 1; // megfordítjuk a done státuszát

    $todoService.modify(modifiedTodo).then(function(newTodo) {
        todo = newTodo; // az eredeti task értékét felülcsapjuk a szerver felől kapottal

    });
}

A képlet egyszerű, megkapjuk az eredeti objektumot, lemásoljuk egy új változóba, átbillentjük a done-t, meghívjuk a modify-t a service-en, az általa visszakapott értékkel pedig felülírjuk az eredetit. A service-ben mindeközben:
function modify(todo) {
    var deferred = $q.defer(); // deferred objektum

    $http.put(todo._links.self.href, { // a todo-ban tárolt linken érjük el azt PUT methodal

       name : todo.name, // csak a lényeges elemeket rakjuk bele

       done: todo.done 
    }).then(function(response) {
        deferred.resolve(response.data); // visszaadjuk az értéket

    });
    return deferred.promise;
}

Nagyjából annyi történik, hogy meghívjuk PUT-al az objektum önmagára mutató linkjét, a módosított mezőket átadjuk, aztán ami visszatért felülírja az előzőt.
Igen, ennyi példa után már bizonyára feltűnt, hogy a hibakazelés kimaradt. Ezt mindenki a saját szájíze szerint teheti meg, de majd nézünk rá 1-2 példát a végén.

Némileg hasonló lesz az edit is, azt leszámítva, hogy ott a prompt ablak visszatérése után történik meg a változtatás.
$scope.edit = function(todo) {
    $scope.data = {response : todo.name }; // beállítjuk a jelenlegi állapotát a textboxnak

    $ionicPopup.prompt({
       title : "Edit task",
       scope : $scope // kap egy $scope-ot a promptablak is

    }).then(function(res) {
        if (res !== undefined) { 
            var modifiedTodo = todo; // egy ideiglenes változóba tesszük 

            modifiedTodo.name = $scope.data.response; // módosítjuk a nevét a tasknak

            $todoService.modify(modifiedTodo).then(function(newTodo) { // átadjuk a service-nek

                todo = newTodo; // a visszatérési értékkel felülcsapjuk

                $ionicListDelegate.closeOptionButtons(); // becsukjuk az elemet

            })
        }
    });
};

Láthatjuk, hogy alapjaiban ugyanazt csináltuk itt is, ráadásul a modify service-t már megírtuk, így már ennek is működnie kell. A következő már némileg trükkösebb lesz, mert a törléskor nem bízhatunk a visszatérési értékben, lévén a DELETE nem fog body-t visszaadni. A template-ben tehát át kell adjuk az $index-et is, hogy később tudjunk ez alapján hivatkozni az elemre a dataseten belül, a törléshez:
<ion-option-button class="button-assertive" ng-click="remove(todo, $index)">
    Delete
</ion-option-button>

A controllerben is csináljunk egy remove metódust a célra:
$scope.remove = function(todo, $index) {
    $todoService.remove(todo).then(function() { // átadjuk az elemet a service-nek

        $scope.todos.splice($index, 1); // ha sikerrel járt, akkor a datasetből kivágjuk az elemet

    });
};

Nézzük a service-t:
function remove(todo) {
    var deferred = $q.defer(); // deferred objektum

    $http.delete(todo._links.self.href).then(function() { // a beágyazott linket használjuk ismét

        deferred.resolve(); // mivel nem ad vissza body-t, ezért nincs is mit visszaadni

    })
    return deferred.promise;
}

A módszer közel ugyanaz, mint a korábbiakban, az entityvel érkező link segítségével nem kell sehova beégetni semmit. Az alap CRUD műveletekkel végeztünk is, némileg kihasználjuk a médialinkeket is, de pár dologra még ki kell térnünk, Lokálisan is le kéne cache-elni a dolgot, valami queue-t is bevezethetnénk, ami próbálja a háttérben felszinkronizálni a változtatásainkat. Gondoskodnunk kellene a hibakezelésről, pl. ha nincs netkapcsolat, de mindent a maga idejében, ezeket későbbre tartogatjuk!

A módosított projekt elérhető githubon.

Hozzászólások betöltése
2014-2017 © Letscode.hu. Minden jog fenntartva. Build verzió: 1.2.37

Mik azok a sütik?


As is common practice with almost all professional websites this site uses cookies, which are tiny files that are downloaded to your computer, to improve your experience. This page describes what information they gather, how we use it and why we sometimes need to store these cookies. We will also share how you can prevent these cookies from being stored however this may downgrade or 'break' certain elements of the sites functionality.

Áltálnos információkat nyújthat ez a Wikipedia cikk a HTTP sütikről...

Hogy használjuk a sütiket?


We use cookies for a variety of reasons detailed below. Unfortunately is most cases there are no industry standard options for disabling cookies without completely disabling the functionality and features they add to this site. It is recommended that you leave on all cookies if you are not sure whether you need them or not in case they are used to provide a service that you use.

Sütik kikapcsolása


You can prevent the setting of cookies by adjusting the settings on your browser (see your browser Help for how to do this). Be aware that disabling cookies will affect the functionality of this and many other websites that you visit. Disabling cookies will usually result in also disabling certain functionality and features of the this site. Therefore it is recommended that you do not disable cookies.

A sütik amiket mi használunk


This site offers newsletter or email subscription services and cookies may be used to remember if you are already registered and whether to show certain notifications which might only be valid to subscribed/unsubscribed users.

In order to provide you with a great experience on this site we provide the functionality to set your preferences for how this site runs when you use it. In order to remember your preferences we need to set cookies so that this information can be called whenever you interact with a page is affected by your preferences.


Harmadik féltől származó sütik


In some special cases we also use cookies provided by trusted third parties. The following section details which third party cookies you might encounter through this site.

This site uses Google Analytics which is one of the most widespread and trusted analytics solution on the web for helping us to understand how you use the site and ways that we can improve your experience. These cookies may track things such as how long you spend on the site and the pages that you visit so we can continue to produce engaging content.

For more information on Google Analytics cookies, see the official Google Analytics page.

The Google AdSense service we use to serve advertising uses a DoubleClick cookie to serve more relevant ads across the web and limit the number of times that a given ad is shown to you.

For more information on Google AdSense see the official Google AdSense privacy FAQ.

We also use social media buttons and/or plugins on this site that allow you to connect with your social network in various ways. For these to work the following social media sites including; Facebook, will set cookies through our site which may be used to enhance your profile on their site or contribute to the data they hold for various purposes outlined in their respective privacy policies.


More Information


Hopefully that has clarified things for you and as was previously mentioned if there is something that you aren't sure whether you need or not it's usually safer to leave cookies enabled in case it does interact with one of the features you use on our site. However if you are still looking for more information then you can contact us through one of our preferred contact methods.

Email: fejlesztes@letscode.hu

Bezárás