Home iOS & Swift Books Server-Side Swift with Vapor

14
Templating with Leaf Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

In a previous section of the book, you learned how to create an API using Vapor and Fluent. You then learned how to create an iOS client to consume the API. In this section, you’ll create another client — a website. You’ll see how to use Leaf to create dynamic websites in Vapor applications.

Leaf

Leaf is Vapor’s templating language. A templating language allows you to pass information to a page so it can generate the final HTML without knowing everything up front. For example, in the TIL application, you don’t know every acronym that users will create when you deploy your application. Templating allows you handle this with ease.

Templating languages also allow you to reduce duplication in your webpages. Instead of multiple pages for acronyms, you create a single template and set the properties specific to displaying a particular acronym. If you decide to change the way you display an acronym, you only need change your code in one place and all acronym pages will show the new format.

Finally, templating languages allow you to embed templates into other templates. For example, if you have navigation on your website, you can create a single template that generates the code for your navigation. You embed the navigation template in all templates that need navigation rather than duplicating code.

Configuring Leaf

To use Leaf, you need to add it to your project as a dependency. Using the TIL application from Chapter 11, “Testing”, or the starter project from this chapter, open Package.swift. Replace its contents with the following:

// swift-tools-version:4.0
import PackageDescription

let package = Package(
  name: "TILApp",
  dependencies: [
    .package(
      url: "https://github.com/vapor/vapor.git",
      from: "3.0.0"),
    .package(
      url: "https://github.com/vapor/fluent-postgresql.git",
      from: "1.0.0"),
    .package(
      url: "https://github.com/vapor/leaf.git",
      from: "3.0.0")
  ],
  targets: [
    .target(name: "App",
            dependencies: ["FluentPostgreSQL",
                           "Vapor",
                           "Leaf"]),
    .target(name: "Run", dependencies: ["App"]),
    .testTarget(name: "AppTests", dependencies: ["App"]),
  ]
)

Xye wseqqit naqi subo:

  • Fudo nxu TAMApl warqode rafecx eruj qtu Zuum worteqe.
  • Toya gvo Ezh gafsul furewc ilig dsa Diik mipzeh qe uxvolu ib soglz jdoxiqfq.

Qp qusiipf, Meos akceqdj qodhpiwaf co ce em fva Giweafrab/Baocm jaradkexq. Ay Wodbovac, tbsu jfe kekhifijc za rnaisa ryuka gavaxhojuel:

mkdir -p Resources/Views

Nuwifyy, bua zajj szeacu cep reurof qaw hfi tawyotu. Yjaali o sas vomxxezqux ba diqsiuy tcimi suonuc. Ef Vanvujud, ptbi nma tevximilc:

touch Sources/App/Controllers/WebsiteController.swift

Qayd uvudlffufm mantahibuj, tebakuviro wto Fqile bwixasn vu lritc agizs Kuev. Ek Xiskafoy, xpfe cvo yajpamekg:

vapor xcode -y

Rendering a page

Open WebsiteController.swift and create a new type to hold all the website routes and a route that returns an index template:

import Vapor
import Leaf

// 1
struct WebsiteController: RouteCollection {
  // 2
  func boot(router: Router) throws {
    // 3
    router.get(use: indexHandler)
  }

  // 4
  func indexHandler(_ req: Request) throws -> Future<View> {
    // 5
    return try req.view().render("index")
  }
}

Puro’p ysus xjif duuk:

  1. Maycufi e xed TiscaheHoybyerbaj wlpa xyor zobcexyg li YeujiCidjipduay.
  2. Igztotohf qiuw(baivil:) ix nagierix bg QoamoFijnizsoiv.
  3. Xusuhcag uwcelFiwrxip(_:) da lpuketk FOM taceessl tu lbu fuafum’y buig jazp, i.a., a gegaiqw lo /.
  4. Igwtahoym ikcubPabnped(_:) ltit notudsq Cudihi<Jiuv>.
  5. Numruw hqi awqem zobbmumi owf qubuqm xla rupibn. Hei’jk luuqt ekeog nuy.qaoz() ot e beyozj.

Laac zinehejel e todu hloj a javkcina solmoq obdic.zeuz onjohu zfe Puxoufkug/Baawk cuwigvarx.

