用alpinejs建立自己的博客

In the previous entry we developed the backend for our own blog using Deno. Now that we have written some posts and created a backend that will serve them, let us develop the frontend that will show them to the rest of the world. For the frontend we will use AlpineJS, a lean framework.

一篇文章中,我们使用Deno为我们自己的博客开发了后端。 现在我们已经写了一些帖子并创建了一个可以为他们服务的后端,让我们开发一个可以将它们展示给世界其他地方的前端。 对于前端,我们将使用精益框架AlpineJS

If you want to skip the theory and jump straight to the results, you can find the code here and see the finished blog here.

如果你想跳过理论和直接跳转到结果,你可以找到的代码在这里 ,看到了成品博客在这里

AlpineJS (AlpineJS)

AlpineJS is a minimal frontend framwork for composing JavaScript behavior in HTML templates. As the authors say in their GitHub:

AlpineJS是用于在HTML模板中编写JavaScript行为的最小前端框架。 正如作者在其GitHub上所说的:

Think of it like Tailwind for JavaScript.

可以将其视为 JavaScript的Tailwind

AlpineJS offers a low weight and simple library to add reactivity to our frontend. I consider it particularly useful for simple SPAs and prototyping. You can just add it on top of your index.html and start coding.

AlpineJS提供了一个轻量级且简单的库,以增加我们前端的React性。 我认为它对于简单的SPA和原型设计特别有用。 您可以将其添加到index.html顶部并开始编码。

Since our blog will only have two views, a list of all the posts and the selected post, we will program directly without any transpiling.

由于我们的博客只有两个视图,即所有帖子的列表和所选帖子,因此我们将直接进行编程,而无需进行任何编译。

布局 (Layout)

For the basic layout we decide to have a header with our logo, a link to the posts list, and a toggle for dark mode. Then the body that will contain the list view or the post view. At the bottom a footer with the copyright and social links.

对于基本布局,我们决定有一个带有徽标的标头,一个指向帖子列表的链接以及一个用于暗模式的开关。 然后将包含列表视图或帖子视图的正文。 底部的页脚带有版权和社交链接。

Let us start by adding our external stylesheets and scripts.

让我们开始添加外部样式表和脚本。

<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link href="./pretty-checkbox.min.css" rel="stylesheet" />
<link href="./font-awesome.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.3.5/dist/alpine.min.js" defer></script>

We will use Tailwind CSS for most of the styling, Pretty Checkbox as a CSS only toggle, Font Awesome for the icons, and AlpineJS as a framework. In this case we downloaded Pretty Checkbox and Font Awesome to serve them locally for faster and more reliable loading.

我们将使用Tailwind CSS进行大多数样式设计,使用Pretty Checkbox作为仅CSS切换按钮,使用Font Awesome作为图标,并使用AlpineJS作为框架。 在这种情况下,我们下载了Pretty Checkbox和Font Awesome以便在本地提供它们,以便更快,更可靠地加载。

AlpineJS works with scoped data. For a block you declare a set of data and functions that the elements inside have access to.

AlpineJS使用范围数据。 对于一个块,您声明一组内部元素可以访问的数据和函数。

<script>
  function state() {
    return {
      darkMode: false,
      posts: [],
      entry: {
        loaded: false,
        data: undefined,
        prevId: null,
        nextId: null
      },
      loadPosts() {
        if (this.posts.length === 0) {
          fetch('http://localhost:8000/posts')
            .then(res => res.json())
            .then(posts => this.posts = posts);
        }
      },
      async loadEntry(id) {
        document.body.style.cursor = 'wait';
        this.entry.data = '<br />';
        fetch(`http://localhost:8000/posts/${id}`, {
            mode: 'cors',
            headers: {
              'Content-Type': 'text/html'
            }
          })
          .then(res => res.text())
          .then(data => {
            document.body.style.cursor = 'default';
            window.scrollTo(0, 0);
            this.entry = {
              ...this.entry,
              loaded: true,
              data
            };
          })
          .catch(error => console.error(error));
        const entryIdx = this.posts.findIndex(p => p.id === id);
        this.entry.prevId = entryIdx > 0 ? this.posts[entryIdx - 1].id : null;
        this.entry.nextId = entryIdx < this.posts.length - 1 ? this.posts[entryIdx + 1].id : null;
      }
    }
  }
</script>


<body class="bg-black" x-data="state()" x-init="loadPosts()">
</body>

