Phoenix Elixir – Deploy Phoenix Apps on Heroku

Heroku merupakan salah satu tempat publish apps yang free, daftarkan akun anda pada website heroku di https://signup.heroku.com/ dan service heroku dapat digunakan. Heroku juga support dengan bahasa pemrograman elixir dan phoenix framework tentunya.. Bagaimana langkah men-deploy aplikasi phoenix pada Heroku? Phoenix sendiri sudah menjelaskan tahapan ini pada website resmi phoenix framework https://hexdocs.pm/phoenix/heroku.html, tapi saya akan menjelaskan beberapa detail yang saya alami dalam men-deploy project phoenix ke heroku.

Proses pertama yang harus dilakukan adalah mendaftarkan email anda pada web heroku dan siapkan project phoenix yang akan di-deploy.

Initialisasi project dengan git, hal ini juga akan membantu proses deploy ke depan. Untuk diketahui project yang di deploy dapat dihubungkan dengan server heroku melalui git, artinya setiap perintah commit yang dijalankan pada project akan mengupdate (setelah dilakukan push ke server heroku) file project pada server. Untuk melakukan initialisasi project dengan git, arahkan directory terminal ke directory project kemudian jalankan perintah berikut pada terminal:

$ git init
$ git add .
$ git commit -m "Initial commit"

Install heroku toolbelt, download di https://toolbelt.heroku.com/. Heroku toolbelt digunakan untuk menjalankan perintah2 heroku melalui terminal. Setelah menginstall heroku toolbelt, masukkan perintah berikut pada terminal:

$ heroku -v

Jika heroku toolbelt telah terinstal terminal akan menampilkan pesan berikut (versi bisa berbeda):

heroku-cli/6.14.30-304197d (darwin-x64) node-v8.5.0

 

MEMBUAT APLIKASI HEROKU

Kita akan membuat aplikasi baru pada heroku dengan heroke toolbelt. Pertama-tama pastikan terminal sudah berada pada direktori project yang akan di -deploy, kemudian login melalui heroku toolbelt, masukkan perintah

$ heroku login

Buat aplikasi baru dengan perintah berikut:

$ heroku create --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git"
Creating stark-savannah-17693... done, stack is cedar-14 Buildpack set. Next release on mysterious-meadow-6277 will use https://github.com/HashNuke/heroku-buildpack-elixir.git. 
https://stark-savannah-17693.herokuapp.com/ | https://git.heroku.com/stark-savannah-17693.git

Perintah di atas akan membuat aplikasi pada heroku. Nama aplikasi heroku yang dibuat digenerate secara acak.

Kita harus mendefinisikan environment apa saja yang dibutuhkan pada aplikasi heroku yang dibuat, misal kita ingin server tersebut memiliki environment PHP 5.7, Elixir x.x, Erlang x.x. Heroku akan membaca environment ini dari sebuah file buildpack yang diletakkan pada root project. Kita juga dapat mendefinisikan buildpack dengan menambahkan perintah “–buildpack” pada proses create aplikasi baru. Pada perintah di atas aplikasi yang dibuat akan memiliki buildpack dari tautan yang didefinisikan.

Jika ditelusur ke dalam tautan buildpack diatas, environment yang diminta adalah berikut:

erlang_version=19.3
elixir_version=1.4.2
always_rebuild=false
runtime_path=/app

Anda dapat memodifikasi buildpack di atas jika diperlukan.

Selain buildpack tersebut, phoenix masih membutuhkan beberapa environment agar dapat dijalankan pada heroku. Masukkan perintah berikut

$ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git 
Buildpack added. Next release on stark-savannah-17693 will use: 
1. https://github.com/HashNuke/heroku-buildpack-elixir.git 
2. https://github.com/gjaldon/heroku-buildpack-phoenix-static.git 
Run `git push heroku master` to create a new release using these buildpacks.

Perintah di atas menambahkan buildpack tambahan seperti library dan komponen yang diperlukan oleh phoenix framework.

Setiap kita masukkan perintah heroku, secara otomatis perintah tersebut akan berpengaruh pada project yang didefinisikan sebelumnya. Namun dalam kondisi anda menutup terminal (yah pasti butuh istirahat kan?), kita dapat mengarahkan perintah terminal ke aplikasi yang dituju dengan perintah:

heroku git:remote -a [our-app-name]

KONFIGURASI APLIKASI

Aplikasi phoenix memilik konfigurasi pada file “config/prod.secret.exs” dimana secara default file tersebut masuk ke dalam daftar .gitignore (file tersebut tidak masuk pada proses commit melalui git). Hal ini menyebabkan file configurasi tidak ditemukan pada heroku. Kita akan memindahkan configurasi aplikasi dari “config/prod.secret.exs” ke “config/prod.exs”. Modifikasi code pada “config/prod.exs” seperti berikut:

config :myapp, Myapp.Endpoint,
http: [port: {:system, "PORT"}],
url: [scheme: "https", host: "your heroku app url", port: 443],
force_ssl: [rewrite_on: [:x_forwarded_proto]],
cache_static_manifest: "priv/static/manifest.json",
secret_key_base: Map.fetch!(System.get_env(), "SECRET_KEY_BASE")

Konfigurasi di atas mengarahkan url aplikasi ke url heroku. Kita juga mendefinisikan nilai “secret_key_base” dengan nilai dari (System.get_env(), “SECRET_KEY_BASE”).

Dari mana dan apa System.get_env() ??

System.get_env() adalah nilai yang tersimpan pada server. Penggunaan System.get_env() sering digunakan untuk nilai yang bersifat rahasia seperti token, password, API secret, dll. Hal ini karena dengan System.get_env() tidak perlu menuliskan informasi rahasia tersebut langsung ke dalam code aplikasi, tapi di generate ke dalam variabel server (selanjutnya akan dicontohkan cara menambahkan suatu variabel keserver)

Tambahkan juga code berikut untuk untuk configurasi database:

config :myapp, Myapp.Repo,
adapter: Ecto.Adapters.Postgres,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
ssl: true

Konfigurasi di atas mengatur koneksi database yang digunakan pada heroku.  Karena informasi database rahasia, akan dimasukkan juga dengan System.get_env(). Kita tidak lagi menggunakan file “config/prod.secret.exs” sehingga kita dapat menghapus baris berikut:

import_config "prod.secret.exs"

Karena heroku memiliki batas minimun 55 detik waktu timeout, kita perlu mengurangi timeout untuk websocket transport. Pada file “web/channels/user_socket.ex” (untuk phoenix versi 1.3 file ini terdapat pada “lib/myapp/channels/user_socket.ex”) modifikasi code menjadi seperti berikut:

defmodule Myapp.UserSocket do 
   use Phoenix.Socket 
   ... 

   ## Transports 
   transport :websocket, Phoenix.Transports.WebSocket, 
   timeout: 45_000 
   ... 
end

Untuk men-deploy aplikasi pada heroku, kita perlu membuat file “Procfile” pada root aplikasi. File ini selanjutnya dibaca oleh heroku server untuk menjalankan perintah terminal awal saat menjalankan aplikasi pada heroku. Buatlah sebuah file dengan nama “Procfile” pada root project dan masukkan perintah berikut (untuk phoenix 1.2 atau kebawah bisa dengan “mix phoenix.server”):

web: MIX_ENV=prod mix phx.server

Kita telah menyelesaikan konfigurasi aplikasi, selanjutnya menambahkan konfigurasi variabel pada server.

DATABASE_URL

Nilai “DATABASE_URL” secara otomatis sudah ditambahkan ke dalam konfigurasi server saat kita membuat database postgresql pada heroku. Untuk membuat database pada baru pada heroku, masukkan perintah berikut pada terminal:

heroku addons:create heroku-postgresql:hobby-dev

Database yang dibuat juga dapat dilihat melalui web dashboard heroku.

 

POOL_SIZE

Konfigurasi nilai POOL_SIZE dengan memasukkan menjalankan perintah berikut:

$ heroku config:set POOL_SIZE=18

 

SECRET_KEY_BASE

Pertama-tama kita harus dapatkan nilai secret key dari aplikasi dengan menjalankan perintah berikut (untuk phoenix 1.2 dengan mix phoenix.gen.secret) :

mix phx.gen.secret xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53

Nilai yang di dapat barulah di arahkan ke dalam server heroku dengan perintah berikut:

heroku config:set SECRET_KEY_BASE="xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53"

Selesai konfigurasi variabel, selanjutnya adalah men-deploy project ke dalam heroku server. Sebelum men-deploy project, commit seluruh perubahan yang telah dilakukan pada langkah di atas. Kemudian deploy project dengan perintah berikut:

$ git push heroku master

Proses deploy akan memakan waktu, juga tergantung koneksi internet yang dimiliki. Jika proses deploy berjalan tanpa pesan error, artinya project berhasil di deploy hingga dan dapat diakses di server heroku.

BEBERAPA KONDISI DAN PROBLEM

Terdapat beberapa permasalahan umum yang terjadi pada proses deploy project phoenix ke server heroku. Berikut permasalahannya dan pemecahannya:

 