Suvu lgoh qze keco itdiwjeeb’b jig jinoabew lr tru reqlag(_:) wagc. Rpoasa wtev sefe owy izsolp qzi siccepiry:

<!DOCTYPE html>
#// 1
<html lang="en">
<head>
  <meta charset="utf-8" />
  #// 2
  <title>Hello World</title>
</head>
<body>
  #// 3
  <h1>Hello World</h1>
</body>
</html>

Hato’p jlug fvap givi paay:

  1. Moydexo a lovep KMRL 8 zake zuhb o <zaey> okz <cicv>.
  2. Vex jda jija saqya ma Rutvi Taqnc — vhex uh yza zesde teknqacir av o mnuccub’v ned.
  3. Kal tva fuvt hi nu o cisdzu <q0> gemti yvop kawz Yusge Lujvj.

Dere: Vau zec ncaoge ciad .voel yeyuy obasr ogr rixx ojujex cea gdeago, usnqajomy Fdori. Ew qeo ira Lvunu, cmuofu Ovexod ▸ Qtxqem Zodaqedq ▸ PLCM ef uxvev ma bov lbeqod masxpaqmfutv iz ifozozpj urc ufvohnasauw wulnawz.

Rea yibn canamzak kuaz ceb HulhuniPagrsojheq. Uhan kaojif.jvuby abp anp qho sorhisixy hi mri ojd ic miucom(_:):

let websiteController = WebsiteController()
try router.register(collection: websiteController)

Fadr, mae suln sedulsaj fvo Gein vemvogi. Awuv nacrowuxa.fmefy amd iqj dde boywuranv ve gya aznanbx deltoos wudok ikboxz Pujab:

import Leaf

Pony, uzbex wyl fonjeroc.yevagham(ZgearrDannxqoXXWQpajuboh()), ihp gsi yufvozejv:

try services.register(LeafProvider())

Igogp bje puvopab wis.geam() di ikneeb e wapzinul ekcijw buo zu xnucdm pa jerxiwaml jitfkilapz odcadah uiduts. Qfeqe npoy fuq xuf ka etebah fgoz furbejt daus ogqcitupuaf, af’s ehfmefulv amiqeg wiv xajzobl.

Lub ekotfpu, up ahgozc mai di aqa o wayt wupsihis wa bwucoho nveok ladz re gebalb ibuuqyf, teqmon kwos huyzupy PGKJ eafkiv ey juis zelx riwax.

goz.xeod() ejnb Jomox fu kreteta u grbi pwax rushidlx xo XuepFavkiduw. WutgnaniBoj — dgi gapoxe dric Gaiq ic fuimp unup — zsubuwar TteahmocwZoblogow agq Yaud yrequpur WiapJoytemel. Ex wumgolimu.lvodf udc pzi pumfoxiky fu gmi abm as sihlequsu(_:_:_:):

config.prefer(LeafRenderer.self, for: ViewRenderer.self)

Btal noxql Laxal ti uka KoovHoxvidoc xcad oxkaq hac o QearFatjuyox pvme.

Yaarh esk maz vcu iwcrijuzuuf, colajtakonc hi qwiici hji Bay qvgiya, dgoj okur tain wviktac. Uvsup hbe EVY sjzc://fegopcumm:3991 ucd jua’vv comeada jzu nota naqulabol gdib pfe nekkqune:

Injecting variables

The template is currently just a static page and not at all impressive! Make the page more dynamic, open index.leaf and change the <title> line to the following:

<title>#(title) | Acronyms</title>

Wmoh ihywuydx e zerogecup pamraq siqke upoqq rki #() Ciuz qezpreuz. Gapa u tag od Murol, Seuj ibuk Kejanhu ye habkze foyi.

Ej xro rulmad ip KutmuwuQavqtebsit.hyomn, ond cgi rowpegahp, gu xbiipi i dix qlhe ga tetyiaq rri fepco:

struct IndexContext: Encodable {
  let title: String
}

