Örnek Uygulama – Sayfalar
Örnek uygulamalarımızın servislerini yazdığımıza göre artık frontend’in ayrıntılarına girebiliriz. İlk önce kullandığımız HTML Template‘i projemize adepte edeceÄŸiz. Ardından serverside rendering kullanarak sayfalarımızı oluÅŸturacağız.
Örnek Uygulama: Profile.me
- Hazırlık
- Modellerin oluşturulması ve MongoDB Bağlantılarının yapılması
- Servislerin yazılması
- Serverside rendering ile sayfaların oluşturulması
4. Sayfaların Hazırlanması
Bir önceki bölümde sistemde kayıtlı bir portfolioyu servisler üzerinden username kullanarak nasıl çekeceÄŸimizi belirtmiÅŸtim. Åžimdi ise kullandığımız HTML Template‘i üzerinden sayfalarımızı oluÅŸturacağız. Bunun için öncelikle indirip, tempalte’i, css dosyalarını ve istemci tarafındaki javascript dosyalarını projemize ekleyelim. Arıdından deÄŸiÅŸiklik yapmamız gereken yerleri düzenleyelim. Son olarakta Jade kullanarak sunucu tarafında saylarımızın nasıl render edeceÄŸimize bakalaım.
4.1 HTML Template
Öncelikle HTML Template‘in MultiPage versiyonunu indiriyoruz. Bunun için Link‘ten projeye ulaşıyoruz, ve Download Mp butonuna basarak MultiPage versiyonunu indiriyoruz. IndirdiÄŸimiz zip dosyasının içinde demoda kullanılan HTML dosyalarının yanı sıra css dosyalarının bulunduÄŸu css klasörünü, javascript dosyalarının bulunduÄŸu js klasörünü ve custom fontların bulunduÄŸunu font klasörü bulunuyor.
HTML Template‘i geliÅŸtiren adamın demosunda kullandığı HTML sayfalarını biz Jade kısmına geldiÄŸimizde, kendi Jade template’lerimizi oluÅŸturmak için kullanacağız. Bundan önce diÄŸer tüm klasörleri projemize kopyalamamız gerekiyor.
Tüm klasörleri projemize kopyalamadan önce projemizde biraz temizlik yapmamız gerekiyor. Profile.me projesinde aşağıdaki klasörlerin içeriğini siliyoruz.
- views: Bu klasörde WebStorm ile projemizi oluştururken, otomatik olarak oluşan index.jade ve user.jade dosyaları bulunmakta. Şu anda bizim için gereksiz ondan dolayı silebiliriz.
- public: Bu klasörde, istemci tarafında kullanılan dosyalar yani css, javascript ve resimler bulunuyor. Bunlarda proje ile birlikte otomatik olarak oluÅŸturulmuÅŸ. Biz HTML Template‘i örnek alıcaz ve farklı bir klasör yapısı kullanacağız. Ondan dolayı bu public klasörünün içerisindeki tüm klasör ve dosyaları kaldırıyoruz.
Projemizi temizlediÄŸimize göre kopyalama iÅŸlemine baÅŸlayabiliriz. IndidiÄŸmiz HTML Template‘i içerisinden çıkan css, js, img ve font klasörlerini Profile.me projesinin içerisindeki public klasörünün içerisine kopyalıyoruz.
4.2 Sitillerin Güncellenmesi
Kopyalama işlemi de bittiğine göre artık css dosyalarındaki bir iki ufak değişikliği yapabiliriz. Değişiklik için public/css/style.css dosyasını açıyoruz ve aşağıdaki satırları css dosyasının sonuna ekliyoruz.
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /** * Custom.... **/ .skills .item { padding:25px; width:100px; height:100px; -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; text-align:center; border:8px solid #203748; float:right; -webkit-transition: all 0.2s linear; -moz-transition: all 0.2s linear; -o-transition: all 0.2s linear; transition: all 0.2s linear; } .skills .item:hover{ border:8px solid #fff; } .skills .item h3 { color:#fff; font-size: 2.5em; font-weight: normal; line-height: 80px; } |
style.css dosyasının sonuna eklediÄŸimiz bu kod, skills sayfasındaki bilgilerin programlanabilir olmasını saÄŸlıyor. ÖrneÄŸin siteden indrdiÄŸimiz halinde, skills sayfasındaki photoshop deÄŸeri hep %90 lık deÄŸerde gösteriliyor. Bu deÄŸiÅŸiklikle biz deÄŸeri %70’e çektiÄŸimizde renki bar otomatik olarak %70lik deÄŸer göstermeye baÅŸlayacak.
4.3 Jade
Sayfalarımızı hazırlamaya baÅŸlamadan önce biraz Jade’den bahsetmek istiyorum. Jade aslında kelimenin tam anlamıyla bir Template Engine. Yani sunucu tarafında HTML sayfaları üretmenize yarayan template’ler sunan bir sistem. Yine java ile karşılaÅŸtırmalı gidecek olursak, JSF’in Facelets’ları gibi düşünebilirsiniz. JSF‘te nasıl Facelets‘lar render edilirken, Context içerisinde ki Bean‘ler kullanılıyorsa, Jade dosyaları render edilirkende anlık olarak oluÅŸturulan context içerisinde Javasciprt nesneleri kullanılıyor.
Jade dosyalarının diğer template sağlayıcılarından en büyük farkı doğrudan XHTML gibi HTML türevi bir yazım şekli kullanmaması. HAML benzeri, boşluk ve tablara dayalı bir yazım dili kullanıyor. İlk sayfamızı geliştirdiğimizde ne demek istediğimi daha iyi anlayacağınızı düşünüyorum.
4.4 Index ve ilk Sayfa
Åžimdi ilk sayfamızı oluÅŸturalım ve Jade’in ne olduÄŸuna bakalım. Bunun için views klasörünün içerisine index.jade isminde bir dosya oluÅŸturuyoruz. İçeriÄŸini ise, indirdiÄŸimiz HTML Template‘i içerisinden çıkan Index.html’den örnek alarak aÅŸağıdaki gibi dolduruyoruz.
index.jade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | doctype 5 html head meta(charset='utf-8') title= title meta(name='description', content='') meta(name='keywords', content='') meta(name='author', content='') meta(name='viewport', content='width=device-width, initial-scale=1.0') link(rel='stylesheet', href='/css/bootstrap.css') link(rel='stylesheet', href='/css/style.css') link(rel='stylesheet', href='/font/css/fontello.css') link(rel='stylesheet', href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700') link(rel='stylesheet', href='/css/jquery.fancybox.css?v=2.1.5', media='screen') script(src='/js/jquery-1.10.1.min.js') script(src='/js/jquery.fancybox.js?v=2.1.5') body .navbar .navbar-inner .container a(class='btn btn-navbar', data-toogle='collapse', data-target='.nav-collapse'): span.icon-bar a(class='brand', href='/portfolio/#{portfolio.username}/index.html'): img(width='64', height='64', src='#{portfolio.profile.thumbnail}') ui(class='nav nav-collapse pull-right') li a(href='#', class='active') i.icon-user | Profile li a(href='#') i.icon-trophy | Skills li a(href='#') i.icon-picture | Works li a(href='#') i.icon-doc-text | Resume .nav-collapse.collapse // Everything you want hidden at 940px or less, place within here .container.profile .span3 img(src='#{portfolio.profile.picture}') .span5 h1 #{portfolio.profile.name} h3 #{portfolio.profile.job} p #{portfolio.profile.description} a(href='mailTo:info@example.com', class='hire-me') i.icon-paper-plane | Hire Me .row.social ul.social-icons li a(href='#{portfolio.profile.facebook}', target='_blank'): img(src='/img/fb.png', alt='facebook') li a(href='#{portfolio.profile.twitter}', target='_blank'): img(src='/img/tw.png', alt='twitter') li a(href='#{portfolio.profile.google}', target='_blank'): img(src='/img/go.png', alt='google') li a(href='#{portfolio.profile.pinterest}', target='_blank'): img(src='/img/pin.png', alt='pinterest') li a(href='#{portfolio.profile.stumbleupon}', target='_blank'): img(src='/img/st.png', alt='stumbleupon') li a(href='#{portfolio.profile.dribbble}', target='_blank'): img(src='/img/dr.png', alt='dribbble') .footer .container p.pull-left a(href='http://www.example.com') |http://www.example.com p.pull-right a(href='#myModal', role='button', data-toggle='modal') i.icon-mail |CONTACT div(id='myModal', class='modal hide fade', tabindex='-1', role='dialog', aria-labelledby='myModalLabel', aria-hidden='true') .modal-header button(type='button', class='close', data-dismiss='modal', aria-hidden='true') |X h3#myModalLabel i.icon-mail | Contact Me .modal-body form input(type='text', placeholder='Your Name') input(type='text', placeholder='Your Email') input(type='text', placeholder='Website (Optional)') textarea(rows='3', style='width: 80%') br button(type='submit', class='btn, btn-large') i.icon-paper-plane |SUBMIT script(src='/js/bootstrap.min.js') script. $('#myModal').modal('hidden') |
Template’imizi oluÅŸturduÄŸumuza göre sırada bu template’i oluÅŸturuacak servisin yazılmasına geldi. Åžimdilik bu sayfayı http://localhost:3000/jdCruz/index.html isteÄŸi yapıldığında çağıralım. Yazacağımız servis ise bir önceki yazdığımızla hemen hemen aynı. Tek farkı JSON olarak ekrana basmak yerine index.jade template’ini render edeceÄŸiz. Render ederken de içerisine portfolio nesnesini barındıran bir context göndereceÄŸiz.
Öncelikle portfolioRoute.js dosyasına aşağıdaki metodu ekliyoruz. Bu metodu ileride kaldıracağız. Ama jade dosyası ile servis ilişkisini anlamak adına ilk başta bu şekilde yapıyoruz.
portfolioRoute.js
1 2 3 4 5 6 7 8 9 10 11 12 | module.exports.index = function(req,res){ var username = req.params.username; Portfolio.findOne({username : username},function(err, entity){ if(err){ console.log("Error while getting Portfolio for username: " + username + " ERROR: " + err); }else{ console.log(entity); res.render('index',{portfolio:entity}); } }); } |
Şimdi ise aşağıdaki satırı app.js dosyasına ekliyoruz.
app.js
1 | app.get('/portfolio/:username/index.html', portfolioRoute.index); |
Böylelikle route tanımlamamızıda yapmış oluyoruz. Åžimdi uygulamamızı çalıştırıp http://localhost:3000/jdCruz/index.html sayfasına geldiÄŸimizde demo’da gördüğümüz sayfanın hemen hemen aynısını elde ediyoruz.
4.5 Layout ve Genel Route
Åžimdi gelin index.jade dosyası ve route’u üzerinden, uygulamamızın diÄŸer sayfalarını ve route’larını inceleyelim.
- Index
- Jade: index.jade
- Route: http://localhost:3000/:username/index.html
- Skills
- Jade: skills.jade
- Route: http://localhost:3000/:username/skills.html
- Work
- Jade: work.jade
- Route: http://localhost:3000/:username/work.html
- Resume
- Jade: resume.jade
- Route: http://localhost:3000/:username/resume.html
EÄŸer tüm sayfalarımızı index.jade dosyası gibi oluÅŸturup aynı ÅŸekilde render edersek, bu route’ları tanımlamak zorundayız. Aynı zamanda her route içinde ayrı ayrı servis fonksiyonlarını yazmamız lazım. Tabi tüm header bilgilerini ve üst menü gibi her sayfada aynı olan verileri kopyalamamız gerektiÄŸini saymıyorum bile.
Bu yaptığımız örnek bir uygulama bile olsa bu ÅŸekilde yapmamız doÄŸru deÄŸil. Bunun için öncelikle route URL bilgisini parametrik hale getireceÄŸiz. Hangi sayfanın render olacağına route URL’i üzerinden karar vereceÄŸiz. Daha sonra ise tüm sayfaları tek bir sayfadan yani layout’tan türeteceÄŸiz. Layout sayfamızı oluÅŸturacağız.
4.5.1 Tek bir Route
Eğer yukarıdaki route ve jade dosyalarının incelemesine bakacak olursanız zaten hangi kısmın parametrik hale getirileceğini anlarsınız. Bizim tek yapmamız gereken render olacak sayfa adını parametrik hale getirmek ve portfolioRoute.js ile app.js dosyalarını bu şekilde güncellemek. Kullanacağımız genel route URL bilgisi aşağıdaki gibi olacak.
- http://localhost:3000/:username/:jadeTemplate.html
Öncelikle bu parametreyi okuyacak ve hangi jade template’ini render edeceÄŸine ona göre karar verecek fonksyionu yazıyoruz. Bunun için portfolioRoute.js dosyasından index fonksiyonunu çıkartıp yerine aÅŸağıdaki fonksiyonu ekliyoruz.
portfolioRoute.js
1 2 3 4 5 6 7 8 9 10 11 12 13 | module.exports.portfolioPage = function(req,res){ var username = req.params.username; var jadeTemplate = req.params.jadeTemplate; Portfolio.findOne({username : username},function(err, entity){ if(err){ console.log("Error while getting Portfolio for username: " + username + " ERROR: " + err); }else{ console.log(entity); res.render(jadeTemplate,{portfolio:entity, jadeTemplate:jadeTemplate}); } }); } |
Burada dikkat ettiyseniz render olacak parametreyi URL parametresi olarak alıyoruz. Ayrıca sayfayı render edecek context bilgisinin içerisine hangi template’in render olduÄŸu bilgisini de gönderiyorum. Bu sayede ileride layout’umu oluÅŸtururken, sayfa bazlı ufak kararlar verebileceÄŸim.
Şimdi ise app.js içerisinden index ile ilgili route bilgisini kaldırıp yerine yeni route bilgisini ekleyelim. Bununla birlikte sonra durumda app.js dosyasının içeriği aşağıdaki gibidir.
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | var express = require('express'); var http = require('http'); var path = require('path'); var mongoose = require('mongoose'); var portfolioRoute = require('./routes/portfolioRoute.js'); var urlString = 'mongodb://localhost/portfoliodb'; mongoose.connect(urlString,function(err,res){ if(err){ console.log('Error while connecting to ' + urlString + " . ERROR: " + err); }else{ console.log('Succeeded connected to ' + urlString); } }); var app = express(); // all environments app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.json()); app.use(express.urlencoded()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public'))); // development only if ('development' == app.get('env')) { app.use(express.errorHandler()); } app.get('/', portfolioRoute.saveExampleData); app.get('/portfolio/:username', portfolioRoute.getPortfolioByUsername); app.get('/portfolio/:username/:jadeTemplate.html', portfolioRoute.portfolioPage); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); }); |
Bununla birlikte artık her yeni eklediğim jade dosyası için yeni bir route tanımlamama gerek kalmayacak. Eğer başka bir context ile render olması gereken bir sayfam olursa onun yine tanımlamam gerekecek. Uygulamanızı tekrar çalıştırıp linke gittiğinizde uygulamanızın hala sorunsuz bir şekilde çalıştığını göreceksiniz.
4.5.2 Layout
Tahmin edeceÄŸiniz gibi diÄŸer sayfaları oluÅŸturmadan önce, her sayfada tekrar edecek, header ve footer bilgilerini ayrı bir layout’a çıkarmak mantıklı olacaktır. Böylelikle sayfalarda tekrar edecek bilgileri tek bir yerden yönetebileceÄŸiz. EÄŸer daha önce web uygulaması geliÅŸtirdiyseniz zaten az çok nerelerin her sayfada yineleneceÄŸini biliyorsunuzdur. Bizim durumumuzda her sayfada, header, footer ve menu bar yinelenecek. Bizde bu bilgileri ayrı bir jade dosyasına çıkacağız. Bunun için yine views klasörünün altına layout.jade isminde bir dosya oluÅŸturuyoruz. İçeriÄŸini ise aÅŸağıdaki gibi yapıyoruz.
layout.jade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | doctype 5 html head block head block meta meta(charset='utf-8') title= title meta(name='description', content='') meta(name='keywords', content='') meta(name='author', content='') meta(name='viewport', content='width=device-width, initial-scale=1.0') block stylesheets link(rel='stylesheet', href='/css/bootstrap.css') link(rel='stylesheet', href='/css/style.css') link(rel='stylesheet', href='/font/css/fontello.css') link(rel='stylesheet', href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700') link(rel='stylesheet', href='/css/jquery.fancybox.css?v=2.1.5', media='screen') block js script(src='/js/jquery-1.10.1.min.js') script(src='/js/jquery.fancybox.js?v=2.1.5') block additionaljs body block header .navbar .navbar-inner .container a(class='btn btn-navbar', data-toogle='collapse', data-target='.nav-collapse'): span.icon-bar a(class='brand', href='/portfolio/#{portfolio.username}/index.html'): img(width='64', height='64', src='#{portfolio.profile.thumbnail}') ui(class='nav nav-collapse pull-right') li a(href='/portfolio/#{portfolio.username}/index.html', class=(jadeTemplate==='index' ? 'active' : ' ')) i.icon-user | Profile li a(href='/portfolio/#{portfolio.username}/skills.html', class=(jadeTemplate==='skills' ? 'active' : ' ')) i.icon-trophy | Skills li a(href='/portfolio/#{portfolio.username}/work.html', class=(jadeTemplate==='work' ? 'active' : ' ')) i.icon-picture | Works li a(href='/portfolio/#{portfolio.username}/resume.html', class=(jadeTemplate==='resume' ? 'active' : ' ')) i.icon-doc-text | Resume .nav-collapse.collapse // Everything you want hidden at 940px or less, place within here block container block footer .row.social ul.social-icons li a(href='#{portfolio.profile.facebook}', target='_blank'): img(src='/img/fb.png', alt='facebook') li a(href='#{portfolio.profile.twitter}', target='_blank'): img(src='/img/tw.png', alt='twitter') li a(href='#{portfolio.profile.google}', target='_blank'): img(src='/img/go.png', alt='google') li a(href='#{portfolio.profile.pinterest}', target='_blank'): img(src='/img/pin.png', alt='pinterest') li a(href='#{portfolio.profile.stumbleupon}', target='_blank'): img(src='/img/st.png', alt='stumbleupon') li a(href='#{portfolio.profile.dribbble}', target='_blank'): img(src='/img/dr.png', alt='dribbble') .footer .container p.pull-left a(href='http://www.example.com') |http://www.example.com p.pull-right a(href='#myModal', role='button', data-toggle='modal') i.icon-mail |CONTACT block modal div(id='myModal', class='modal hide fade', tabindex='-1', role='dialog', aria-labelledby='myModalLabel', aria-hidden='true') .modal-header button(type='button', class='close', data-dismiss='modal', aria-hidden='true') |X h3#myModalLabel i.icon-mail | Contact Me .modal-body form input(type='text', placeholder='Your Name') input(type='text', placeholder='Your Email') input(type='text', placeholder='Website (Optional)') textarea(rows='3', style='width: 80%') br button(type='submit', class='btn, btn-large') i.icon-paper-plane |SUBMIT block javascripts script(src='/js/bootstrap.min.js') script. $('#myModal').modal('hidden') |
Burada dikkatinizi çekmek istediğim bir kaç kısım olacak.
- Her bir bilgi kendi block bilgisinin içerisine yazılmıştır. Bunun sebebi bu layout’u kullanan jade dosyalarında arzu edilirse bu bilgilerin yeniden yazılabilmesidir.
- Bazı block tanımlamaları boş bırakılmıştır. Bu bloklardan container adındaki kısım her yeni sayfada doldurulmalıdır. Doldurulmadığı taktirde hata vermeyecektir. Fakat doldurmazsanız layout kullanmanızında çok bir mantığı kalmayacaktır.
- Servis tarafından, URL parametresi olarak okuyup sonrasında context içerisinde gönderdiğimiz jadeTemplate parametresi burada bazı CSS bilgilerin doldurulmasında kullanılmaktadır. Menu içerisinde aktif olan sayfanın bilgisinin gösterilmesinde kullanılıyor.
Tüm bu bilgileri aldıktan sonra, index.jade dosyamızı ise aşağıdaki şekilde güncelliyoruz.
index.jade
1 2 3 4 5 6 7 8 9 10 11 12 | extends layout block container .container.profile .span3 img(src='#{portfolio.profile.picture}') .span5 h1 #{portfolio.profile.name} h3 #{portfolio.profile.job} p #{portfolio.profile.description} a(href='mailTo:info@example.com', class='hire-me') i.icon-paper-plane | Hire Me |
Gördüğünüz gibi index.jade dosyamız gayet ufaldı ve okunabilecek, değiştirilebilecek hale geldi. Şimdi uygulamamızı tekrar başlattığımızda index.jade dosyasının başarılı bir şekilde render olduğunu göreceğiz. Fakat diğer sayfalara gitmeye çalıştığınızda uygulamız hata verecektir. Bu sebpten diğer sayfaları tanımlamaya başlayabiliriz.
4.6 Diger Sayfalar
Şimdi diğer sayfaları oluşturmaya başlayabiliriz. İlk önce skills sayfasından başlayalım.
4.6.1 Skills
Yine views klasörümüzün altına skills.jade isminde dosyamızı oluşturuyoruz. Bu dosyamızın içeriğinide aşağıdaki gibi değiştiriyoruz.
skills.jade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | extends layout block container .container.skills h2 My Skills each skill in portfolio.skills .row .span3 div(class='item', style='background: #{skill.color}') h3 #{skill.shortcode} .span5 h3 | #{skill.fullname} span #{skill.value}% .expand-bg span(class='expand itemBar #{skill.styleClass}', style='background: #{skill.color}; width: #{skill.value}%') |
Bura da dikkatinizi çekmek istediğim kısımlar şu şekilde
- Örnek HTML template’imizi düşünecek olursak, tüm yetenekleri tek tek yazmamız gerekirdi. Fakat yetenek sayımız belirli olmadığından bunun bir döngü içerisinde yapılmasında fayda var. SaÄŸolsun jade bize bu döngü mekanizmasını da saÄŸlıyor.
- Daha projemizi oluştururken eklediğimiz stilleri burada kullanıyoruz. Eğer o sitiller olmasa, yüzde olarak verdiğimiz bilgi düzeyi ile bar arasında bir uyuşmazlık yaşanıyor.
Uygulamızı tekrar başlattığımızda http://localhost:3000/jdCruz/skills.html sayfası üzerinden testlerimizi gerçekleştiriyoruz.
4.6.2 Work
Work sayfamızın mantığıda skills sayfasınınkiyle hemen hemen aynı. Views klasörünün altına work.jade isimli bir dosya yaratıyoruz ve içeriğini aşağıdaki gibi yapıyoruz.
work.jade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | extends layout block additionaljs script. $(document).ready(function() { $('.fancybox-thumb').fancybox({ helpers :{ type : 'inside' }, overlay : { css : { 'background' : 'rgba(1,1,1,0.65)' } } }); }); block container .container.work h2 My Work ul.work-images each work in portfolio.works li div a(class='fancybox-thumb', rel='fancybox-thumb', href='#{work.image}', title='#{work.title}') img(src='#{work.thumbnail}') |
Burada normal container bloÄŸu haricinde ek javascript’lerin yazıldığı additionaljs bloÄŸuda yazılıyor. Bu sayfada açılacak modal panel’ler için bu javascript kod parçası gerekli.
4.6.3 Resume
Son sayfamızıda yapıp projemizi sonlandırıyoruz. resume.jade dosyamızı oluşturup içeriğini aşağıdaki gibi ayarlıyoruz.
1 2 3 4 5 6 7 8 9 10 | extends layout block container .container.resume h2 My Resume h3 #{portfolio.resume.description} .btn-center a(href='#{portfolio.resume.file}', class='hire-me') i.icon-download | Download Resume h2 #{portfolio.resume.size} |
 Son
Uygulamamızın sonuna geldik. Veritabanına yeni yeni kullanıcılar kaydedip testlerinizi sürdürebilirsiniz. Uygulamanın bir kopyasını indirmek için aşağıdaki git adresini kullanabilirsiniz.
Git: https://github.com/bhdrkn/nodejs-examples/tree/master/node-js-profile
End Of Line