ERLANG TIDAK DAPAT DIINSTAL

Terkadang Heroku gagal dalam melakukan installasi erlang bisa jadi karena versi Erlang yang tidak support atau kesalahan penulisan version. Jika versi Erlang memang tidak di support, kita harus mengubah versi Erlang yang di support oleh Heroku. Untuk kesalahan penulisan sering terjadi karena kita menuliskan versi Erlang seperti berikut:

erlang_version=19
elixir_version=1.3.4

harusnya ditulis seperti berikut:

erlang_version=19.0
elixir_version=1.3.4

 

MIGRASI DATABASE

Mengelola database Heroku tidak semudah mengelola database pada komputer lokal karena Heroku tidak menyediakan fitur untuk mengelola database (untuk versi gratisan) seperti menambah tabel, manambah kolom, insert data, dll. Satu-satunya cara mendefinisikan tabel2 tersebut ke dalam database adalah menjalankan perintah database pada aplikasi. Yah… phoenix punya fitur migrate untuk mengelola schema database. Kita cukup menjalankan perintah migrate database dengan perintah berikut:

migrate database: heroku run "POOL_SIZE=2 mix ecto.migrate"

 

Selesai.. itulah langkah men-deploy project phoenix ke Heroku.. Semoga berhasil, happy coding…

Phoenix Elixir – Using System.get_env()

Jika pada suatu kasus anda tidak ingin menyimpan sebuah informasi rahasia langsung pada kode anda (ex: API key, client secret, dll), maka anda dapat menyimpan variabel tersebut pada environment variabel. Variabel ini akan disimpan pada environment anda dan dapat digunakan oleh aplikasi dengan cara seperti berikut :

System.get_env("MY_SECRET")

Bagaimana cara mendefinisikan variabel “MY_SECRET” pada environment anda?

Buat sebuah file “.env” pada root aplikasi, dan masukkan code berikut untuk mengatur nilai variabel:

export MY_SECRET="i_love_you"
export HER_SECRET="she_dont_love_me"

dari contoh di atas kita mendefinisikan variabel “MY_SECRET” dan “HER_SECRET” pada environment kita. Kemudian jalankan perintah “source .env” pada terminal kemudian aktifkan server anda. Anda sudah dapat memanggil variabel tersebut pada aplikasi anda.

Happy coding.. 😀

Xamarin iOS – Custom UIAlertAction Style With SetValueForKey

UIAlertAction merupakan komponen list pada UIAlertController dimana ditampilkan dalam bentuk text. Style pada  UIAlertAction dapat dimodifikasi dengan cara berikut :


     UIAlertAction temp = UIAlertAction.Create(head, UIAlertActionStyle.Default, alert => { /* your function here */ }); 

     temp.SetValueForKey(UIColor.Black, (NSString)"titleTextColor");
     temp.SetValueForKey(UIImage.FromBundle("your image path"), (NSString)"image"); 
     listAlertController.AddAction(temp);

Pada contoh di atas kita membuat sebuah UIAlertAction yang di definisikan pada variabel “temp”. Modifikasi style pada variabel temp dilakukan dengan “SetValueForKey”, dimana kita akan menyisipkan property suatu nilai berdasarkan nama property nya (pastikan property yang di set memiliki nama yang benar, kesalahan nama akan menyebabkan error). Untuk kasus di atas kita melakukan custom property “titleTextColor” untuk warna dari label dan “image” untuk menambahkan icon pada label.

 

Sekian, happy coding…

Phoenix Elixir – Using Preload to Call Relation Data

Phoenix Elixir – Using Preload to call Relation Data

Phoenix Framework menyediakan sebuah fitur untuk memudahkan developer menampilkan field-field pada tabel yang berelasi dengan suatu tabel tanpa harus melakukan query yang berulang. Sebagai contoh terdapat tabel “mothers” dan tabel “children” dimana setiap data pada tabel “children” akan memiliki field “mother_id” yang merupakan foreign_key dari tabel “mothers”. Berikut adalah class model mothers :

defmodule App.Mother do
  use App.Web, :model

  schema "mother" do
    field :name, :string
  end
end

Dan berikut class dari tabel “Children” :

defmodule App.Children do
  use App.Web, :model

  schema "children" do
    field :label, :string
    field :mother_id, :integer
  end
end

Saya asumsikan kita ingin menampilkan semua data dari tabel “Children”, berikut query yang kita gunakan :

 query = from u in Children,
                select: u

 datas = Repo.all(query)

Variabel “datas” akan menyimpan semau data dari tabel “children” dengan field “label” dan “mother_id”. Pengguna tidak akan tahu apa arti dari “mother_id” karena data yang ditampilkan berupa “id” bukan nama. Untuk mengatasi masalah ini, phoenix menyediakan fitur untuk memanggil field “name” dari tabel mother. Berikut langkah-langkahnya :

Pada class “Children” lakukan modifikasi pada schema sebagai berikut:

  schema "children" do
    field :label, :string
    belongs_to :mother, App.Mother
  end

Code di atas akan mendefinisikan relation pada tabel children yang mengarah pada tabel mother. Relation bernama “:mother” akan membuat Phoenix secara otomatis membaca field yang be-relasi (foreign key) adalah field “mother_id” (field dengan tambahan “_id” dari nama relasi). Untuk itu pastikan nama field anda sesuai dengan nama relation ditambah “_id”.

Berikut cara memanggil seluruh data children beserta data dari tabel relasinya :

    query = from u in Children,
                select: u

    datas = Repo.all(query)
          |> Repo.preload(:mother)

Bagaimana jika kita memiliki sebuah database yang telah memasuki tahap production sehingga tidak memungkinkan mengikuti nama field sesuai kaedah dari Phoenix? Jangan khawatir, Phoenix telah menyediakan sebuah mekanisme yang memungkinkan anda bebas melakukan pengaturan dengan relation anda. Sebagai contoh tabel “Children” memiliki field relation dengan nama “id_mother”, maka buatlah schema anda seperti berikut :

  schema "children" do
    field :label, :string
    field :id_mother, :integer
    belongs_to :mother, App.Mother, define_field: false, foreign_key: :id_mother
  end

Anda juga dapat mendefinisikan lebih dari satu field relasi pada suatu schema. Sebagai contoh:

  schema "children" do
    field :label, :string
    field :id_mother, :integer
    belongs_to :mother, App.Mother, define_field: false, foreign_key: :id_mother
    belongs_to :another_relation, App.AnotherRelation
  end

Kedua relasi tersebut dapat dipanggil dengan cara berikut :

   query = from u in Children,
                select: u

    datas = Repo.all(query)
          |> Repo.preload([:mother, :another_relation])

Itulah semua yang perlu anda lakukan untuk mengatur relasi tabel anda… Selesai, selamat mencoba..

Happy coding..

Phoenix Elixir – Make Authenticate Page (Register, Login, Logout)

Phoenix Elixir – Make Authenticate Page (Register, Login, Logout)

Postingan kali ini akan memaparkan bagaimana membuat fitur autentikasi pada Phoenix Framework yang terdiri dari fitur register, login dan logout. Sebelum kita lanjutkan ke tahapan membuat proses register, saya asumsikan anda sudah memahami cara membuat fitur CRUD pada Phoenix Framework (bisa dilihat di https://sabitlabscode.wordpress.com/2017/01/04/phoenix-elixir-make-crud-with-postgresql/ bagi anda yang belum paham). Proses register dimulai dengan men-generate CRUD tabel user dengan field tabel sebagai berikut : username , name , password dan email. Semua field yang dibuat memiliki tipe string kecuali field id dan timestamps() yang dibentuk otomatis oleh Phoenix. Setelah melakukan generate CRUD pada tabel user, pastikan seluruh fungsi CRUD berjalan dengan baik. Untuk mempersingkat maka proses “register” yang dibuat adalah proses action “new” dan “create” pada UserController (anda bisa membuat fungsi register tersendiri untuk memperindah managemen code anda), ke depannya kita akan arahkan tautan register ke action tersebut.

Guna meningkatkan security pada proses autentikasi, kita harus melakukan beberapa modifikasi berikut pada proses register yang dibuat sebelumnya:

  • enkripsi pada field password. Setiap user yang melakukan proses register, field password yang diisi oleh user harus di enkripsi kemudian disimpan ke dalam database.
  • User harus mengisi password sebanyak 2 kali (password dan password confirmation) untuk memastikan password yang dimasukkan benar.

Untuk menambahkan password confirmation, kita harus menambahkan sebuah field pada model “User” dan menandakan field tersebut menjadi sebuah field virtual yang sebenarnya tidak ada pada tabel. Berikut schema pada model User setelah ditambahkan field password confirmation:

schema "users" do
    field :username, :string
    field :name, :string
    field :password, :string
    field :email, :string
    field :password_confirmation, :string, virtual: true

    timestamps()
  end

Tambahkan juga field tersebut pada form.html.eex. Ketika pengguna menginput data user ke dalam form, maka aplikasi harus memastikan bahwa antara field password dan password_confirmation harus sama. Masukkan validasi berikut pada model User untuk melakukan pengecekan tersebut :

 @required_fields ~w(username name email password password_confirmation)
  @optional_fields ~w()
  @doc """
  Builds a changeset based on the `struct` and `params`.
  """

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @required_fields)
    |> validate_required([:username, :name, :password, :password_confirmation, :email])
    |> validate_format(:email, ~r/@/, message: "Please fix your email format")
    |> validate_confirmation(:password)
  end

