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

%d bloggers like this: