21 Nisan 2020 Salı

ORTA SEVİYE BİR RAİLS TUTORIAL - 4

Önceki bölümü okumadan buraya geldiyseniz hiç kasmayın geri dönün




JavaScript Stiller


Ana sayfamız hala sade bir görüntüye sahip. Biraz kontrast yaratmak için gönderileri renklendireceğiz. Fakat CSS yardımıyla sabit renk vermek yerine sayfa her yenilendiğinde başka renk paterni alması için JavaScript kullanacağız (ki öğrenmiş olalım).

assets klasörü altındaki javascripts klasörü içine posts adında bir alt klasör ekleyelim ve içine style.js adında bir yeni dosya ekleyelim. Ayrıca javascripts klasöründeki default .coffee dosyalarını da silebiliriz çünkü kullanmayacağız.



app/assets/javascripts/posts/style.js
$(document).on("turbolinks:load", function() {
    if ($(".single-post-card").length) {
        // arkaplana sabit renk
        if (mode == 1) {
            $(".single-post-card").each( function() {
                $(this).addClass("solid-color-mode");
                $(this).css("background-color", randomColor());
            });
        }
        // etraf çizgi rengi stili
        else {
            $(".single-post-card").each( function() {
                $(this).addClass("border-color-mode");
                $(this).css("border", "5px solid" + randomColor());
            });
        }
    }


    $(#feed).on("mouseenter", ".single-post-list", function() {
        $(this).css("border-color", randomColor());
    });

    $(#feed).on("mouseleave", ".single-post-list", function() {
        $(this).css("border-color", "rgba(0,0,0, 0.05)");
    });

});

var colorSet = randomColorSet();
var mode = Math.floor(Math.random() * 2);

// Rastgele bir renk şeması döner
function randomColorSet() {
    var colorSet1 = ["#45ccff", "#49e83e", "#ffd432", "#e84830", "#8243ff"];
    var colorSet2 = ['#FF6138', '#FFFF9D', '#BEEB9F', '#79BD8F', '#79BD8F'];
    var colorSet3 = ['#FCFFF5', '#D1DBBD', '#91AA9D', '#3E606F', '#193441'];
    var colorSet4 = ['#004358', '#1F8A70', '#BEDB39', '#FFE11A', '#FD7400'];
    var colorSet5 = ['#105B63', '#FFFAD5', '#FFD34E', '#DB9E36', '#BD4932'];
    var colorSet6 = ['#04BFBF', '#CAFCD8', '#F7E967', '#A9CF54', '#588F27'];
    var colorSet7 = ['#405952', '#9C9B7A', '#FFD393', '#FF974F', '#F54F29'];
    var randomSet = [colorSet1, colorSet2, colorSet3, colorSet4,
 colorSet5, colorSet6, colorSet7];
    return randomSet[Math.floor(Math.random() * randomSet.length)]; 
}

// bir renk şemasından rastgele renk döner
function randomColor() {
    var color = colorSet[Math.floor(Math.random() * colorSet.length)];
    return color;
}

Not: Sayfayı yenilediğinizde renklenme olmadıysa jQuery en başta yüklenmemiş olabilir. application.js içinde jQuery çağıran satırı en önceye alın, aşağıdaki gibi.

//= require jquery
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require bootstrap-sprockets
Yukarıdaki koda kısaca bakarsak, her sayfa yenilenişinde iki değişik renklendirme modundan biri seçiliyor. Stilin birinde sadece sınır çizgileri renkleniyor, diğerinde gönderi komple renkleniyor.

mouseenter ve mouseleave işleyicileri ise daha sonra kullanılacak, ana sayfadan farklı gönderi gösterimleri olacak, onlarda alttaki sınır çizgi rengi yavaşça değişecek.

Tekrar posts.scss dosyasını açalım ve birkaç stil ekleyelim.

app/assets/stylesheets/partials/posts.scss
...
.solid-color-mode, .border-color-mode {
    .post-text {
        text-align: center;
    }
}

.solid-color-mode {
    .post-text {
        padding: 10px;
        background-color: white;
        border-radius: 25px;
    }
}