Validasi “validate_confirmation” akan mengecek kesamaan field parameter dengan field yang bernama parameter+”_confirmation” dalam hal ini “password” dan “password_confirmation.

Ingat, ketika melakukan proses penyimpanan maka aplikasi akan melakukan enkripsi field password. Buatlah sebuah fungsi untuk melakukan enkripsi pada model User :

  def generate_password_and_store_user(changeset) do
    put_change(changeset, :password, hashpwsalt(changeset.params["password"]))
      |> Repo.insert
  end

Fungsi di atas dapat dipanggil dengan parameter sebuah “changeset”. Nilai dari changeset tersebut akan memodifikasi changeset dengan parameter “password” dimana nilainya diganti dengan nilai hasil enkripsi. Setelah merubah nilai pada parameter “password” dilakukan proses insert data ke database.

Import Comeonin agar dapat menggunakan fungsi “hashpwsalt” pada proses enkripsi :

 import Comeonin.Bcrypt, only: [hashpwsalt: 1]

Pada UserController action create, masukkan code berikut :

 def create(conn, %{"user" => user_params}) do
    changeset = User.changeset(%User{}, user_params)
    if changeset.valid? do
      new_user = User.generate_password_and_store_user(changeset)

      conn
        |> put_flash(:info, "Successfully registered and logged in")
        |> redirect(to: user_path(conn, :index))
    else
      render conn, "new.html", changeset: changeset
    end
  end

Yang terjadi pada code di atas adalah :

  • mendefinisikan variabel “changeset” yang di dapat dari post form “new” dan dibundle ke dalam User changeset
  • jika variabel changeset sudah memenuhi validasi pada model User, maka akan memanggil fungsi “generate_password_and_store_user pada model User. Fungsi ini akan memodifikasi field password untuk kemudian disimpan ke dalam database.

Proses register telah dapat anda jalankan..

LOGIN dan LOGOUT

Proses login dan logout akan kita letakkan pada sebuah controller terpisah. Buatlah sebuah controller dengan nama AuthController. Di dalam controller ini akan terdapat fungsi “login”, “authenticate” dan “logout”. Berikut code pada AuthController :

defmodule MyApp.AuthController do
  use FotoKerja.Web, :controller

  def login(conn, _params) do
    render(conn, "login.html")
  end

  def authenticate(conn, %{"user" => user_params}) do
    case MyApp.Auth.login(user_params, FotoKerja.Repo) do
      {:ok, user} ->
        conn
        |> put_session(:current_user, user.id)
        |> put_flash(:info, "Logged in")
        |> redirect(to: "/")
      :error ->
        conn
        |> put_flash(:info, "Wrong email or password")
        |> render("login.html")
    end
  end

  def logout(conn, _) do
    conn
      |> delete_session(:current_user)
      |> put_flash(:info, "Logged out")
      |> redirect(to: "/")
  end

end

Action “login” hanya akan menampilkan sebuah form “login.html”, form ini berisi field username dan password untuk proses login. Action “authenticate” akan bertindak sebagai “post” pada action login dimana disini akan mengecek inputan login user dari fungsi Auth.login yang akan kita buat nanti. Sedangkan fungsi logout akan menghapus session “:current_user” yang artinya menghapus session user yang sedang login.

Untuk kebutuhan view pada proses login, buatlah sebuah file “web/views/auth_view.ex” dengan code berikut :

defmodule FotoKerja.AuthView do
  use FotoKerja.Web, :view
end

Dan file “web/templates/auth/login.html.eex” yang berisi text input username dan password. Untuk username gunakan text_input dan password gunakan password_input.

Tambahkan file “web/models/auth.ex”, file ini akan melakukan proses login. Masukkan code berikut :

defmodule MyApp.Auth do
   alias MyApp.User

  def login(params, repo) do
    user = repo.get_by(User, username: String.downcase(params["username"]))
    case authenticate(user, params["password"]) do
      true -> {:ok, user}
      _    -> :error
    end
  end

  defp authenticate(user, password) do
    case user do
      nil -> false
      _   -> Comeonin.Bcrypt.checkpw(password, user.password)
    end
  end

  def current_user(conn) do
    id = Plug.Conn.get_session(conn, :current_user)
    if id, do: FotoKerja.Repo.get(User, id)
  end

  def logged_in?(conn), do: !!current_user(conn)
end

Code di atas terdiri dari beberapa fungsi, berikut penjelasan masing-masing fungsi :

  • authenticate : fungsi ini akan mengecek apakah password seorang user sudah sama dengan password yang diinput oleh user yang mencoba login. Tentunya pengecekan password akan menggunakan fungsi dari Comeonin untuk matching dengan nilai yang di enkripsi.
  • login : fungsi authenticate akan dipanggil pada fungsi ini, fungsi login pertama-tama akan memanggil data user yang meiliki “username” yang sama seperti nilai username yang diinput pada proses login. Jika data username tersebut ada pada database, barulah fungsi “authenticate” dipanggil untuk mengecek password.
  • current_user : akan mengembalikan data user yang sedang login
  • logged_in : mengecek apakah orang yang mengakses aplikasi dalam kondisi login atau tidak

Kembali pada “UserController” pada action authenticate yang memanggil Auth.login untuk proses autentikasi. Seperti dapat anda lihat bahwa fungsi Auth.login akan mengecek kebenaran “username” kemudian memanggil “authenticate” untuk mengecek kebenaran “password”.

Informasi tentang user yang telah melakukan proses login akan disimpan ke dalam session “:curent_user”. Dapat dilhat pada “UserController” ketika proses ketika proses login berhasil dilakukan, akan menjalankan perintah berikut :

        |> put_session(:current_user, user.id)

Perintah di atas akan menyimpan data session “:current_user” ke dalam data dengan data “user.id”. Dan ketika melihat AuthController action “logout”, kita menemukan code untuk menghapus nilai session :current_user seperti berikut :

      |> delete_session(:current_user)

Selanjutnya kita akan membuat sebuah tautan dengan kondisi sebagai berikut :

  • Jika user belum login, akan menampilkan tautan untuk login dan register
  • Jika user telah login, akan menampilkan email user dan tautan untuk logout.
  • Kita akan meletakkan tautan tersebut pada bagian atas/header aplikasi

Masukkan code berikut pada “web/templates/layout/app.html.eex” :

           <%= if logged_in?(@conn) do %>
              <li><%= current_user(@conn).email %></li>
              <li><%= link "Logout", to: auth_path(@conn, :logout), method: :delete %></li>
            <% else %>
              <li><%= link "Login",    to: "/login" %></li>
              <li><%= link "Register", to: user_path(@conn, :new) %></li>
            <% end %>

Kita memanggil fungsi logged_in dan current_user yang ada pada model Auth. Agar template di atas dapat menggunakan kedua fungsi tersebut, kita harus melakukan import pada file “web/web.ex” tepatnya di dalam fungsi “view” seperti berikut :

      import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

Setelah modifikasi di atas, silahkan jalankan aplikasi.. anda dapat melakukan proses login seperti berikut :

jan-11-2017-02-13-24

Thanks to :

http://nithinbekal.com/posts/phoenix-authentication/

http://meatherly.github.io/2015/05/11/phoenixauthentication/

Happy coding…

Xamarin iOS – Dynamic UITabBarController

Xamarin iOS – Dynamic UITabBarController

Untuk kebutuhan yang lebih spesifik, kita dapat membuat kumpulan UIViewController pada UITabBarController secara dinamis. Tahapannya adalah :

  • Tambahkan sebuah UIViewController (xib file) yang akan digunakan secara dinamis pada UITabBarController. Misal dalam contoh ini saya akan membuat sebuah file UIViewController dengan nama “DynamicController”
  • Pada UITabBarController, tambahkan sebuah button atau UITBarButtonItem yang digunakan untuk memberi perintah menambahkan DynamicController ke dalam UITabBar
  • Masukkan code berikut untuk menambah bar dengan DynamicController
 List<UIViewController> temp_list = new List<UIViewController>(this.ViewControllers);
 UIViewController new_tab = new DynamicController();
 new_tab.TabBarItem.Title = "Item #" + (temp_list.Count + 1).ToString();
 temp_list.Add(new_tab);

 this.ViewControllers = temp_list.ToArray();