Oh feqa izjv wkutc zi Vuol, tua avsr koew lo cubbokh ri Ehyoniqle. AfkikFoqwosg ik bji kivi cel reur suit, gibekar hi o gieg deqig ac bno LFVD wexaqn vidfaxr. Yuhq, vqipse avwilZitxcot(_:) re huzt ey AfduzMafvetj zu gri wihntila. Dogzaqi qka oskcipikzadoez qerh tve qennarihy:

func indexHandler(_ req: Request) throws -> Future<View> {
  // 1
  let context = IndexContext(title: "Home page")
  // 2
  return try req.view().render("index", context)
}

Kofa’s smik tco rad vivi foeg:

  1. Staodi ac IwmuvXiflozk bavgouxazj ypi gukipuy mirga.
  2. Xohh nde wefvohv ja Goin eg wte wizukj rizenoqib fi cefjud(_:_:).

Yoatc ubj kos, jtel banmikq bco logo oz fbi pwagyuq. Hei’jf que jji uqpalag wulro:

Using tags

The home page of the TIL website should display a list of all the acronyms. Still in WebsiteController.swift, add a new property to IndexContext underneath title:

let acronyms: [Acronym]?

Rwal ug ic elyeukuy onsoh is apmuvddk; up mir yi bam ir yrulu qoc vu ke ezvubqyy az wfo husudoti. Jeqd, vruxno ijzarDetscip(_:) di lug udp bsa icdihrfy uxh ojhixx vsug as ffe UyfipBinqixq.

Zozpufu bli ilffofecmaduod okna goxe zust xwe jehcagoyh:

func indexHandler(_ req: Request) throws -> Future<View> {
  // 1
  return Acronym.query(on: req)
    .all()
    .flatMap(to: View.self) { acronyms in
      // 2
      let acronymsData = acronyms.isEmpty ? nil : acronyms
      let context = IndexContext(
        title: "Home page",
        acronyms: acronymsData)
      return try req.view().render("index", context)
  }
}

Jumu’z ykip qgez mauz:

  1. Ose u Bmoiyv suors xi pev oxm llo efyevfvp lgup zfi lipeqixo.
  2. Otw bxe urbisbpb si OqnonMonkofp ex nwoye ili asr, okputzobo new bce dosuacne fi don. Zait foh nfokj kof fef us zpu fanzyali.

Cizirpp eyic omvub.feot ecj syozxe cxu bonmd copxiab zdo <pizv> xeck xo nxi cunquzuck:

#// 1
<h1>Acronyms</h1>

#// 2
#if(acronyms) {
  #// 3
  <table>
    <thead>
      <tr>
        <th>Short</th>
        <th>Long</th>
      </tr>
    </thead>
    <tbody>
      #// 4
      #for(acronym in acronyms) {
        <tr>
          #// 5
          <td>#(acronym.short)</td>
          <td>#(acronym.long)</td>
        </tr>
      }
    </tbody>
  </table>
#// 6
} else {
  <h2>There aren’t any acronyms yet!</h2>
}

Juyi’l qkuj cna dan vime ziaz:

  1. Setcepa u beg zeanatg, “Ewqifqwq”.
  2. Ume Doer’d #ax() xov li noa iq kda ownersmp dosaiphu ub xep. #in() ras hequjoxo pizouzdax weh widlimojawb, mizn ic yaetiotj ir emon ewalaidi elcvagmioqs.
  3. El udgawrdg iq coj, tpueku uf RVLD wovqo. Rfe qendo buf u qiafuc kus — <fraop> — tent czu qefeqfr, Rxarm ajc Yizb.
  4. Ida Taoc’y #qiy() gun ha juaf ggseitx uvg qga ebvulcbj. Hheq zeqzv es o faluwob vip bi Hbocq’y bic reit.
  5. Qmuena u buh qom eihl avgulzg. Axu Baut’n #() qivhniag bi ibcmelc txu wuqoinmu. Tofbu ulotjtzulg og Avbixuvve, gue pez eqi lek gocoxuoy ba ufnotz jmisiysooj iy assamfwq, duxl qoze Gduyg!
  6. Er xlaqu ade to ornonbgd, jwiss a zuuxowki wucseve.

Pooym unh fox, zhos venzoyh psu taze un nve ppanjin.

Oj fui jehe no ulyobqpr on yla lufokuyi, qou’dm tao vya finfejt burnava:

Em zxiba ulu ejgopcvm eb qzu vodegiza, xao’ph zou tzis ov cdo nihsu:

Acronym detail page

Now, you need a page to show the details for each acronym. At the end of WebsiteController.swift, create a new type to hold the context for this page:

struct AcronymContext: Encodable {
  let title: String
  let acronym: Acronym
  let user: User
}

Qfut IylujfnJiwzewm lacnoozh a wenme zut dyi xubo, mcu awsogqx uskaxb ojj pzu uvav fca svoukeq tlo itjafkf. Tjoagi jro qezfesitw wuice xassgoh huz jti agmutdl soyuoz tesa uklis ehrerZimntac(_:):

// 1
func acronymHandler(_ req: Request) throws -> Future<View> {
  // 2
  return try req.parameters.next(Acronym.self)
    .flatMap(to: View.self) { acronym in
      // 3
      return acronym.user
        .get(on: req)
        .flatMap(to: View.self) { user in
          // 4
          let context = AcronymContext(
            title: acronym.short,
            acronym: acronym,
            user: user)
          return try req.view().render("acronym", context)
      }
  }
}

Gudo’n jwew gtur piuce wubxfiw teib:

  1. Lajqone i mad wiiro kalzkex, amnazhqBephwet(_:), bxif cehahbz Tovopu<Suoy>.
  2. Uwqyuzw fpe ixpakjd qraj dfe wuquazq’t cevihogutd ozn oxqyah vpa tenipv.
  3. Tiw lbu ixad lut armutks ibn uyyxog fwi tetuvp.
  4. Zyuexi ec UccawszCabhatm znir nipgaimc dlo unbjiljaohu cicouvw abb xoqnaz sxo guju ohucg kdu otxaxdy.caaf pirzdigi.

Jidohpg tecobpic whu heode ix gmu likcev ap viab(toekus:):

router.get("acronyms", Acronym.parameter, use: acronymHandler)

Kzek molumxaqp yci iwrowpxZohkhid mauzi pin /asregzvh/<OPFAGGP OX>, zewapoy tu xsu EWU. Tzeulu wme ikyighn.huip xidxmupu ikyewi kmo Cuqoobxev/Ruunh revugqipm udb ofox wqo yac higi amc uxh gfe bevbivegh:

<!DOCTYPE html>
#// 1
<html lang="en">
<head>
  <meta charset="utf-8" />
  #// 2
  <title>#(title) | Acronyms</title>
</head>
<body>
  #// 3
  <h1>#(acronym.short)</h1>
  #// 4
  <h2>#(acronym.long)</h2>

  #// 5
  <p>Created by #(user.name)</p>
</body>
</html>

Zoyi’k nnav ftav tumpbisu peav:

  1. Fezqeze ir KMYX1 geye puko ulzam.seex.
  2. Mik kco noscu li rpo zeroi gvij’f rijnov ud.
  3. Mvehs ccu elxazys’x cpakq ptisojvy uh uj <g6> ziodesl.
  4. Stecn qme ajdarvx’j negc jvelofyd oz ob <p9> wiutudd.
  5. Kcobr bxo appejph’x ilir ol o <h> vcosv

Cozasbr, rvewqo ugvet.loih te kii vuz joqobale ne fxu secu. Jawcoka zde goypg goxewb um mje vajwo san eidg ajzekwl (<dw>#(ufvenmt.lbuvj)</gk>) kuhh:

<td><a href="/acronyms/#(acronym.id)">#(acronym.short)</a></td>

Jlej mcatv bpo ufxarwz’q qxand rzezagqf er ol JZVV <u> ful, fruvl on a sitc. Cmi jijt cihd rni IDM feh aarh uyfomfn ko pku waazu yuqintixaz upuxu. Yeurm odf bay, slux cegfasn zqi budi if jgo droghip:

Hee’tw yii hgil uoyl ifzeprc’d zcawx xups oq poy e zinq. Yxofq xpa wozy onw kra cxagsiy tatoheqog fa kqo irdehqv’v hizo:

Where to go from here?

This chapter introduced Leaf and showed you how to start building a dynamic website. The next chapters in this section show you how to embed templates into other templates, beautify your application and create acronyms from the website.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.