.border-color-mode {
    background-color: white;
}

Ayrıca mobile.scss içine uzun yazıların sıkıntısı için şunu ekleyelim

app/assets/stylesheets/responsive/mobile.scss
...
@media screen and (max-width: 767px) {
    .solid-color-mode, .border-color-mode {
        .post-text {
            font-size: 16px;        
        }
    }
}

Ana sayfayı yeniledikçe şimdi şunlara benzer görüntüler alacağız.







Modal penceresi


Sayfadaki gönderilerden birine tıkladığımızda başka sayfaya geçmeden gönderinin tamamını görnek istiyoruz. Bunu yapmak için Bootstrap’ın modal elemanını kullanacağız.

Görsel için posts klasörü içine _modal.html.erb parça görseli dosyasını ekleyelim ve şu kodu yazalım:

app/views/posts/_modal.html.erb
<!-- Modal -->
<div class="modal myModal" tabindex="-1" role="dialog" 
          arialabelledby="myModalLabel">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <span class="posted-by"></span>
                <button type="button" class="close" 
                        data-dismiss="modal" 
                        aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <div class="loaded-data">
                    <h3></h3>
                    <p></p>
                    <div class="interested"><a href="">İlgimi çekti</a></div>
                </div><!-- loaded-data -->
            </div><!-- modal-body -->
        </div>
    </div>
</div>

Bu Bootstrap dökümanından biraz değiştirilmiş bir görsel. Şimdi bunu ana sayfa görsel dosyasının en üstünde yayınlayalım.

app/views/pages/index.html.erb
<%= render "posts/modal" %>
...

Bu modal pencerenin çalışması için biraz JavaScript eklememiz gerekiyor. posts klasörü içinde modal.js adında yeni bir dosya ekleyelim ve içine şunları yazalım:

app/assets/javascripts/posts/modal.js
$(document).on("turbolinks:load", function() {
    // gönderi tıklandığında tüm içeriğini modal pencerede göster
    $("body").on("click", ".single-post-card, .single-post-list", function() {
        var posted_by = $(this).find(".post-content .posted-by").html();
        var post_heading = $(this).find(".post-content h3").html();
        var post_content = $(this).find(".post-content p").html();
        var interested = $(this).find(".post-content .interested").attr("href");
        $(".modal-header .posted-by").text(posted_by);
        $(".loaded-data h3").text(post_heading);
        $(".loaded-data p").text(post_content);
        $(".loaded-data .interested a").attr("href", interested);
        $(".myModal").modal("show");
    });
});

Sayfaya daha önce koyduğumuz sonra CSS ile görünmez yaptığımız verileri önce değişkenlere alıyoruz. Sonra modal penceresinde ait oldukları tere koyuyoruz. En son olarak da modal penceresini görünür yapıyoruz.

Modal pencerenin stilini biraz değiştireceğiz. Ama önce biraz düzenleme yapalım. Stillerin içindeki partials klasöründe posts adında bir alt klasör tanımlayalım. posts klasörü altında bir home_page.scss dosyası yapalım ve daha önce posts.scss içine yazdığımız tüm stil kodlarını buraya kopyalayıp eski posts.scss dosyasını silelim. Kafa karışmaması için bu daha düzenli olacak.

Şimdi posts klasörü içine modal.scss dosyasını ekleyelim ve içine şu kodu yazalım:

app/assets/stylesheets/partials/posts/modal.scss
.modal-content {
    h3 {
        text-align: center;
    }
    p {
        margin: 50px 0;
    }
    .posted-by {
        color: rgba(0,0,0, 0.5);
    }
}

.modal-content {
    .loaded-data {
        h3, p {
            overflow: hidden;
        }
        padding: 0 10px;
        .posted-by {
            margin: 0;
        }
    }
}

.interested {
    text-align: center;
    a {
        background-color: $navbarColor;
        padding: 10px;
        color: white;
        border-radius: 10px;
        &:hover {
            background-color: black;
            color: white;
        }
    }
}

Tabii bu dosyaları application.scss içinde import etmemiz gerekiyor.