Yang terjadi pada code di atas adalah :

  • Mendefinisikan variabel temp_list dalam bentuk list dan kita masukkan semua array pada this.ViewControllers (array UIViewController yang sudah ada pada BarItem
  • Mendefinisikan variabel “new_tab” yang berisi UIViewController yang akan ditambahkan
  • Konfigurasi judul TabBar untuk UIViewController “new_tab”
  • Menambahakan list “temp_list” dengan UIViewController baru
  • Atur ulang nilai this.ViewControllers

Source saya ada di : https://github.com/sabithuraira/xamarin_ios_playground/commit/3c5ea649474c69192f264461667082f267e6878b

Happy coding…

Phoenix Elixir – About Ecto

Phoenix Elixir – About Ecto

Ecto adalah sebuah source yang digunakan agar aplikasi dapat terhubung ke database pada bahasa pemrograman Elixir, dan Phoenix Framework juga menggunakan Ecto untuk komunikasi antara database dan aplikasi. Jika anda mengikuti panduan create aplikasi Phoenix baru pada tautan berikut http://www.phoenixframework.org/docs/up-and-running, maka Ecto secara otomatis terinstall pada aplikasi anda.

Secara default Ecto pada Phoenix akan menggunakan database postgreSQL, jika ingin menggunakan database lain harus dilakukan beberapa konfigurasi terlebih dahulu. Pendefenisian Ecto pada aplikasi terdapat pada file “web/mix.exs” pada code :

 def application do
    [mod: {FirstApp, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex]]
 end

 defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"}]
  end

Pada konfigurasi di atas dapat dilihat telah didefinisikan phoenix_ecto dan postgrex sebagai database. Sedangkan untuk menghubungkan aplikasi dengan suatu database postgreSQL lakukan konfigurasi pada file “config/dev.exs” tepatnya pada code berikut :

# Configure your database
config :first_app, FirstApp.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "first_app_dev",
  hostname: "localhost",
  pool_size: 10

Code diatas mendefinisikan identitas database yang akan digunakan mulai dari username dan password, nama database dan attribute lainnya.

MIGRATION

Ecto memungkinkan anda membuat tabel/schema melalui fitur Migration (Ecto.Migration). Sebagai contoh kita akan membuat sebuah tabel “users” pada database. Lakukan pendefinisian file migration pada “priv/repo/migrations” misalnya file “create_user.exs”. Masukkan code berikut :

defmodule FirstApp.Repo.Migrations.CreateUser do
  use Ecto.Migration
  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :birth, :date
      add :age, :integer
      timestamps()
    end
  end
end

File Ecto.Migration di atas akan membuat table users dengan field name, email, birth dan age dengan tipe data yang telah tertera pada masing-masing field. Tabel users juga akan menambahkan field inserted_at dan updated_at yang sudah dimasukkan melalui fungsi “timestamps()”. Untuk menjalankan perintah membuat schema database pada file migration, jalankan perintah berikut pada terminal.

$ mix ecto.migrate

Setelah menjalankan perintah tersebut tabel users akan terbentuk pada database anda dengan tambahan “id” sebagai primary key (id ditambah otomatis oleh Ecto).

MODEL

Komponen MODEL pada MVC Phoenix juga menggunakan Ecto. Model sendiri adalah representasi data pada database termasuk kumpulan field, tipe data, validasi, dll. Berikut adalah contoh class model yang didapat dari tabel User :

defmodule FirstApp.User do
  use FirstApp.Web, :model

  schema "users" do
    field :name, :string
    field :email, :string
    field :birth, Ecto.Date
    field :age, :integer
    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :birth, :age])
    |> validate_required([:name, :email, :birth, :age])
    |> validate_length(:name, min: 3)
    |> validate_length(:email, min: 5)
    |> validate_format(:email, ~r/@/, message: "Please fix your email format")
  end
end

Pada contoh di atas kita dapat melihat schema user yang terdiri dari beberapa field beserta jenis data masing-masing field. Model juga memiliki changeset yang digunakan untuk meletakkan validasi, mengatur pesan error, mendefinisikan model pada form, mendefinisikan data untuk proses update/insert, dll.

MENAMPILKAN DATA

Ecto menggunakan “Repo” untuk komunikasi data antara aplikasi dan database. Anda dapat menggunakan Ecto query atau Ecto model untuk mengambil data pada database yang kemudian dapat digunakan pada aplikasi. Untuk menampilkan seluruh data pada tabel “users” ggunakan Repo dan Model dengan code seperti berikut :

    Repo.all(User) 

User merupakan class model User.

Menampilkan suatu data berdasarkan id/primary key tertentu gunakan code berikut :

    Repo.get(User, id)

atau gunakan code berikut untuk menjalankan peirntah yang sama tetapi menampilkan pesan error saat data tidak ditemukan :

    Repo.get!(User, id)

Untuk menampilkan data yang memiliki nilai attribute tertentu :

 Repo.get_by(User, name: “John")

atau gunakan code berikut untuk menjalankan peirntah yang sama tetapi menampilkan pesan error saat data tidak ditemukan :

 Repo.get_by!(User, name: “John”)

Selain memadukan Repo dan Model, kita dapat memadukan Repo dan Query guna mendapatkan data dengan query yang lebih kompleks. Berikut contoh codenya :

 query = from u in User,
                where: u.age == 26,
                select: u

 users = Repo.all(query)

Code di atas akan menampilkan data yang memiliki field age = 26.

MODIFIKASI DATA

Ecto Repo juga menyediakan fitur untuk melakukan proses modifikasi data (update, insert, delete). Proses insert data dapat dilakukan dengan code berikut :

      case Repo.insert %User{name: “Jhon”,age: “26”,birth: “1989-01-01”,email: “jhon@email.com”} do
        {:ok, struct}       -> # Insert something for success process
        {:error, changeset} -> # Insert something for wrong process
      end

Anda dapat melakukan insert data dengan mengambil langsung variabel dari changeset pada model :

    changeset = User.changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, _user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end

Untuk proses update data gunakan Repo.update :

    user = Repo.get!(User, id)
    changeset = User.changeset(user, user_params)

    case Repo.update(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User updated successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end

Dan untuk delete gunakan Repo.delete :

    user = Repo.get!(User, id)
    Repo.delete!(user)

Selesai, itulah sedikit pembahasan tentang Ecto.. Happy coding..

Phoenix Elixir – Make CRUD with PostgreSQL

Phoenix Elixir – Make CRUD with PostgreSQL

Sebelumnya perkenalkan Phoenix Framework.. adalah framework yang dibangun di atas bahasa pemrograman Elixir, lebih lengkap tentang Elixir dapat dilihat di http://elixir-lang.org/ dan Phoenix dapat dilihat di http://www.phoenixframework.org/. Code Elixir sendiri akan di compile ke Erlang byte code dan berjalan di atas Erlang VM, karena itu kita harus memiliki Erlang untuk menjalankan Elixir.

Proses installasi Phoenix Framework dapat dilihat pada tautan berikut http://www.phoenixframework.org/docs/installation , tautan tersebut akan memaparkan proses installasi Phoenix, Elixir, Erlang dan tools lain yang dibutuhkan untuk menjalankan Phoenix Framework.

Pada postingan ini saya tidak memaparkan bagaimana proses installasi dan proses membuat project baru pada Phoenix Framework (saya asumsikan sudah bisa) tapi jika anda masih belum tahu cara membuat project baru pada Phoenix, silahkan lihat pada tautan berikut http://www.phoenixframework.org/docs/up-and-running.

Setelah membuat project baru kita akan coba memahami bagaimana membuat operasi CRUD pada Phoenix. Saya akan menggunakan database postgreSQL (karena secara default di support Phoenix dan update terbaru dengan fitur yang bagus). Phoenix menyediakan fitur generate operasi CRUD dimana dapat menghemat waktu kita dalam mengembangkan aplikasi. Pada postingan ini kita akan generate CRUD tabel “users” (tabel “users” belum dibuat pada database postgreSQL, phoenix akan melakukannya).

Masukkan perintah berikut :

$ mix phoenix.gen.html User users name:string email:string birth:date age:integer
* creating web/controllers/user_controller.ex
* creating web/templates/user/edit.html.eex
* creating web/templates/user/form.html.eex
* creating web/templates/user/index.html.eex
* creating web/templates/user/new.html.eex
* creating web/templates/user/show.html.eex
* creating web/views/user_view.ex
* creating test/controllers/user_controller_test.exs
* creating web/models/user.ex
* creating test/models/user_test.exs
* creating priv/repo/migrations/20161224162452_create_user.exs

Add the resource to your browser scope in web/router.ex:
    resources "/users", UserController

Remember to update your repository by running migrations:
    $ mix ecto.migrate

Perintah di atas mendefinisikan data untuk tabel “users” yang akan disimpan dalam model “User”. Pada perintah kita harus mendefinisikan field yang didefinisikan pada tabel users beserta tipe datanya. Perintah di atas akan men-generate file eex elixir yang digunakan untuk operasi CRUD. Apakah tabel users pada database sudah dibuat? Belum.. kita akan memasukkan perintah lain untuk melakukannya..

Lakukan modifkasi pada web/route.ex :

scope "/", FirstApp do
    pipe_through :browser # Use the default browser stack
    get "/", PageController, :index
    resources "/users", UserController
  end

Setelah memodifikasi code pada web/route.ex masukkan perintah berikut pada terminal :

mix ecto.migrate
Compiling 11 files (.ex)
Generated first_app app
23:28:53.583 [info]  == Running FirstApp.Repo.Migrations.CreateUser.change/0 forward
23:28:53.583 [info]  create table users
23:28:53.616 [info]  == Migrated in 0.0s

Perintah di atas akan menjalankan fungsi ecto.migrate. Perintah tersebut akan menjalankan semua file yang ada pada direktori “priv/repo/migrations” dimana migrations yang dilakukan berkaitan dengan database. Saat ini kita memiliki sebuah file pada “priv/repo/migrations” yaitu file yang di generate dari proses sebelumnya. Perintah di atas secara otomatis akan membuat sebuah tabel pada database anda dengan schema seperti berikut :

screen-shot-2016-12-24-at-11-29-29-pm

Pada perintah sebelumnya kita hanya membuat kolom name, email, birth dan age. Phoenix secara otomatis akan menambahakan kolom id sebagai primary key dan kolom inserted_at dan updated_at dari fungsi timestamp yang dimiliki phoenix.

Setelah tabel “users” dibuat pada database, barulah kita dapat menjalankan proses CRUD yang telah di generate sebelumnya. Jalankan perintah berikut pada terminal :

$ mix phoenix.server

Masukkan url berikut : http://localhost:4000/users , akan tampil halaman pengelolaan data “users” seperti berikut :

screen-shot-2016-12-25-at-12-27-03-am

Jika memilih menu “New user” aplikasi akan mengarahkan anda pada halaman untuk menambah data user baru :

screen-shot-2016-12-25-at-12-27-11-am

Khusus input data dengan type “date” phoenix secara otomatis membuat form input berupa dropdown dengan pilihan tahun, bulan dan tanggal. Form yang digenerate juga memiliki validasi sebagai contoh jika anda klik “Submit” tanpa mengisi data akan muncul halaman dengan tampilan error seperti berikut :

screen-shot-2016-12-25-at-1-11-05-am

Berikut tampilan halaman user ketika sudah menambah data baru :

screen-shot-2016-12-25-at-12-27-59-am

Pada halaman tersebut disediakan fitur untuk melakukan show, edit dan delete data. Silahkan mencoba masing-masing fitur..

Bekerja dengan framework yang memiliki fitur generate sering kali membuat programmer pemula jadi tidak memahami code hasil proses generate yang dilakukan. Untuk itu mari kita pahami code yang ada di balik proses generate tersebut.

Phoenix Framework menggunakan konsep MVC (Model – View – Controller) yang artinya kebanyakan proses pada Phoenix Framework akan berputar-putar pada ketiga komponen tersebut (bagi anda yang belum memahami apa itu MVC silahkan googling ya…). Proses generate yang dilakukan oleh Phoenix akan membuat beberapa file pada folder:

  • web/controllers/ (controller)
  • web/models (models)
  • web/templates/user (views)
  • web/views
  • test/controllers
  • test/models
  • priv/repo/migrations

Pada priv/repo/migrations kita akan melihat file migration createuser :

defmodule FirstApp.Repo.Migrations.CreateUser do
  use Ecto.Migration
  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :birth, :date
      add :age, :integer
      timestamps()
    end
  end
end

Pada code di atas dapat dilihat bahwa migration akan membuat sebuah tabel user dengan kolom yang sudah kita definisikan pada terminal ditambah dengan “timestamps()” yang terdiri dari kolom inserted_at dan updated_at.

MODEL

Model digunakan untuk mendefinisikan data pada suatu tabel dalam database termasuk jenis data, validasi, query, dll. Proses generate oleh phoenix akan meletakkan file model dalam folder “web/models” pada kasus ini pada file “web/models/user.ex”. Code dalam model user adalah seperti berikut :

defmodule FirstApp.User do
  use FirstApp.Web, :model

  schema "users" do
    field :name, :string
    field :email, :string
    field :birth, Ecto.Date
    field :age, :integer
    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :birth, :age])
    |> validate_required([:name, :email, :birth, :age])
  end