In the template’s body we define the scoped data with x-data and the post-DOM initialization code with x-init.

在模板的body我们使用x-data定义范围数据,并使用x-init定义DOM后的初始化代码。

The scoped data state returns the object with the elements and functions we are going to use throughout the template.

范围数据state返回带有我们将在整个模板中使用的元素和功能的对象。

darkMode is a boolean flag to mark if the user toggled dark mode.

darkMode是一个布尔型标志,用于标记用户是否切换了黑暗模式。

posts is an array holding the loaded posts list.

posts是一个数组,其中包含已加载的帖子列表。

entry indicates if a specific post is loaded, its data in HTML, and the previous and next id, to jump to the previous or next post.

entry指示是否已加载特定帖子,以HTML格式显示其数据以及上一个和下一个ID,以跳转到上一个或下一个帖子。

loadPosts is the initialization function that loads the posts list from our backend.

loadPosts是初始化函数,可从后端加载帖子列表。

loadEntry changes the cursor to indicate we are loading the post, fetches the post from our backend in HTML, and calculates the previous and next post IDs. When the data is loaded we scroll to the top and change back the cursor.

loadEntry更改光标以指示我们正在加载帖子,从后端以HTML格式获取帖子,并计算上一个和下一个帖子ID。 加载数据后,我们滚动到顶部,然后改回光标。

<header class="header-height bg-neon-pink flex items-center" :class="{ 'dark': darkMode }">
    <img class="logo" :src="`./assets/giko-logo${darkMode ? '-white' : ''}.svg`" alt="Blog logo" />
    <a :href="entry.loaded ? '#post-area' : '#posts'" class="ml-4 md:ml-2 text-4xl md:text-2xl font-bold">Posts</a>
    <div class="push flex flex-col items-center">
      <span class="icon-container"><i class="fa" :class="{ 'fa-sun-o': !darkMode, 'fa-moon-o': darkMode }"></i></span>
      <div class="pretty p-switch p-fill text-3xl md:text-lg">
        <input type="checkbox" x-model="darkMode" />
        <div class="state">
          <label />
        </div>
      </div>
    </div>
  </header>

With our data and functions defined, we can write our markup. At the top we have the header.

使用定义的数据和函数,我们可以编写标记。 在顶部,有标题。

To toggle dark mode, AlpineJS offers us class binding. x-bind:class="{}" binds an object where each property is a boolean value indicating if the class is applied or not. In our case we use the object { 'dark': darkMode } to apply the dark class when darkMode is toggled.

要切换暗模式,AlpineJS为我们提供了类绑定。 x-bind:class="{}"绑定一个对象,其中每个属性都是一个布尔值,指示是否应用该类。 在我们的例子中,当切换darkMode时,我们使用对象{ 'dark': darkMode }来应用dark类。

x-bind: allows the shorthand :, so x-bind:class can be written as :class.

x-bind:允许使用简写形式: ,因此x-bind:class可以写为:class

In img we have our logo, and we can bind to its src attribute. We can even use string templates to change our logo according to the user’s chosen mode.

img我们有我们的徽标,并且可以绑定到其src属性。 我们甚至可以使用字符串模板根据用户选择的模式来更改徽标。

To two-way data bind darkMode to the input toggle, we use AlpineJS’ x-model. Whenever we toggle the input, the value of darkMode will update accordingly, adding the dark class to anywhere is needed, like in the header.

为了双向数据将darkMode绑定到输入切换,我们使用AlpineJS的x-model 。 每当我们切换输入时, darkMode的值都会相应地更新,将dark类添加到需要的任何位置,例如在标题中。

<footer class="bg-neon-pink px-6 py-4 md:py-2 flex items-center" :class="{ 'dark': darkMode }">
    <span class="text-2xl md:text-base font-medium">Created by Oriol Miró Barceló</span>
    <div class="push flex space-x-2">
      <a href="https://www.instagram.com/magna_shogun/" target="_blank" class="icon-container"><i class="fa fa-instagram"></i></a>
      <a href="https://www.linkedin.com/in/omirobarcelo" target="_blank" class="icon-container"><i class="fa fa-linkedin"></i></a>
      <a href="https://github.com/omirobarcelo" target="_blank" class="icon-container"><i class="fa fa-github"></i></a>
      <a href="https://stackoverflow.com/users/8526764/" target="_blank" class="icon-container"><i class="fa fa-stack-overflow"></i></a>
    </div>
  </footer>