app/assets/stylesheets/application.scss
...
// Parça dosyalar
@import "partials/*";
@import "partials/layout/*";
@import "partials/posts/*";
...

Modal pencere artık böyle görünüyor:






Tek Gönderi Gösterimi


Eğer İlgimi çekti düğmesine tıklarsak bir hata mesajı alırız. Henüz show.html.erb görsel kalıbını oluşturmadık. Ayrıca PostController içinde show aksiyonunu da tanımlamadık.

PostController içinde show aksiyonunu tanımlayalım ve içine istenen gönderiyi seçen bir kod yazalım.

app/controllers/posts.controller.rb
class PostsController < ApplicationController

    def show
        @post = Post.find(params[:id])
    end
    
end


İlgimi çekti butonu tıklayınca bizi /posts/1 adresi benzeri bir adrese gönderdi. Bu bir GET işlemi olduğu için PostController kontrolörünün show aksiyonuna id parametre değeri 1 olarak yönlendirir. Nereden mi biliyoruz? Konsolda

rails routes
yazınca yollardan biri aynen şunu yazıyor

post GET    /posts/:id(.:format)           posts#show
Şimdi posts klasörü altında show.html.erb görsel kalıp dosyasını oluşturalım ve içine şu kodu yazalım:

app/views/posts/show.html.erb
<div id="single-post-content" class="container">
    <div class="row">
        <div class="col-sm-6 col-sm-offset-3">
            <div class="posted-by">Posted by <%= @post.user.name %></div>
            <h3><%= @post.title %></h3>
            <p><%= @post.content %></p>
        </div>
    </div><!-- row -->
</div>

Bu sayfa için bir de stil sayfası oluşturalım.

app/assets/stylesheets/partials/posts/show.scss
#single-post-content {
    background: white;
    height: calc(100vh - 50px);

    h3 {
        text-align: center;
    }
    p {
        margin: 50px 0;
    }
    .posted-by {
        font-size: 12px;
        font-size: 1.2rem;
        margin: 20px 0;
        color: rgba(0,0,0, 0.5);
    }
}

Burada 100vh - 50px hesabıyla tüm görünen alan yüksekliğinden 50 piksel düşük bir yükseklik ifade ediliyor. Niye 50 piksel? Çünkü daha önce içerik gezinti bar altında kalmasın diye 50 piksel aşağı kaydırmıştık. Eğer sadece 100vh deseydik tüm yükseklikten aşağı kaymış olacaktık ve içerik sayfayı doldurmamış olsa bile yanda scroll bar çıkacaktı. Bu stil ile içerik kısa da olsa tüm sayfa arka plan rengini beyaz yapıyoruz.

Şimdi İlgimi çekti düğmesini tıklarsak şuna benzer bir sayfa ile karşılaşırız:


Daha sonra görsel dosyamıza başka özellikler de ekleyeceğiz.



Özel Kolleksiyonlar


Her gönderi değişik kolleksiyonlara ait. Bu çeşitlere ait ayrı sayfalar oluşturalım.

Ana Sayfa kenar menüsü


Önce ana sayfanın index.html.erb görselinde kenarda bu kolleksiyonlara ait linkler yerleştirerek başlayalım.

#side-menu elemanı içine linkleri yerleştireceğiz. Ortalık karışmasın diye #side-menu ve #main-content elemanlarını kesip ayrı parça görseller içine yerleştirelim. pages klasörü altında index adında bir alt klasör oluşturalım ve içine şu parça görselleri koyalım:

app/views/pages/index/_side_menu.html.erb
<div id="side-menu" class="col-sm-3">
</div><!-- sol kenar menü -->

app/views/pages/index/_main_content.html.erb
<div id="main-content" class="col-sm-9">
    <%= render @posts %>
</div><!-- ana içerik -->


Şimdi ana sayfa görselinde bu parça görselleri yayınlayalım.

app/views/pages/index.html.erb
<%= render "posts/modal" %>
<div class="container">
    <div class="row">
        <%= render "pages/index/side_menu" %>
        <%= render "pages/index/main_content" %>
    </div><!-- row -->