end

Code di atas merupakan class FirstApp.User merupakan sebuah model. Pada class tersebut terdiri dari definisi schema model Users yang mendefinisikan nama field dan type data masing-masing field. Field “inserted_at” dan “updated_at” didefinisikan pada fungsi “timestamps()”.

Model juga mendefinisikan changeset, changeset ke depannya akan mendefinisikan data yang digunakan untuk memanipulasi suatu data. Changeset pada code di atas terdiri dari “cast” yang mendefinisikan field yang akan menjadi parameter pada proses modifikasi data dan sebagai daftar field yang akan di update (cast hanya dapat berisi nama field yang telah didefinisikan pada schema. Changeset di atas juga terdiri dari “validate_required” yang berisi kumpulan field yang wajib diisi ketika proses modifikasi.

CONTROLLER DAN VIEW

Controller adalah komponen yang melakukan suatu action dan menterjemahkan request suatu user. Ketika user melakukan request suatu action maka controller akan menterjemahkan permintaan user dan menentukan UI (biasanya memanggil suatu view) dan data (diambil dari model) yang akan ditampilkan kepada user. File controller yang digenerate oleh phoenix ada pada “web/controller/user_controller.ex” sedangkan views ada pada folder “web/templates/user” dan “web/views/user_view.ex”. Baiklah pertama-tama kita akan memahami bagaimana url http://localhost:4000/users yang kita akses akan menampilkan kita halaman daftar user yang sudah ada pada database.

Ketika kita melakukan request suatu url, aplikasi akan menangkap request url dari file “web/router.ex” tepatnya pada code :

 scope "/", FirstApp do
    pipe_through :browser # Use the default browser stack
    get "/", PageController, :index
    resources "/users", UserController
  end

Code di atas telah mendefinisikan seluruh resources yang ada pada controller user agar dapat diakses dengan url “/users”, adapaun jenis fungsi yang dapat dijalankan pada url mengikuti nama masing-masing fungsi dalam UserController.

FUNGSI INDEX

Mengakses url http://localhost:4000/users secara otomatis akan mengarahkan anda mengakses fungsi “index” pada UserController. Perintah yang dijalankan adalah : 

def index(conn, _params) do
    users = Repo.all(User)
    render(conn, "index.html", users: users)
  end

Yang terjadi pada code di atas adalah :

  • Mendefinisikan variabel users yang diisi dengan Repo.all(User), fungsi Repo.all(User) akan menampilkan seluruh data yang ada pada tabel User. UserController telah mendefinisikan code “  alias FirstApp.User” yang artinya kita tidak perlu memanggil model User dengan memanggil FirstApp.User tetapi cukup “User” saja.
  • Me-render halaman index.html (pada folder “web/template/users”) dan mengirimkan variabel “users” dengan nama users ke halaman yang di-render.

Halaman index.html yang di-render pada fungsi index adalah halaman index.html pada direktori “web/template/users/“ dimana memiliki code sebagai berikut :

<h2>Listing users</h2>
<table class="table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Birth</th>
      <th>Age</th>
      <th></th>
    </tr>
  </thead>

  <tbody>
<%= for user <- @users do %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.email %></td>
      <td><%= user.birth %></td>
      <td><%= user.age %></td>
      <td class="text-right">
        <%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %>
        <%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %>
        <%= link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
      </td>
    </tr>
<% end %>

  </tbody>
</table>
<%= link "New user", to: user_path(@conn, :new) %>

Code di atas akan menampilkan sebuah tabel dengan head Name, Email, Birth, Age dan sebuah head tanpa label. Isi dari tabel tersebut diisi dengan melakukan looping data user yang menampilkan field name, email, birth dan age. Sedangkan satu kolom lagi akan diisi dengan tombol Show, Edit dan Delete untuk masing-masing data.

MENAMBAH DATA BARU

Untuk menambah data baru kita akan melibatkan dua buah fungsi pada controller yaitu “new” (menampilkan form input data) dan “create” (memproses data yang telah diinput pada form). Code fungsi “new” adalah sebagai berikut :

  def new(conn, _params) do
    changeset = User.changeset(%User{})
    render(conn, "new.html", changeset: changeset)
  end

Yang terjadi pada code di atas adalah :

  • mendefinisikan variabel “changeset” dengan changeset pada model User dengan kondisi seluruh field yang masih kosong.
  • me-render halaman “new.html” dan mengirimkan variabel changeset.

Code “new.html” adalah sebagai berikut :

<h2>New user</h2>
<%= render "form.html", changeset: @changeset,
                        action: user_path(@conn, :create) %>
<%= link "Back", to: user_path(@conn, :index) %>

Code new.html me-render kembali halaman “form.html” (halaman ini juga digunakan ulang pada halaman update) dan mengirim variabel changeset yang berasal dari controller sebelumnya dan mengirimkan variabel “action” yang merupakan url dari fungsi “create” pada UserController. Silahkan lihat code “form.html.eex” anda. Code tersebut berupa form yang terdiri dari beberapa macam input data. Ketika tombol “Submit” dipilih, maka form di atas akan menjalankan fungsi “create” pada UserController. Fungsi “create” sendiri akan menjalankan code berikut :

  def create(conn, %{"user" => user_params}) do
    changeset = User.changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, _user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

Yang terjadi pada code di atas adalah :

  • “create” akan menangkap inputan dari form “new.html” melalui parameter “user”
  • mendefinisikan variabel “changeset” yang diisi oleh changeset pada model User dan field pada changeset akan diisi oleh parameter “user_params” yang didapat dari inputan pada form.
  • dilakukan proses input data changeset ke dalam database.
  • Jika data berhasil disimpan ke dalam database maka fungsi akan menampilkan pesan sukses (fungsi put_flash digunakan untuk menampilkan suatu pesan pada tampilan layout) dan me-redirect ke halaman index.
  • Jika data gagal disimpan maka aplikasi akan me-render halaman “new.html” kembali beserta changeset yang telah di definisikan sebelumnya. Changeset yang dikirim juga akan membawa serta pesan error yang terjadi pada inputan sebelumnya sehingga penguin dapat melihat validasi yang gagal dipenuhi ketika proses insert data.

MENAMPILKAN DETAIL DATA

Fungsi untuk menampilkan detail data ada pada code :

def show(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    render(conn, "show.html", user: user)
  end

Dimana code di atas akan melakukan tahapan sebagai berikut :

  • Mendefinisikan variabel user yang diisi dengan data dari tabel user yang memiliki primary key atau id sesuai dengan parameter yang di dapat dari url. Fungsi “Repo.get!(User,id)”  artinya data akan diambil melalui model User.
  • Me-render halaman “show.html” dan mengirimkan variabel user yang telah didefinisikan sebelumnya.

Halaman show.html akan menampilkan data kepada user dengan code sebagai berikut :

<h2>Show user</h2>
<ul>
  <li>
    <strong>Name:</strong>
    <%= @user.name %>
  </li>

  <li>
    <strong>Email:</strong>
    <%= @user.email %>
  </li>

  <li>
    <strong>Birth:</strong>
    <%= @user.birth %>
  </li>

  <li>
    <strong>Age:</strong>
    <%= @user.age %>
  </li>
</ul>

<%= link "Edit", to: user_path(@conn, :edit, @user) %>
<%= link "Back", to: user_path(@conn, :index) %>

Code di atas menampilkan masing-masing field pada tabel user ke dalam tag HTML yang telah disusun. Pada HTML tersebut juga ditampilkan tautan untuk melakukan fungsi edit dan fungsi back ke halaman index.

MEMPERBAHARUI DATA

Proses memperbaharui/update data akan ditampilkan pada fungsi “edit” dan “update. Cara kerja kedua fungsi ini sama dengan fungsi “new” dan “create” pada saat kita ingin menambahkan data baru. Perbedaan pada kedua proses ini adalah fungsi “edit” yang mendefinisikan changeset dengan mengambil data dari tabel user dengan id/primary key tertentu :

 def edit(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    changeset = User.changeset(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

Pada code tersebut changeset akan mendefinisikan changeset dari model User dimana isi dari changeset akan mengambil data pada tabel user yang memiliki id tertentu.

MENGHAPUS DATA

Perintah delete data akan menjalankan code berikut :

def delete(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).

    Repo.delete!(user)
    conn
    |> put_flash(:info, "User deleted successfully.")
    |> redirect(to: user_path(conn, :index))
  end

Code tersebut akan mendefinisikan variabel user yang berisi data user yang memiliki id sesuai request. Data user akan digunakan untuk perintah hapus data dimana setelah proses hapus data berjalan aplikasi akan menampilkan pesan sukses hapus data dan me-redirect pengguna ke halaman index awal.

MODIFIKASI APLIKASI

PENYEMPURNAAN FORM

Code yang telah di generate oleh Phoenix tidak selalu dapat memenuhi kebutuhan kita. Untuk itu diperlukan proses modifikasi code secara manual oleh programmer. Pertama-tama mari kita sempurnakan tampilan form input data. Form input data memiliki sebuah input khusus untuk input nilai date (tanggal) dimana inputan tersebut akan menampilkan 3 buah dropdown tahun, bulan dan tanggal. Dropdown tahun hanya menampilkan sepuluh tahun terakhir dari tahun saat ini, padahal kita ingin menyediakan opsi dari tahun 1960 sampai 2010. Lakukan modifikasi pada code “web/templates/user/form.html.eex” tepatnya pada baris code berikut :

<%= date_select f, :birth, class: "form-control" %>

lakukan perubahan menjadi seperti berikut :

    <%= date_select f, :birth, year: [options: 1960..2010], class: "form-control" %>

Jika anda jalankan aplikasi maka dropdown tahun akan menampilkan opsi tahun dari tahun 1960 hingga tahun 2010.

Untuk memberikan nilai default pada tahun dapat memasukkan code sebagai berikut :

    <%= date_select f, :birth, year: [value: 1989 ,options: 1960..2010], class: "form-control" %>

Jika anda ingin membuat rentang tahun dari tahun 1960 sampai dengan tahun saat ini dapat membuat code seperti berikut :

    <%= date_select f, :birth, year: [value: 1989 ,options: 1960..DateTime.utc_now.year], class: "form-control" %>

VALIDASI

Sebagai contoh kita ingin menambahkan validasi berikut :

  • Nama harus memiliki panjang minimal 3 huruf
  • Email memiliki panjang minimal 5 huruf
  • Email harus memiliki format email yang benar, jika format email salah maka akan mengirimkan pesan error “Please fix your email format”

Phoenix meletakkan segala hal yang berkaitan dengan data (termasuk validasi) pada Model. Untuk menambahkan validasi di atas kita dapat melakukan modifikasi model User tepatnya pada changeset menjadi seperti berikut :

def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :birth, :age])
    |> validate_required([:name, :email, :birth, :age])
    |> validate_length(:name, min: 3)
    |> validate_length(:email, min: 5)
    |> validate_format(:email, ~r/@/, message: "Please fix your email format")
  end
  • validate_length digunakan membatasi panjang suatu field. Pada code di atas kita memberikan validasi field name minimal terdiri dari 3 huruf dan field email minimal terdiri dari 5 huruf. Selain membatasi panjang minimal anda juga dapat membatasi panjang maksimal (gunakan “max”) dan membuat suatu field harus memiliki panjang tertentu (gunakan “is”).
  • validate_format digunakan untuk menentukan format suatu  inputan data. Pada code di atas validate_format mengharuskan field email memiliki karakter “@“ di dalamnya.
  • validate_format di atas juga akan mengeluarkan pesan error “Please fix your email format” jika pengguna tidak mengikuti format yang telah ditentukan.

Selesai.. itulah sedikit perkenalan dengan Phoenix Framework..

Xamarin iOS – Expendable UITableView

Xamarin iOS – Expendable UITableView

Anggaplah saya ingin menampilkan sekumpulan data klub sepak bola ke dalam UITableView. Jika salah satu klub sepak bola di-klik, akan menampilkan daftar pemain pada data tersebut seperti gambaran berikut :

jan-03-2017-10-16-37

Pertama buatlah model untuk mendefinisikan data SoccerClub pada file “SoccerClubModel.cs”, masukkan code berikut :

 using System;
 using System.Collections.Generic;
 
 namespace ios_playground
 {
     public class SoccerClubModel
     {
         private List<string> players;
         private string name;
 
         public SoccerClubModel(string name)
         {
             this.name = name;
             players = new List<string>();
         }
 
         public string Name { 
             get { return this.name;}
         }
 
         public List<string> Players
         {
             get { return this.players; }
             set { this.players = value;}
         }
     }
 }

Code tersebut mendefinisikan class SoccerClubModel dengan variabel nama klub sepak bola dan list nama pemainnya.

Tambahkan sebuah UIViewController dan tambahkan UITableView di dalamnya. Beri nama UITableView tersebut dengan nama “tableExpend”. Paad UITableView yang telah ditambahkan terdapat sebuah UITableViewCell, atur property “Style” -nya menjadi “Right Detail” dan beri identifier “CellChild”.

Kita akan memasuki bagian utama yaitu membuat UITableViewSource yang dapat mendefinisikan Expendable table. Buatlah sebuah class “ExpendableSource” yang akan meng-extend class UITableViewSource. Masukkan code berikut :

 using System;
 using System.Collections.Generic;
 using ObjCRuntime;
 using Foundation;
 using UIKit;
 using CoreGraphics;
 
 namespace ios_playground
 {
     public class ExpendableSource : UITableViewSource
     {
         List<SoccerClubModel> datas;
         NSString childIdentifier = new NSString("CellChild");
         UITableView parentTable;
 
         private bool[] _isSectionOpen;
         private EventHandler _buttonClick;
 
         public ExpendableSource(UITableView parentTable,List<SoccerClubModel> datas)
         {
             this.datas = datas;
             this.parentTable = parentTable;
 
             _isSectionOpen = new bool[datas.Count];
 
             _buttonClick = (sender, e) =>
             {
                 var button = sender as UIButton;
                 var section = button.Tag;
                 _isSectionOpen[(int)section] = !_isSectionOpen[(int)section];
 
                 parentTable.ReloadData();
                 var paths = new NSIndexPath[RowsInSection(parentTable, section)];
                 for (int i = 0; i < paths.Length; i++)
                 {
                     paths[i] = NSIndexPath.FromItemSection(i, section);
                 }
 
                 parentTable.ReloadRows(paths, UITableViewRowAnimation.Automatic);
             };
         }
 
         public override nint NumberOfSections(UITableView tableView)
         {
             return datas.Count;
         }
 
         public override nint RowsInSection(UITableView tableview, nint section)
         {
             return _isSectionOpen[(int)section] ? datas[(int)section].Players.Count : 0;
         }
 
 
         public override nfloat GetHeightForHeader(UITableView tableView, nint section)
         {
             return 44f;
         }
 
         public override nfloat EstimatedHeightForHeader(UITableView tableView, nint section)
         {
             return 44f;
         }
 
         public override UIView GetViewForHeader(UITableView tableView, nint section)
         {
             UIView view = CustomView(datas[(int)section].Name, (int)section);
             return view;
         }
 
         public UIView CustomView(string caption,nint tag)
         {
             UIView view = new UIView(new System.Drawing.RectangleF(0, 0, 320, 20));
             view.BackgroundColor = UIColor.White;
 
             UILabel label = new UILabel();
             label.Font = UIFont.FromName("Helvetica-Bold", 16f);
             label.Frame = new System.Drawing.RectangleF(5, 10, 315, 20);
             label.Text = caption;
             view.AddSubview(label);
 
             UIButton hiddenButton = new UIButton(view.Bounds);
             hiddenButton.Tag = tag;
             hiddenButton.TouchUpInside += _buttonClick;
             view.AddSubview(hiddenButton);
 
             return view;
         }
 
         public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
         {
             UITableViewCell cell = tableView.DequeueReusableCell(childIdentifier);
             if (cell == null)
                 cell = new UITableViewCell(UITableViewCellStyle.Default, childIdentifier);
 
             cell.TextLabel.Text = datas[indexPath.Section].Players[indexPath.Row];
             cell.DetailTextLabel.Text = datas[indexPath.Section].Name;
 
             return cell;
 
         }
     }
 }
 

Berikut penjelasan code di atas :

  • Mendefinisikan variabel datas untuk menyimpan data yang ditampilkan, childIdentifier sebagai identifier dari UITableViewCell yang digunakan, dan parentTable yang akan menghubungkan class dengan tabel yang menggunakannya. Perlakuan yang terjadi pada variabel “parentTabel” akan berpengaruh langsung pada table yang memanggil class ini.
  • Pendefinisian variabel _isSectionOpen untuk variabel penyimpan data mana saja yang sedang menampilkan dan menyembunyikan daftar nama pemain, _buttonClick yang akan menyimpan suatu event.
  • Constructor akan mendefinisikan tableView, datas serta proses yang dilakukan untuk event _buttonClick
  • NumberOfSections sebagai jumlah section pada tabel
  • RowsInSection sebagai jumlah row pada masing-masing section
  • GetHeightForHeader dan EstimatedHeightForHeader untuk tinggi header
  • GetViewForHeader menentukan UIView apa yang ditampilkan pada section header. Header sendiri akan memanggil UIView dari fungsi CustomView() dimana fungsi ini akan menghasilkan sebuah UIView yang terdiri dari sebuah UILabel beserta hidden UIButton yang ketika di klik akan menjalankan event _buttonClick
  • Yang terakhir fungsi GetCell() adalah code yang menampilkan data pada masing-masing row data nama pemain sepak bola.

Selesai.. silahkan running aplikasi anda..

Thanks to https://github.com/b099l3/StackOverflow/tree/master/ExpandableTableView

Code dari tutorial ini dapat juga dilihat di https://github.com/sabithuraira/xamarin_ios_playground/commit/1eccd61bee5720009aef8af99613245bc1161c4d

[Xamarin iOS] Consuming REST API

Setelah memahami cara mengkonsumsi REST API dengan Xamarin android pada tautan berikut, sekarang kita akan mencoba penerapan pada aplikasi iOS dengan Xamarin.

Buatlah sebuah aplikasi iOS pada Xamarin. Design main.storyboard anda seperti berikut :

screen-shot-2016-12-24-at-1-38-22-am

Main.storyboard di atas terdiri dari :

  • UINavigationController
  • MainController : terhubung langsung dengan UINavigationController. MainController terdiri dari sebuah UITableView (kita beri nama “TabelFood”) dan sebuah UITextField (diberi nama “txtTitle”). UITableView secara otomatis memiliki sebuah UITableViewCell, atur style-nya menjadi “Right Detail” dan berilah identifier-nya “CellData”.
  • FormController : Controller ini digunakan untuk melakukan proses edit data (insert, update dan delete). FormController terdiri dari dua buah UITextField guna input data baru yaitu txtName dan txtPrice.
  • Sebuah segue dari UINavigationController ke MainController dengan type segue “root”
  • Sebuah segue dari MainController ke FormController dengan  type “show” dan nama segue “editSegue”

Setelah selesai melakukan pengaturan UI pada masing-masing controller buatlah class FoodMenu.cs sebagai model dari data Food. Masukkan code berikut :

 using System;
 namespace silexiOS
 {
     public class FoodMenu
     {
         private int id;
         private string name;
         private double price;
 
         public FoodMenu(string name, double price)
         {
             this.name = name;
             this.price = price;
         }
 
         public FoodMenu(int id, string name, double price)
         {
             this.id = id;
             this.name = name;
             this.price = price;
         }
 
         public int Id { get { return this.id; } }
 
         public string Name { get { return this.name; } }
         public double Price { get { return this.price; } }
     }
 }
 

Tambahkan package “Newtonsoft.Json” pada project anda untuk memudahkan mengkonsumsi json format yang didapat dari REST API. Buatlah sebuah fungsi “FoodLoader.cs” untuk mengakses REST API dari server, masukkan code berikut :

 using System;
 using System.Collections.Generic;
 using System.Text;
 using System.IO;
 using System.Net;
 using Newtonsoft.Json.Linq;
 
 namespace silexiOS
 {
     public class FoodLoader
     {
         private static readonly string serverLink = "http://localhost/medical/web/index2.php/";
 
         public FoodLoader()
         { }
 
         public static List<FoodMenu> LoadData()
         {
             string link = serverLink + "daftar";
             List<FoodMenu> datas = new List<FoodMenu>();
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
                 request.Method = "GET";
                 request.ContentType = "application/json";
 
                 HttpWebResponse response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     string outputString = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
 
                     outputString = outputString.Trim().Replace("// ", "");
                     var resultArray = JArray.Parse(outputString);
 
                     for (int i = 0; i < resultArray.Count; i++)
                     {
                         var jstock = (JObject)resultArray[i];
 
                         FoodMenu temp_data = new FoodMenu(
                             int.Parse((string)jstock["id"]),
                             (string)jstock["name"],
                             double.Parse((string)jstock["price"])
                         );
 
                         datas.Add(temp_data);
                     }
                 }
             }
             catch (Exception e)
             {
                 throw;
             }
             return datas;
         }
 
         public static String InsertData(FoodMenu data)
         {
             string link = serverLink + "insert";
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
 
                 StringBuilder postData = new StringBuilder();
                 postData.Append("name=" + data.Name + "&");
                 postData.Append("price=" + data.Price.ToString());
                 var data_encode = Encoding.ASCII.GetBytes(postData.ToString());
 
                 request.Method = "POST";
                 request.ContentType = "application/x-www-form-urlencoded";
                 request.ContentLength = data_encode.Length;
 
                 using (var stream = request.GetRequestStream())
                 {
                     stream.Write(data_encode, 0, data_encode.Length);
                 }
 
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
 
         public static String UpdateData(FoodMenu data, int id)
         {
             string link = serverLink + "update/" + id.ToString();
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
 
                 StringBuilder postData = new StringBuilder();
                 postData.Append("name=" + data.Name + "&");
                 postData.Append("price=" + data.Price.ToString());
                 var data_encode = Encoding.ASCII.GetBytes(postData.ToString());
 
                 request.Method = "PUT";
                 request.ContentType = "application/x-www-form-urlencoded";
                 request.ContentLength = data_encode.Length;
 
                 using (var stream = request.GetRequestStream())
                 {
                     stream.Write(data_encode, 0, data_encode.Length);
                 }
 
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
 
         public static String DeleteData(int id)
         {
             string link = serverLink + "delete/" + id.ToString();
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
                 request.Method = "DELETE";
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
     }
 }
 

Dua buah class di atas sama dengan class yang di definisikan pada aplikasi Android yang telah dibuat sebelumnya.

Buatlah class “FoodAdapter.cs” dimana class ini merupakan source yang digunakan untuk mengisi row data pada TabelFood yang ada pada MainController.

 using System;
 using System.Collections.Generic;
 using UIKit;
 using Foundation;
 
 namespace silexiOS
 {
     public class FoodAdapter : UITableViewSource
     {
         private MainController parent;
         private List<FoodMenu> datas = new List<FoodMenu>();
         private string cellIdentifier = "CellData";
 
         public FoodAdapter(MainController parent, List<FoodMenu> datas)
         {
             this.parent = parent;
             this.datas = datas;
         }
 
         public override nint RowsInSection(UITableView tableview, nint section)
         {
             return this.datas.Count;
         }
 
         public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
         {
             var cell = tableView.DequeueReusableCell(cellIdentifier);
 
             if (cell == null)
             {
                 cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
             }
 
             cell.TextLabel.Text = this.datas[indexPath.Row].Name;
             cell.DetailTextLabel.Text = this.datas[indexPath.Row].Price.ToString();
             return cell;
         }
     }
 }
 

Sekarang kita akan mulai masuk mengelola code pada MainController dan FormController yang menjalankan proses utama dari aplikasi yang dibuat. Masukkan code berikut pada MainController :

 using Foundation;
 using System;
 using System.Collections.Generic;
 using UIKit;
 
 namespace silexiOS
 {
     public partial class MainController : UIViewController
     {
         public int EditPosition = -1;
         List<FoodMenu> datas = new List<FoodMenu>();
 
         public MainController (IntPtr handle) : base (handle)
         {
         }
 
         public override void ViewWillAppear(bool animated)
         {
             base.ViewWillAppear(animated);
             InitializeView();
         }
 
         public override void ViewDidLoad()
         {
             base.ViewDidLoad();
             this.InitializeBar();
         }
 
         private void InitializeBar()
         {
             List<UIBarButtonItem> rightButtons = new List<UIBarButtonItem>();
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Add,
                 (sender, e) => { PerformSegue("editSegue", this); }
             ));
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Refresh,
                 (sender, e) => { this.InitializeView(); }
             ));
 
             NavigationItem.RightBarButtonItems = rightButtons.ToArray();
         }
 
         private void InitializeView()
         {
             this.EditPosition = -1;
             txtTitle.Text = "Loading...";
 
             this.datas = FoodLoader.LoadData();
             TabelFood.Source = new FoodAdapter(this, this.datas);
             TabelFood.ReloadData();
 
             txtTitle.Text = "List Food";
         }
 
         public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
         {
             base.PrepareForSegue(segue, sender);
             if (segue.Identifier == "editSegue")
             {
                 var vc = segue.DestinationViewController as FormController;
             }
         }
     }
 }