The footer is straight forward and we only use an AlpineJS directive to add the dark class for dark mode.

页脚直截了当,我们只使用AlpineJS指令为暗模式添加dark类。

列表显示 (List View)

In our posts list view, not only we are going to show the list per se, but we are going to start with a cover, to indicate what this blog is. We do this because the list view is also the initial view.

在我们的帖子列表视图中,我们不仅要显示列表本身,而且还要从封面开始说明这个博客是什么。 我们这样做是因为列表视图也是初始视图。

<div class="background"></div>
  <template x-if="!entry.loaded">
    <div class="w-full h-full flex flex-col justify-center items-center bg-transparent">
      <h2 class="text-gray-100 text-6xl font-bold">Oriol Miró Barceló</h2>
      <p class="text-white text-xl font-medium">Software Developer, Cat Lover</p>
      <p class="text-white text-2xl font-medium"><i>Experience Creator</i></p>
    </div>
  </template>

The background div adds a fixed background cover image.

background div添加固定的背景封面图像。

x-if is another AlpineJS directive, used to show or remove — not simply hide — DOM elements. While there is no loaded entry, we show our cover, which can show whatever we wish, in my case, who am I.

x-if是另一个AlpineJS指令,用于显示或删除(而不仅仅是隐藏)DOM元素。 虽然没有加载的条目,但我们会显示封面,该封面可以显示我们想要的任何内容,就我而言,我是谁。

<template x-if="!entry.loaded">
    <div id="posts" class="px-4 py-5 flex flex-wrap" :class="{ 'dark': darkMode }">
      <template x-for="post in posts" :key="post.id">
        <div class="max-w-lg md:max-w-sm m-6 rounded overflow-hidden flex-none flex flex-col card" @click="loadEntry(post.id)">
          <img class="w-full card-cover" :src="post.coverUrl" :alt="`Cover for ${post.title}`">
          <div class="px-6 py-4">
            <div class="font-bold text-4xl md:text-xl mb-2" x-text="post.title"></div>
          </div>
          <div class="px-6 py-4 flex flex-wrap">
            <template x-for="label in post.labels" :key="label">
              <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-xl md:text-sm font-semibold text-gray-700 m-1" x-text="label"></span>
            </template>
          </div>
        </div>
      </template>
    </div>
  </template>

We add a div with an ID to jump from our header link, if the user wants to skip our beautiful cover.

如果用户想跳过漂亮的封面,我们添加一个ID为ID的div从标题链接中跳转。

To show multiple elements that share markup, we can use AlpineJS’ for, x-for. x-for iterates over our posts array and uses the post’s ID as a key. This key gives a hint over the identity of the elements to AlpineJS, and can use it to decide ordering and re-rendering.

为了显示共享标记的多个元素,我们可以将AlpineJS用于x-forx-for遍历我们的posts数组,并使用post的ID作为键。 该键提示AlpineJS元素的身份,并可以使用它来决定顺序和重新渲染。

For both x-if and x-for is necessary only one root element inside of them. If we want to nest them, we need intermediate elements.

对于x-ifx-for而言,仅在其中一个根元素是必需的。 如果要嵌套它们,则需要中间元素。

We use TailwindCSS’ implementation for cards. Each card represents one entry and it shows the cover image, the title, and a list of tags. We can click on the card to view the post.

我们对卡使用TailwindCSS的实现。 每张卡代表一个条目,并显示封面图像,标题和标签列表。 我们可以单击该卡以查看该帖子。

For handling events, AlpineJS provides the x-on directive, e.g.: x-on:click. We can use the @ shorthand, in our example, to load the post when we click on the card we have @click="loadEntry(post.id)".

为了处理事件,AlpineJS提供了x-on指令,例如: x-on:click 。 在我们的示例中,我们可以使用@简写形式来加载帖子,当我们单击具有@click="loadEntry(post.id)"

To show the tags, we use x-text. This property will bind our selected data to the innerText attribute of the DOM element.

为了显示标签,我们使用x-text 。 此属性会将我们选择的数据绑定到DOM元素的innerText属性。

帖子查看 (Post View)

The post view is going to download the HTML from our backend and show it with our own styiling.

帖子视图将从我们的后端下载HTML并以我们自己的样式显示它。