</div><!-- container -->


Kenar menü görseli içine yeni linkler ekleyelim.

app/views/pages/index/_side_menu.html.erb
<div id="side-menu" class="col-sm-3">
    <ul id="links-list">
        <%= render "/pages/index/side_menu/no_login_required_links" %>
    </ul>
</div><!-- sol kenar menü -->

Sırasız bir liste ekledik ve liste içinde de başka bir parça görsel çağırdık. Bunlar kullanıcı girişi yapmadan tüm kullanıcıların görebileceği sayfalara olan linkler. Parça görsel dosyasını oluşturalım ve içine linkleri ekleyelim.

index klasöründe side_menu adında bir alt klasör oluşturalım ve içine _no_login_required_links.html.erb dosyasını koyalım.

app/views/pages/index/side_menu/_no_login_required_links.html.erb
<li id="hobby">
    <%= link_to hobby_posts_path do %>
        <i class="fa fa-user-circle-o" aria-hidden="true"></i> 
        Bir hobi arkadaşı bulun
    <% end %>
</li>

<li id="study">
    <%= link_to study_posts_path do %>
        <i class="fa fa-graduation-cap" aria-hidden="true"></i>
        Bir çalışma arkadaşı bulun
    <% end %>
</li>

<li id="team">
    <%= link_to team_posts_path do %>
        <i class="fa fa-users" aria-hidden="true"></i>
        Bir takım üyesine ulaşın
    <% end %>
</li>

Gönderilerin özel kolleksiyonları için linkler ekledik. hobby_posts_path değişkeni nasıl tanımlandı? routes.rb içinde resources :posts standart link listesine ilaveten bu 3 linki de tanımlamıştık hatırlarsanız.

Eğer i elemanının özelliklerine bakarsak fa sınıf dikkatimizi çeker. Bu sınıf ile Font Awesome ikonlarını deklare ediyoruz. Henüz bu kütüphaneyi eklemedik, ama çok kolay. Ana görsel yerleşimi olan application.html.erb dosyasında head elemanı içine şu satırı ekleyelim:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

Kenar menü artık şöyle görünecek:


767 ve 1000 piksel arası genişlikte olan ekranlarda Bootstrap container çok sıkışık görünüyor (yanlarda boş bantlar sıkıştırıyor). bunu düzenlemek için mobile.scss dosyası içine şu tanımı da ekleyelim:

app/stylesheets/responsive/mobile.scss
...

@media only screen and (min-width: 767px) and (max-width: 1000px) {
    .container {
        width: 100% !important;
    }
}




Kolleksiyon sayfası


Eğer kenar menüde linklerden birine tıklarsak bir hata mesajı alırız. Ne PostController içinde aksiyon tanımladık ne de bir görsel kalıbımız var. PostController içinde hobby , study ve team aksiyonlarını tanımlayalım:

app/controllers/posts_controller.rb
...

    def hobby
        posts_for_branch(params[:action])
    end

    def study
        posts_for_branch(params[:action])
    end

    def team
        posts_for_branch(params[:action])
    end

end

Her aksiyon içinde posts_for_branch metodu çağırılıyor. Bu metod aksiyon ismine göre sayfaya özel kayıtları dönecek. Bu metodu private bölümünde tanımlayalım.

app/controllers/posts_controller.rb
...
    private

    def posts_for_branch(branch)
        @categories = Category.where(branch: branch)
        @posts = get_posts.paginate(page: params[:page])
    end
end

@categories oluşum değişkeni belirtilen kolleksiyona ait kategorileri içeriyor. Gönderileri @posts oluşum değişkenine koymak için get_posts metodu kullanılıyor ve sonra paginate metodu ile düzenleniyor. paginate metodu will paginate gem’inden geliyor. PostController içindeki private bölümünde get_posts metodunu tanımlayarak başlayalım:

app/controllers/posts_controller.rb
...
    def get_posts
        Post.limit(30)
    end
...

Metod şu anda sadece sıradan 30 tane kaydı alıyor. Daha ileride bunu geliştirebiliriz. Yakında bu metoda geri dönecğiz.