Yang terjadi pada MainController adalah :

  • Terdapat 2 variabel yang didefinisikan yaitu “EditPosition” dan “datas”. EditPosition digunakan untuk mengetahui posisi data yang di-klik oleh user (ingat kita akan membuat fungsi edit dan delete), sedangkan “datas” akan menyimpan data daftar makanan yang di dapatkan dari REST API.
  • Jika nilai “EditPosition” lebih kecil dari 0 (nol) artinya tidak ada data yang dipilih oleh pengguna.
  • Ketika screen akan muncul maka akan dijalankan fungsi ViewWillAppear dimana fungsi ini akan menjalankan fungsi “InitializeView()”
  • Fungsi “InitializeView()” proses utama-nya adalah mengunduh data yang disediakan dengan REST dimana data yang diunduh langsung ditampilkan pada “TabelFood” yang ada pada screen.
  • Selain menampilkan data ke dalam TabelFood, InitializeView() juga akan memberikan nilai pada “EditPosition” menjadi -1 (ketika kita memanggil/refresh data sudah pasti belum ada data yang dipilih) dan mengeset nilai txtTitle guna menampilkan indikator proses unduh data. Jika proses unduh sedang berjalan, maka txtTitle akan menampilkan tulisan “Loading…”.
  • Setelah menjalankan ViewWillAppear, MainController menjalankan ViewDidLoad() ketika screen telah di load.
  • ViewDidLoad akan menjalankan fungsi “InitializeBar()” dimana fungsi ini akan menambahkan dua buah bar menu pada screen yaitu menu “Add” dan menu “Refresh”.
  • Ketika menu “Add” dipilih maka akan menjalankan fungsi untuk menjalankan segue dimana Controller segue yang dituju adalah form untuk menambahkan data baru. Sedangkan jika menu “Refresh” dipilih, aplikasi akan mengunduh ulang data pada server REST API dan menampilkan ulang data terbaru ke dalam tabel.