<template x-if="entry.loaded">
    <div id="post-area" :class="{ 'dark': darkMode }">
      <div class="w-full pt-6 flex justify-center">
        <button :disabled="entry.prevId === null" class="py-2 px-4 rounded-l border-r" title="Previous Post" @click="loadEntry(entry.prevId)">
          <i class="fa fa-arrow-left"></i> Previous
        </button>
        <button class="px-4 text-2xl" title="Post List" @click="entry.loaded = false">
          <i class="fa fa-home"></i>
        </button>
        <button :disabled="entry.nextId === null" class="py-2 px-4 rounded-r border-l" title="Next Post" @click="loadEntry(entry.nextId)">
          Next <i class="fa fa-arrow-right"></i>
        </button>
      </div>
      <div id="post-content" class="pb-4 px-16 md:px-24 md:px-40 xl:px-56" x-html="entry.data"></div>
    </div>
  </template>

When the post is loaded, we are going to show buttons to go to the previous or next post, and to go home. We can bind to the disabled property and disable the button if there is no previous or next post. And when we bind a handler to an event, we do not need to attach a function from our scope. In our case, to go home we indicate that there is no loaded post.

帖子发布后,我们将显示按钮以转到上一个或下一个帖子,然后返回首页。 如果没有上一个或下一个帖子,我们可以绑定到disabled属性并禁用按钮。 而且,当我们将处理程序绑定到事件时,我们不需要从我们的作用域附加函数。 在我们的例子中,要回家,我们表明没有职位。

To show the HTML data, we use AlpinJS’ x-html directive. This directive binds to the DOM element’s innerHTML attribute. We need to be careful with what we bind to x-html to avoid XSS attacks.

为了显示HTML数据,我们使用AlpinJS的x-html指令。 该指令绑定到DOM元素的innerHTML属性。 我们需要谨慎对待绑定到x-html以避免XSS攻击。

造型 (Styling)

We can apply any style we want for our blog. In our case, we used mostly TailwindCSS classes. For the styles we defined ourselves, there is an interesting trick to improve initial loading time.

我们可以为博客应用任何样式。 在我们的案例中,我们主要使用了TailwindCSS类。 对于我们自己定义的样式,有一个有趣的技巧可以缩短初始加载时间。

If we put all our styles in the index.html, the file grows with them, and the user needs to wait until the file is fully downloaded to start seeing our blog. If we put all the styles into an external file index.css, we have a similar result, since the browser is going to wait until all the link elements are loaded before start rendering.

如果我们将所有样式都放在index.html ,则文件会随着它们一起增长,用户需要等到文件完全下载后才能开始查看我们的博客。 如果将所有样式放入外部文件index.css ,我们将得到类似的结果,因为浏览器将等到所有link元素加载完毕后再开始渲染。

We define a mixed approach. We put the critical styles, those that are applied immediately, in the index.html and we load the rest in an external file, changing the media attribute to print. This will make it so the browser downloads the file in parallel, at the same time it already renders the page. To make it so the downloaded styles apply to everything and not only to printed pages, we add onload="this.media='all'".

我们定义了一种混合方法。 我们将关键样式(立即应用的样式)放在index.html ,并将其余样式加载到外部文件中,将media属性更改为print 。 这样一来,浏览器就可以并行下载文件,同时已经渲染了页面。 为了使下载的样式适用于所有内容,而不仅适用于打印的页面,我们添加了onload="this.media='all'".

<style>
  // Our styles
</style>
<link rel="stylesheet" href="./index.css" media="print" onload="this.media='all'" />

If you want to read more about this trick, check Vernon’s article.

如果您想了解有关此技巧的更多信息,请查看Vernon的文章

部署中 (Deploying)

Since this is a Single Page Application without any transpiling, we can use GitHub Pages, giving the index.html and all the files and assets needed.

由于这是一个没有任何转译的单页应用程序,因此我们可以使用GitHub Pages,提供index.html以及所需的所有文件和资产。

Thank you for reading! Now we completed both the frontend and backend for our blog. And we used new technologies for both!If I made any mistake or something is not clear, please leave a comment and I will answer as soon as possible.

感谢您的阅读! 现在,我们完成了博客的前端和后端。 如果我们有任何错误或不清楚的地方,请发表评论,我将尽快答复。

资源资源 (Resources)

翻译自: https://medium.com/javascript-in-plain-english/build-your-own-blog-with-alpinejs-757c81af6670

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值