Pagination için az evvel bahsi geçen gem’i Gemfile’a ekleyelim

gem "will_paginate", "~> 3.1.0"
tabii ki arkasından

bundle
Geride tek eksiğimiz kaldı, görsel kalıpları. Tüm kolleksiyonlarda görseller birbirine benzeyecek. Kod tekrarlamak yerine ortak olan kısmı bir parça görsel içine alalım. posts klasörü içinde _branch.html.erb dosyası tanımlayıp içine şunları yazalım:

app/views/posts/_branch.html.erb
<div id="branch-main-content" class="container">
    <div class="row">
        <h1 class="page-title"><%= page_title %></h1>
        <%= render "posts/branch/create_new_post", branch: branch %> 
    </div><!-- row -->

    <div class="row">
        <%= render "posts/branch/categories", branch: branch %>
    </div>

    <div class="row">
        <div class="col-sm-12" id="feed">
            <%= render @posts %>
            <%= render no_posts_partial_path %>
        </div>
    </div><!-- row -->

    <div class="infinite-scroll">
        <%= will_paginate @posts %>
    </div>
</div><!-- container -->

Başta page_title adında bir değişken var. Bunu diğer görsellerden _branch.html.erb parça görselini yayınlarken belirleyeceğiz. Sonrasında kullanıcının yeni bir gönderi yazmasını sağlamak amacıyla _create_new_post parça görselini kullanarak bir link vereceğiz. Şimdi bu parça görseli branch adında yeni bir alt klasör içinde tanımlayalım ve içine şu kodu yazalım:

app/views/posts/branch/_create_new_post.html.erb
<div class="col-sm-12">
    <div class="col-sm-8 col-sm-offset-2">
        <%= render create_new_post_partial_path, branch: branch %>
    </div><!-- col-sm-8 -->
</div><!-- col-sm-12 -->

Buradaki create_new_post_partial_path metodu hangi parça dosyanın gösterileceğine karar verecek. posts_helper.rb yardımcılar doyası içinde metodu tanımlayalım:

app/helpers/posts_helper.rb
module PostsHelper
    def create_new_post_partial_path
        if user_signed_in?
            "posts/branch/create_new_post/signed_in"
        else
            "posts/branch/create_new_post/not_signed_in"
        end
    end
...

Bu iki görsel dosyasını create_new_post adında yeni bir alt klasör içine kodlayalım:

app/views/posts/branch/create_new_post/_signed_in.html.erb
<div class="new-post-button-parent">
    <span>Kimseyi bulamadın mı? O zaman: </span>
    <%= link_to "Yeni gönderi oluştur", 
        new_post_path(branch: branch),
        :class => "new-post-button" %>
</div>

app/views/posts/branch/create_new_post/_not_signed_in.html.erb
<div class="text-center login-branch">
    Yeni gönderi oluşturmak için 
    <%= link_to "Giriş yapın", 
        login_path,
        :class => "login-button login-button-branch" %>
</div>

Dönelim _branch.html.erb dosyasına geri. Sırada _categories.html.erb parça görselin yayınlanması var. Bu görselde kategorilerin bir listesi olacak.

app/views/posts/branch/_categories.html.erb
<% branch_path_name = "#{params[:action]}_posts_path" %>

<div class="col-sm-12">
    <ul class="categories-list">
        <%= render all_categories_button_partial_path, 
            branch_path_name: branch_path_name %>
        <% @categories.each do |category| %>
            <li class="category-item">
                <%= link_to category.name,
                    send(branch_path_name, 
                    category: category.name),
 :class => ("selected-item" if params[:category] == category.name) %>
             </li>
        <% end %>
    </ul>
</div><!-- col-sm-12 -->

Yeni bir tanımadık metod çıktı all_categories_button_partial_path . Bunu da post_helper.rb içinde yardımcı metod olarak tanımlayalım.

app/helpers/posts_helper.rb
...

def all_categories_button_partial_path
    if params[:category].blank?
        "posts/branch/categories/all_selected"
    else
        "posts/branch/categories/all_not_selected"
    end