Setelah code dijalankan akan muncul screen MainController yang menampilkan data dari server REST API.

Sekarang mari kita membuat code yang akan mengeksekusi code FormController untuk menambah data baru. Masukkan code berikut pada FormController :

 using Foundation;
 using System;
 using System.Collections.Generic;
 using UIKit;
 
 namespace silexiOS
 {
     public partial class FormController : UIViewController
     {
         private int EditId = 0;
         private string EditName;
         private double EditPrice;
 
         public FormController (IntPtr handle) : base (handle)
         {
         }
 
         public override void ViewDidLoad()
         {
             base.ViewDidLoad();
             this.InitializeBar();
             this.InitializeData();
         }
 
         private void InitializeBar()
         {
             List<UIBarButtonItem> rightButtons = new List<UIBarButtonItem>();
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Save,
                 (sender, e) => {
                     FoodMenu temp = new FoodMenu(txtName.Text, double.Parse(txtPrice.Text));
                     if (this.EditId == 0)
                         FoodLoader.InsertData(temp);
                     else
                         FoodLoader.UpdateData(temp, EditId);
                     NavigationController.PopViewController(true);
                 }
             ));
 
             if (this.EditId > 0)
             {
                 rightButtons.Add(new UIBarButtonItem(
                     UIBarButtonSystemItem.Trash,
                     (sender, e) =>
                     {
                         FoodLoader.DeleteData(this.EditId);
                         NavigationController.PopViewController(true);
                     }
                 ));
             }
 
             NavigationItem.RightBarButtonItems = rightButtons.ToArray();
         }
 
         private void InitializeData()
         {
             if (this.EditId > 0)
             {
                 this.txtName.Text = EditName;
                 this.txtPrice.Text = EditPrice.ToString();
             }
         }
 
         public void SetEdit(int edit_id, string name,double price)
         {
             this.EditId = edit_id;
             this.EditName = name;
             this.EditPrice = price;
         }
     }
 }

Yang terjadi pada baris code FormController adalah :

  • FormController selain digunakan untuk operasi insert data juga digunakan untuk operasi update dan delete data.
  • FormController memiliki variabel EditId, EditName dan EditPrice. Ketiga variabel tersebut digunakan untuk menyimpan nilai data yang akan di update/delete yaitu nilai id, name dan price -nya.
  • Jika nilai pada EditId bernilai 0 (nol) artinya FormController sedang menjalankan proses untuk input data, sedangkan jika nilai EditId bernilai lebih dari 0 (nol) artinya FormController sedang menjalankan fungsi untuk update data.
  • Ketika screen terbuka maka akan menjalankan fungsi “InitializeBar” dan “InitializeData”.
  • InitializeBar() akan menambahkan top bar menu yaitu menu “Save” (digunakan untuk menyimpan data yang sudah di input) dan menu “Trash” (digunakan untuk menghapus data yang telah dipilih). Menu “Trash” hanya ditampilkan jika FormController dalam mode update data (EditId bukan 0 nol) bukan insert data.
  • Jika tombol “Save” dipilih, maka terdapat dua opsi proses yang dijalankan. Opsi pertama jika nilai EditId == 0, maka  akan dijalankan proses insert data baru. Jika tidak maka akan dijalankan proses untuk memperbaharui data yang sudah ada sesuai dengan EditId yang ada.
  • InitializeData() akan dijalankan jika nilai EditId lebih dari 0 (nol) yang artinya FormController saat ini sedang dalam mode update data.

Saat aplikasi dijalankan, anda akan melihat list data yang berasal dari server REST API dan anda dapat menambahkan data baru untuk diinput pada server. Bagaimana dengan proses delete dan update? Agar dapat menjalankan proses Update dan Delete, masukkan fungsi berikut pada class “FoodAdapter” :

        public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
        {
            parent.EditPosition = indexPath.Row;
            parent.PerformSegue(editSegue, parent);
        }

Fungsi diatas akan mengeset nilai “EditPosition” pada MainController agar MainController dapat mengetahui data yang mana yang ingin diperaharui oleh user. Kemudian pada MainController perbaharui fungsi PrepareForSegue seperti berikut :

  public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
         {
             base.PrepareForSegue(segue, sender);
             if (segue.Identifier == "editSegue")
             {
                 var vc = segue.DestinationViewController as FormController;
                 if (EditPosition >= 0)
                 {
                     FoodMenu temp = this.datas[EditPosition];
                     vc.SetEdit(temp.Id, temp.Name, temp.Price);
                 }
             }
         }

Ketika proses segue dijalankan aplikasi akan memeriksa jika nilai “EditPosition” > 0 maka peralihan segue akan memanggil fungsi “SetEdit” yang pada FormController dimana guna dari fungsi tersebut adalah untuk melempar data FoodMenu yang ingin diperbaharui..

Selesai..

Silahkan jalankan program anda…

jan-01-2017-09-14-08

Silahkan lihat langsung code saya di https://github.com/sabithuraira/tutor_silex