end

...

Default olarak tüm kategoriler seçilidir. Eğer params[:category] parametresi yoksa kullanıcı hiç bir kategory seçmemiştir yani default olan all seçilidir. Buna karşı gelen parça dosya şöyle:

app/views/posts/branch/categories/_all_selected.html.erb
<li class="category-item">
    <%= link_to "Tümü",
         send(branch_path_name),
         :class => "selected-item" %> 
</li>


app/views/posts/branch/categories/_all_not_selected.html.erb
<li class="category-item">
    <%= link_to "Tümü",
        send(branch_path_name) %> 
</li>

Burada kullanılan send metodu bir metodu ismini string vererek çalıştırmak için kullanılıyor. Bu metodları dinamik olarak çağırmak için çok kullanışlı bir yöntemdir. Bizim buradaki durumumuzda bulunulan kontrolörün aksiyonuna bağlı olarak farklı yollara yönlenmek için kullanılıyor.

Tekrar _branch.html.erb dosyasına kaldığımız terden devam edersek, no_posts_partial_path metodu çağırılıyor. Eğer gönderi bulunamazsa metod bir mesaj verecektir.

posts_helper.rb dosyasının içine bu metodu da ekleyelim.

app/helpers/posts_helper.rb
...
    def no_posts_partial_path
        @posts.empty? ? "posts/branch/no_posts" : "shared/empty_partial"
    end
    
...

Burada çok sevdiğim Ternary Operator kullanarak kod daha sade yapılmış. render metoduna boş string gönderemeyeceğimiz için boş bir parça görsel sayfasına yönlendiriyoruz.

Şimdi o boş parça görselini oluşturalım. Metoddaki tarifine bakarsak boş dosyamızın yolu şöyle:

app/views/shared/_empty_partial.html.erb
Bu boş dosyayı tanımladıktan sonra branch klasörü altında _no_posts.html.erb dosyasını tanımlayalım.

app/views/posts/branch/_no_posts.html.erb
<div class="text-center">Henüz yayınlanmış gönderi yok</div>

Son olarak da will_paginate metodu ile eğer çok fazla gönderi varsa sayfalara bölüyoruz.

Son olarak hobby , study ve team aksiyonları için görselleri tanımlayalım ve içlerinde _branch.html.erb parça görselini değişik parametrelerle çağıralım. Dosyalar şöyle kodlara sahip olacak:

app/views/posts/hobby.html.erb
<%= render "posts/modal" %>
<%= render partial: "posts/branch", locals: {
    branch: "hobby", 
    page_title: "Aynı hobiye sahip insanlar bulun", 
    search_placeholder: "örn. gitar çalmak, programlama, yemek pişirmek"
} %>

app/views/posts/study.html.erb
<%= render "posts/modal" %>
<%= render partial: "posts/branch", locals: {
    branch: "study", 
    page_title: "Sizinle aynı konuda çalışan insanlar bulun", 
    search_placeholder: "örn. beslenme, matematik, astrofizik"
} %>

app/views/posts/team.html.erb
<%= render "posts/modal" %>
<%= render partial: "posts/branch", locals: {
    branch: "team", 
    page_title: "Takımınıza size benzer ilgileri olan insanlar bulun", 
    search_placeholder: "örn. gruba müzisten, proje için yazılımcı"
} %>

Ana sayfadan herhangi bir kolleksiyon sayfasına tıklarsak şuna benzer bir sayfa çıkar:



Sayfada aşağı scroll edersek en altta gönderilerin sayfalara bölündüğünü görürüz.



Şimdi bir sürü şeyi bir arada yaptık. Ben yaparken bir sürü hata yapmışım , buraya gelene kadar 1 saat onları düzeltmeye uğraştım. Bakalım siz bir kerede buraya gelebilecek misiniz. Ama bazen hata yapmak daha iyi anlamaya yardımcı oluyor.

Tabii ki daha yapılacak çok şey var ama kısa bir ara için iyi zaman. Bir sonraki bölümde görüşmek üzere..

Sırada şunlar var:







Hiç yorum yok:

Yorum Gönder