Liquid-Shopify-主题自定义指南-全-

Liquid Shopify 主题自定义指南(全)

原文:zh.annas-archive.org/md5/f863fbf44becdd3e883527e06db8487c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

现在,我们通常通过论坛和博客上找到的各种信息来学习新知识,并依赖于我们遇到的信息。根据在这些文章上花费的时间,你可能会跳过一些基本概念,甚至当我们发现一些新的、更简单的方法来做某事时,即使是经验丰富的程序员也会感到惊讶。本书涵盖了 Liquid 的基本知识和一些高级概念,这将为您在 Liquid 技能组合中奠定正确的路径。

Liquid 是由 Shopify 联合创始人兼首席执行官 Tobias Lütke 创建的开源项目。作为一种模板语言,Liquid 变量将 Shopify 店铺的数据与我们的主题中的静态 HTML 内容连接起来,使我们能够将静态模板转变为一个完全动态且强大的电子商务店铺,产生令人印象深刻的结果。自 2006 年以来,Liquid 一直在成长和发展。如今,许多不同的网络应用程序都依赖于 Liquid,Shopify 就是其中之一,这表明对 Liquid 的需求一直在增长。

我们可以将本书分为三个主要部分。在第一部分,我们将熟悉一些关于 Shopify 的基本信息,了解 Shopify 界面及其主题结构,并开始熟悉 Liquid。尽管这些话题可能听起来并不那么重要,但它们将使我们能够在基础层面上理解 Shopify 和 Liquid。通过理解这些基础知识,我们将学习如何面对这些领域不可避免且频繁出现的问题。

本书第二部分将完全致力于 Liquid 的核心功能,没有这些功能,我们将无法创建许多复杂而精彩的组件。虽然我们不会详细介绍每一个对象、标签和过滤器,但我们将了解 Liquid 核心的本质,并关注一些常用的开发技术。

本书第三部分和最后一部分将带我们进入幕后,我们将学习如何使用 JSON 创建易于配置的选项,这些选项是 Shopify 电子商务店铺的灵魂。最后,我们将学习如何结合之前介绍的 Liquid 功能使用 Shopify Ajax API 来创建强大的功能,并使我们的代码更加动态。

本书面向对象

本书面向初学者和有经验的 CMS 开发者,他们想了解如何使用 Liquid 与 Shopify 主题一起工作并定制这些主题。致力于设计专业电子商务网站的 Web 开发者也会发现本书很有用。除了熟悉标准网络技术(HTML、CSS 和 JavaScript)外,本书不需要对 Shopify 或 Liquid 有任何先前的知识。本书涵盖了从 Shopify 基础知识、Liquid 核心到 REST API,以及可能对熟练开发者来说也是新的最新 Liquid 功能。

本书涵盖内容

在第 xvi 页“分享您的想法”, 从 Shopify 开始入门,为理解 Shopify 是什么,它是如何工作的,以及其他重要知识奠定了坚实的基础。这种先理论后直接进入语法的做法可能听起来很有吸引力,但我们都知道,即使是微小的涟漪也可能在未来引起相当大的问题。虽然我们不会深入探讨 Toby Lütke 是如何创建 Shopify 的,但我们将涵盖一些基本主题,包括创建私有开发商店、创建子主题、解释主题结构,以及其他我们需要了解的基本主题。

第二章, Liquid 的基本流程,帮助我们通过解释 Liquid 语法来学习什么是 Liquid 以及如何编写它。我们还将回顾逻辑和比较运算符,我们可以在 Liquid 中使用的数据类型,什么是处理程序,以及其他基本概念。通过学习如何使用逻辑运算符和操作处理程序属性,我们还将学习如何将它们组合起来以创建各种动态功能。

第三章, 使用标签深入液态核心,涵盖了编程逻辑,简而言之,就是标签。标签有很多种类型,例如控制流标签,它允许我们根据各种条件通过迭代标签重复运行代码块来输出一个 Liquid 代码块。此外,我们还将了解我们可以使用的不同类型的变量标签来存储数据,以及允许我们渲染特定主题标签的主题标签。

第四章, 使用对象深入液态核心,帮助我们理解内容对象是什么,为什么它们是强制性的,以及如何使用它们,这是创建未来模板的第一步。之后,我们将转向全局对象,了解这些对象将为我们打开一扇新的大门,并在任何页面上输出我们的动态内容。最后,我们将学习什么是元字段对象,并利用它们在任何页面上输出独特的内容。

第五章, 使用过滤器深入液态核心,是 Liquid 核心的一个关键主题,它将允许我们创建或操作不同类型的数据,这是一个令人兴奋的功能。通过 Liquid 过滤器,我们将更深入地了解 Liquid 是如何工作的,以及我们如何轻松地输出动态数据,例如图像 HTML 标签、计算产品折扣和处理字体变体。

第六章配置主题设置,帮助我们了解 JSON 设置以及为什么这些文件对我们如此重要。稍后,我们将通过学习我们可以在 JSON 文件中使用哪些类型的设置以及基本和专用输入设置之间的区别,更直接地探讨这个主题。

第七章与静态和动态部分合作,将帮助我们了解部分是什么以及如何使用它们通过 Shopify 主题编辑器创建易于配置的内容。我们将学习如何创建静态和动态部分及其对应的块,这些块在主题开发中起着相当重要的作用。最后,我们将了解新引入的 JSON 类型模板以及我们如何将它们与元字段结合,以在任意页面上创建真正独特且易于配置的内容。

第八章探索 Shopify Ajax API,将带我们了解 Shopify Ajax API,并解释其要求、限制和可能的用例。此外,我们将学习如何向 Shopify API 发送不同类型的请求。我们将学习如何检索产品信息,将任意数量的产品添加到购物车会话中,并读取购物车的当前内容。最后,我们将学习如何通过 Shopify API 发送请求并检索创建产品推荐和预测搜索功能所需的信息。

附录常见问题解答,包含针对刚开始进入 Shopify 和 Liquid 世界的开发者的额外指南、建议和答案。

评估,包含所有章节的问题答案。

为了充分利用这本书

Shopify 是一个托管服务,它不需要任何特定的设置或软件/硬件。然而,为了充分利用这本书,开发者应该已经熟悉基本的 HTML 标记、CSS,并理解 JavaScript 脚本语言,这些我们将在本书的后续部分需要。

如果您使用的是这本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。

虽然篇幅可能较短,但我衷心希望您会喜欢我们这次的小冒险,并在过程中学到一些东西。我的初衷是编写一本包含大量理论知识,并辅以实际例子和建议的书,教您如何与真实项目合作,让您看到所有内容是如何相互关联的。

下载示例代码文件

您可以从 GitHub 在 https://github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid 下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。

我们在丰富的图书和视频目录中还有其他代码包可供选择,请访问github.com/PacktPublishing/。查看它们!

操作视频

本书的相关操作视频可以在bit.ly/3nHIQtD查看

下载彩色图像

我们还提供了一份包含本书中使用的截图和图表的彩色 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781801813969_ColorImages.pdf

使用的约定

本书中使用了多种文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“我们可以通过包括翻译键作为name属性值,轻松地将schema标签内的大多数属性进行翻译。”

代码块应如下设置:

{% section "related-product-1" %}
{% section "related-product-2" %}

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

{% schema %}
{
  "name": "Announcement bar"
}
{% endschema %}

任何命令行输入或输出都应如下编写:

_9VUPq3SxOc
youtube

粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“通过添加部分按钮添加到主题的任何部分都将允许我们为任何出现包含不同的内容,并且我们可以重复任何次数。”

小贴士或重要注意事项

看起来像这样。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请通过customercare@packtpub.com给我们发邮件,并在邮件主题中提及书名。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。

盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com与我们联系,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问authors.packtpub.com

分享您的想法

一旦您阅读了《使用 Liquid 进行 Shopify 主题定制》,我们非常期待听到您的想法!请点击此处直接访问此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都非常重要,并将帮助我们确保我们提供高质量的内容。

第一部分:Shopify 解释

在本节中,我们将探讨一些关于 Shopify 的理论知识,包括主题结构、创建开发商店以及如何处理主题。在熟悉了 Shopify 之后,我们将开始学习 Liquid,了解其语法如何工作,以及我们如何使用逻辑和比较运算符来操作我们可用的各种数据类型。

本节包括以下章节:

  • 第一章, Shopify 入门

  • 第二章, Liquid 的基本流程

第一章:第一章:Shopify 入门

从互联网的黎明开始,人们就看到了拥有信息触手可及的便利性。从那时起,人们一直在努力创建各种互联网应用程序和全面的服务,使我们的生活更加便捷,随之而来的是电子商务商店。因此,Shopify 诞生了。

第一章为理解 Shopify 是什么以及它是如何工作的奠定了坚实的基础,而根据实践,我们经常跳过这一点。直接跳入语法而省略理论的方法可能听起来很有吸引力。然而,即使是微小的涟漪也可能在长期内引起相当大的问题。虽然我们不会深入探讨 Toby Lutke 如何创建 Shopify,但在本章中,我们将涵盖以下主要主题:

  • 什么是 Shopify?

  • 如何开始?

  • 导航管理面板

  • 管理主题

  • 理解主题结构

到本章结束时,我们不仅将学习 Shopify 是什么,还将学习如何在 Shopify 合作伙伴计划下创建账户,创建一个用于练习的开发商店,学习如何导航 Shopify 管理员界面,创建子主题并理解其结构。有了这些知识,我们将为如何进一步踏上我们的学习之旅,在 Shopify 上定制主题打下坚实的基础。

技术要求

考虑到 Shopify 是一个托管服务,我们将需要互联网连接来能够遵循本章中概述的步骤,尽管每个主题都将得到解释和伴随的图形展示。

什么是 Shopify?

不论是开发者还是通过一般使用互联网,Shopify 这个名字很可能至少出现过一次,但 Shopify 是什么? Shopify 是一家总部位于加拿大渥太华的跨国电子商务公司,为其实客户提供各种全面的服务。这项基于订阅的服务提供从购买域名到轻松构建和管理未来梦想商店的一切。

在其存在的多年里,Shopify 已经证明它不仅仅是一个商店构建者或销售产品的工具。相反,它已经确立了自己作为一个电子商务巨头,允许任何人构建商店并为他们的购物者创造独特的体验。作为一个基于模板的商店构建者,Shopify 提供了各种免费或付费主题,以实现商店定制,使您能够在没有开发知识的情况下使用直观且简单的主题编辑器。然而,如果最终目标是创建一个具有各种定制选项的独特商店,那么我们将需要一个了解 Liquid 的开发者来定制主题并在代码编辑器中创建额外的选项。

如何开始?

学习和开始在 Shopify 上工作的第一步是了解 Shopify 合作伙伴计划。合作伙伴计划是由 Shopify 创建的一个平台,它汇集了来自世界各地的人们,并为他们提供了为店主建立新的电子商务商店、设计主题、开发应用程序以及将新客户推荐给 Shopify 的能力。该平台最吸引人的扩展之一是它将允许我们创建一个开发商店来练习我们的 Liquid 知识。

在创建我们的开发商店并熟悉 Shopify 之前,我们首先需要在 Shopify 合作伙伴计划内创建一个账户。创建账户是一个相对直接的过程,我们可以在以下页面开始:www.shopify.com/partners:

  1. 我们可以通过点击页眉右上角的立即加入按钮或点击登录按钮来访问现有账户,开始这个过程。在创建账户页面和随后的账户信息页面填写基本信息后,我们将首次看到合作伙伴仪表板:图 1.1 – Shopify 合作伙伴仪表板的示例

    图 1.1 – Shopify 合作伙伴仪表板的示例

  2. 在创建账户之后,下一步将是创建我们的开发商店。我们可以通过访问左上角的商店链接并点击添加商店按钮来完成此操作,这将提示我们填写一个需要填写的商店注册表单:

图 1.2 – 创建开发商店的示例

图 1.2 – 创建开发商店的示例

大多数选项都不需要简化,因为它们是自我解释的,但我们需要分析最重要的一个选项,即商店类型,它包含两个选项:

  • 开发商店

  • 托管商店

为了我们的学习目的,我们将使用开发商店。然而,我们还将提供一个简短的解释,说明托管商店选项的含义以及何时可以使用它。

托管商店选项将使我们能够请求对现有商店的协作者访问权限。选择此选项将提示另一个名为权限的部分,在那里我们需要选择我们请求的访问类型。通常,我们可以选择请求访问客户商店的所有区域。但是,为了在客户商店上进行主题定制,我们需要访问在线商店下的主题选项。

一旦我们选择了执行工作所需的权限级别并输入了我们请求访问的商店的 URL,剩下的就是通过点击保存按钮发送请求。商店所有者将收到商店访问请求的通知,然后可以选择授予或拒绝访问。

开发商店选项将允许我们创建一个全新的商店,在那里我们可以练习我们的 Liquid 技能,在它们被推广到 Shopify 实时店面之前测试新功能,或者创建一个我们稍后可以转交给客户的商店。

重要提示:

虽然创建开发商店完全免费,但在将商店转移到客户时,主题将自动失去开发状态。我们转让所有权的人需要选择一个持续计划,这样最初创建主题的开发者将收到持续佣金,直到他们支付订阅计划。

让我们通过选择开发商店作为商店类型并填写商店信息来创建我们的开发商店。

小贴士:

开发者预览是 Shopify 最近推出的一项新功能,将允许我们在它们到达实时店面之前预览他们推出的创新。然而,我们将放弃使用此选项。

填写登录信息、商店地址并选择商店用途后,按保存按钮创建您的商店。

成功!我们的商店已准备就绪,并且随着它,我们准备熟悉 Shopify 管理面板。

导航管理面板

通过创建一个新的开发商店,我们将首次看到我们的商店主页。主页由中间屏幕组成,其中将包含一些一般建议,帮助商店所有者开始他们的业务,日常任务,最近的活动,并且在屏幕的左侧,我们将看到侧边栏,这是我们主要关注的焦点。

![Figure_1.3 – Example Shopify admin home page

![Figure_1.3_B17606.jpg]

图 1.3 – 示例 Shopify 管理主页

虽然我们一开始不会深入细节,但对我们将在本书的不同章节中引用的商店的每个区域有一个基本了解是至关重要的。为了便于参考,我们将侧边栏分为三个部分,可以列出如下:

  • 核心方面

  • 销售渠道

  • 设置

核心方面

第一部分,核心方面,包含商店所有者通常使用的与商店相关的选项,包括以下信息:

  • 订单部分包含商店所有者收到的所有订单信息。在这里,商店所有者可以逐个预览每个订单并决定是否继续处理。所有者还可以使用草稿方法手动创建订单并预览被遗弃的结账。

  • 产品部分由四个独立区域组成,允许我们创建和管理产品,处理现有产品的进货,如果您愿意,还可以创建和管理收藏或产品类别,以及创建礼品卡。

  • 客户部分,正如其名称所示,允许我们创建和管理我们的客户数据库。这个部分,连同之前的产品部分,将特别引起我们的兴趣。我们将在稍后返回并详细解释一些我们将感兴趣的功能。

  • 下一个部分,名为分析,主要将由店主使用。它提供了大量有关商店性能的信息,例如详细的销售报告,以及客户访问商店的实时视图及其行为。

  • 如其名称所示,营销部分允许我们查看商店市场策略概述。我们可以通过电子邮件或其他社交网络创建和管理活动,并创建自动化以提高商店的留存率。

  • 我们可以使用折扣部分创建一个折扣券代码,我们可以将其与客户分享,让他们在结账时手动输入以获得整个订单或特定产品的折扣。此外,我们还可以设置一个自动折扣,一旦我们满足店主设定的要求,它就会自动触发。

  • 最后一个部分,名为应用程序,为我们提供了一个所有安装在商店中的应用程序的快速预览,我们可以选择管理或删除应用程序。

    重要提示:

    由于 Shopify 平台限制,无法合并折扣。如果我们已经符合了 10%的自动折扣条件,我们将无法输入之前从店主那里获得的免费送货手动折扣券代码。

    小贴士:

    虽然可能,但在管理类型商店的协作账户下工作时,你绝不应该自行安装应用程序。假设你需要一个特定的应用程序来完成分配给你的任务。在这种情况下,你应该联系店主,解释安装它的必要性,并要求他们在你继续之前为你安装,因为他们需要授予特定的权限并与应用程序共享商店数据,该应用程序应由店主审查和接受。

销售渠道

我们侧边栏的第二部分,销售渠道,代表我们可以用来销售商店产品的各种平台。默认情况下,唯一可见的渠道是在线商店,这将是我们的主要目标;然而,我们可以通过点击销售渠道旁边的加号按钮轻松添加更多。

在线商店渠道是商店的核心,因为它为我们提供了向客户展示可见店面能力,并且我们可以将其分解为六个独立的部分:

  • 主题部分允许我们通过自定义商店主题来管理我们的商店外观,使其符合我们独特的品牌。一旦我们打开主题部分,有时我们首先看到的是 Shopify 的备注,说明我们的在线商店是密码保护的,这意味着商店尚未对客户可见。在密码保护设置期间,每个试图查看我们商店的访客都只会看到一个通知,说明商店是密码保护的,目前无法访问。

    重要备注:

    我们可以轻松地通过点击主题部分中的密码保护备注上的查看商店密码按钮,或者在在线商店部分的偏好设置部分访问,来禁用密码保护。然而,考虑到我们已选择开发选项作为我们的商店,禁用密码保护不可用。我们只能在将商店转移到客户或购买我们自己的订阅计划之后才能移除密码保护。

    在密码保护备注之后,我们可以在主题部分下看到下一个区域,名为当前主题。该部分显示了当前主题的名称和一个小预览,接着是在线商店速度部分,它为我们提供了商店的速度报告。在线商店速度部分目前对密码保护的商店是禁用的。

    在上一节之后,在屏幕底部附近,我们可以找到最后一个区域,名为主题库。我们可以通过选择相应的链接或通过选择上传主题按钮上传我们自定义的主题,轻松地在这个区域探索免费和付费主题。

  • 博客文章部分允许我们管理和创建我们希望在商店中展示的博客文章,并将它们分类到不同的博客下。

  • 页面部分允许我们创建客户经常访问的多个页面,例如关于我们联系我们页面,或者提供深入信息的支持我们产品的页面。有关管理页面的更多信息,请参阅help.shopify.com/en/manual/online-store/pages

  • 导航部分,我们可以找到创建最多两层嵌套菜单所需的工具,我们的客户可以使用这些菜单在在线商店中导航。有关创建导航菜单和管理链接列表的信息,请参阅help.shopify.com/en/manual/online-store/menus-and-links

  • my-store-name.myshopify.com。此外,我们可以通过点击购买新域名按钮购买自定义域名,或者如果我们通过第三方获得了域名,我们然后可以将其设置为我们的主要域名。有关域名的更多信息,请参阅help.shopify.com/en/manual/online-store/domains

  • 在我们的在线商店频道下的最后一个和最终部分被命名为偏好设置。由于该部分下的大多数选项都是自我解释的,我们将不会深入太多细节,以保持书籍的要点。然而,如果您想了解更多关于每个选项的信息,您可以访问它们各自的页面,我们将列出,以获取更多信息。偏好设置部分允许我们调整一些重要的设置,这将有助于店主们管理未来的商店,我们可以以下列方式列出:

设置

我们侧边栏的第三部分和最后一部分被命名为设置,包含许多选项,可以帮助店主设置和运营他们的商店。由于设置部分下的部分数量众多,选项也很多,我们不会涵盖所有内容,但将在本书的一些后续章节中提及一些对我们有意义的选项。

管理主题

我们已经在 主题 部分提到了这个话题,但 什么是主题? 主题 是一个主模板文件,它控制着您的商店布局,使我们能够通过编辑代码或通过此模板编辑主题编辑器设置来更改店面布局。

默认情况下,在创建新商店时,会自动添加一个名为 debut 的主题作为起始主题。然而,为了我们的学习目的,我们将尝试安装一个我们自己的主题:

  1. 我们需要做的第一件事是在 在线商店 销售渠道中的 主题 部分定位自己。一旦进入,向下滚动到名为 主题库 的区域,并搜索并点击名为 探索免费主题 的按钮:图 1.4 – 探索免费 Shopify 主题

    图 1.4 – 探索免费 Shopify 主题

  2. 通过点击 探索免费主题 按钮,我们将看到一个弹出窗口,其中包含八个可添加到我们的商店的免费 Shopify 主题和已安装的 Debut 主题,该主题带有标签 当前图 1.5 – 选择名为 Minimal 的免费 Shopify 主题

    图 1.5 – 选择名为 Minimal 的免费 Shopify 主题

  3. 虽然我们可以选择这些主题中的任何一个,但让我们通过点击它来选择第三行中第二列的 Minimal 主题。

    与大多数主题一样,Minimal 主题包含多种风格,如 现代复古时尚。虽然所有这些都是不错的选择,但为了我们的学习目的,我们将选择 现代 选项。一旦您选择了 现代 选项,请点击 添加到主题库 按钮以完成此过程并将新选定的主题添加到我们的商店主题库中:

    图 1.6 – 选择所选主题的风格并将其添加到主题库

    图 1.6 – 选择所选主题的风格并将其添加到主题库

    根据您的网络连接,Shopify 在您的商店中加载新主题可能需要几秒钟,之后您将收到通知,表明 Minimal 主题已成功添加到您的商店。尽管我们已经为我们的商店添加了新的主题,但 Debut 主题仍然是我们的实时主题,而如果我们查看主题库,我们将看到我们新添加的 Minimal 主题。要更改这一点,我们需要将我们的新主题设置为当前主题。

  4. 我们可以通过向下滚动到我们想要实时发布的主题来发布新的主题。在我们的例子中,这个主题是 Minimal。点击 操作 按钮,然后点击 发布 按钮,之后将出现一个弹出窗口,要求确认发布 Minimal 主题。第二次点击 发布 按钮以确认我们的选择,这将自动发布并将 Minimal 主题设置为我们的商店的当前主题:![图 1.7 – 实时发布新的 Shopify 主题 图 1.7 – 图 1.7_B17606.jpg

    图 1.7 – 在 Shopify 上发布新的主题

  5. 现在我们已经安装了新的主题,是时候预览我们的新主题在店面中的外观了。我们可以通过点击 操作 按钮来完成此操作:![图 1.8 – 预览主题店面 图 1.8 – 图 1.8_B17606.jpg

    图 1.8 – 预览主题店面

    然而,这次,我们应该点击的 操作 按钮将是在我们的当前主题上,而不是主题库中,然后选择 预览 按钮,这将在新标签页中打开我们的商店预览。

    如我们从为我们打开的预览页面中可以看到,店面看起来并不吸引人,因为它缺少内容。我们只能看到一些带有占位符图像的默认部分。

  6. 在我们对店面进行任何更改之前,我们首先应该创建一个 副本主题 来测试我们的更改,而不用担心它会崩溃我们的实时店面并给我们带来损害。然而,在我们开始复制过程之前,让我们通过再次发布来将 Debut 主题作为我们的当前主题。无论如何,我们都更喜欢 Debut 主题,此外,它将帮助我们练习我们刚刚学到的知识。

    小贴士:

    在对主题进行任何重大修改之前,创建一个新的副本主题应该是我们的首要考虑。拥有多个主题副本将帮助我们确定任何由应用程序或简单的疏忽引起的潜在问题,这些问题将破坏我们的实时店面,这在某个时候不可避免地会发生。

  7. 在设置 Copy of Debut 之后。

    重要提示:

    每次决定进行重大修改时创建一个副本主题是鼓励的。然而,我们应该记住,Shopify 只允许每个店面最多有 20 个副本主题。当我们达到这个限制时,我们将收到通知,表明我们已达到每个店面 20 个副本主题的限制。此限制也将阻止我们创建新的主题副本,如果我们想再创建一个,我们需要删除一些我们不再使用的较旧的主题副本。

  8. 默认情况下,Shopify 将自动将我们要复制的主题名称作为主题名称,并在其前面添加“副本”一词。鉴于拥有多个类似命名的主题可能会迅速失控,我们应该立即将我们的新主题重命名,以避免未来可能出现的混淆。

  9. 点击我们新创建的 Debut 的副本 主题上的 操作 按钮,并选择 重命名选项

![图 1.10 – 重命名主题及其确认示例图 1.10 – 图 1.10_B17606.jpg

图 1.10 – 重命名主题及其确认示例

此操作将自动弹出一个窗口,您可以在其中输入新名称。Shopify 为我们提供了最多 50 个字符来命名我们的新主题,这为我们选择合适的名称提供了足够的空间。您应该包括补充信息,例如日期和包含的自定义内容。在我们的例子中,我们将主题命名为Debut - Learning Shopify - 19 Apr '21,然后我们将点击重命名按钮以确认我们的选择。

现在我们已经学会了如何创建和重命名重复主题,是时候深入了解我们新创建的主题以及它是如何工作的了。

理解主题结构

为了开始熟悉主题结构,我们首先需要打开代码编辑器。我们可以通过点击Debut - Learning Shopify - 19 Apr '21主题然后点击编辑代码按钮来打开代码编辑器。

我们可以将代码编辑器分为以下两个部分:

  • 标题

  • 侧边栏

标题

我们可以在页面的顶部找到标题部分,它包含主题的名称,左侧有退出编辑器的箭头按钮,右侧有三个按钮,名称如下:

  • 预览

  • 定制主题

  • 专家主题帮助

预览

我们列表中的第一个按钮,即预览按钮,将允许我们快速预览我们正在工作的重复主题以及我们所做的任何更改。虽然我们当前的主题预览只包含占位符内容,但它确实包含一个对我们非常有用的元素,即预览栏

![图 1.11 – 预览屏幕和预览栏的示例图 1.11 – 预览屏幕和预览栏的示例

图 1.11 – 预览屏幕和预览栏的示例

我们可以在预览屏幕的底部找到预览栏,它包含我们正在预览的主题名称,右侧包括三个按钮:

  • 预览按钮将自动关闭我们的重复主题预览,并将我们重定向到我们的实时主题主页。

  • 点击分享预览按钮将触发一个弹出窗口,我们可以与任何人分享,以向他们展示我们在同一主题上所做的更改。虽然拥有此链接的任何人都可以查看您新主题的所有方面,但他们将无法完成任何购买或到达结账页面。此外,自动生成的预览链接仅持续 14 天,这意味着在 14 天后,您需要通过重复预览步骤并再次与您选择的任何人分享来生成新的预览链接。

  • 我们预览栏中的最后一个按钮被称为隐藏栏,正如其名称所暗示的,它允许我们隐藏预览栏,这样我们就可以在没有视觉障碍的情况下预览我们的更改。请注意,预览栏将在刷新页面时自动显示。

定制主题

在我们编辑器的 标题 区域下,下一个项目是 自定义主题 按钮,它将打开另一个不同类型的编辑器,即主题编辑器。在这个编辑器内,我们可以更新一些主题设置,例如字体、颜色和媒体链接,甚至管理店面部分。

专家主题帮助

最后,专家主题帮助按钮是店主将使用它来通过 Shopify 合作伙伴计划发布工作请求的东西,在本书结尾时,您将作为 Shopify 专家等待。

侧边栏

代码编辑器内的第二部分,侧边栏,列出了我们将要引用的所有文件和目录;然而,目前我们无法看到所有目录。我们可以通过点击 布局模板 目录来折叠它们:

图 1.12 – 在代码编辑器内折叠目录

图 1.12 – 在代码编辑器内折叠目录

折叠这两个目录后,我们会注意到目录图标内的两个箭头现在消失了,并且我们现在可以看到我们的主题包含的额外四个目录。Shopify 主题包含以下目录:

  • Layout

  • Templates

  • Sections

  • Snippets

  • Assets

  • Config

  • Locales

布局

布局 目录是我们主题的主要目录,因为它包含我们的商店工作所需的必要文件。这个目录通常包含多达四个文件,我们可以称之为主题布局模板,具体如下。

gift_card.liquid

gift_card.liquid 是一个模板文件,包含渲染礼品卡页面的代码,并在客户购买礼品卡时通过电子邮件通知发送给他们。

密码.liquid

password.liquid 文件模板渲染了在线商店密码页面,如果客户在商店处于开发模式时访问我们的商店,他们将会看到这个页面。我们在 导航管理面板 部分讨论 销售渠道 和其 在线商店 区域时提到了密码保护是什么以及如何禁用它。

为了更好地理解密码保护页面,让我们尝试预览它以查看其工作方式。您可以通过将商店的 URL,https://my-store-name.myshopify.com,和末尾添加单词 /password 来预览您的商店密码页面。

图 1.13 – Shopify 密码保护页面的示例

图 1.13 – Shopify 密码保护页面的示例

如我们所见,密码保护只包含最基本的信息。然而,它通过阻止人们查看我们的密码,仍然在开发商店中,成功地完成了我们为其设计的任务。

在我们的密码保护页面的右上角,我们会注意到一个按钮,使用密码进入,这将启动一个弹出窗口,我们可以使用我们的商店管理员凭据登录我们的商店,然后点击 在此登录 链接,这将将我们重定向到我们的管理面板:

图 1.14 – 密码页面登录表单

图 1.14 – 密码页面登录表单

然而,如果我们想通过输入密码来预览我们的主题店面呢?这个表单请求的密码就是我们为启用我们的密码保护页面所设置的密码。在我们的例子中,当我们将开发选项作为我们的商店类型选择时,系统会自动生成这个密码。

为了确定我们的密码保护页面的密码,我们需要通过在新浏览器标签的 URL 中输入 https://my-store-name.myshopify.com/admin 返回到我们的管理员面板。一旦进入,在左侧侧边栏下,在 销售渠道 下,通过点击它来展开 在线商店 字段,然后点击 偏好设置 选项。在名为 密码保护 的区域下,您将找到我们密码页面表单所需的密码,以便访问我们的店面:

图 1.15 – 密码保护页面设置

图 1.15 – 密码保护页面设置

假设我们返回到我们的密码保护页面并输入我们找到的密码。在这种情况下,密码保护页面将暂时为我们暂停,商店将根据我们最初打开的链接类型将我们重定向到预览或实时主题店面。

theme.liquid

在我们的代码编辑器内部,我们可以将列表中的下一个项目,命名为 theme.liquid 的文件,视为主布局文件,其中将渲染所有其他模板文件以及我们稍后将要了解的任何其他元素。

checkout.liquid

我们列表中的最后一个布局文件,命名为 checkout.liquid,目前在我们的开发商店中不可见。这个布局文件仅对购买了 Shopify P****lus 订阅的 Shopify 商店所有者可见。

通常,每个主题都包含一组预定义的选项,这将允许我们对结账页面进行一些基本的样式更改。然而,在我们拥有 checkout.liquid 布局文件的情况下,我们将能够访问我们的结账文件,在那里我们可以创建一些更复杂的修改,否则我们无法做到。

注意,即使我们手头有 checkout.liquid 文件,由于安全原因,我们也不能修改结账页面流程的流程。我们只能进行一些不会打断结账流程的基本修改。

我们只能通过向 Shopify 支持提交请求来激活 Shopify 计划。在审查我们的申请后,他们将为我们商店中启用此独特计划生成一个定制价格。

重要提示:

即使你在你的商店中激活了 Shopify Plus 计划,checkout.liquid文件也不会立即可见。相反,你需要向 Shopify 支持提交请求,并要求他们将此独特文件包含在你的商店中。

由于这些原因,我们不会过多地详细介绍checkout.liquid文件。然而,我们将涵盖布局文件包含的how-to和最基本元素,这应该能让我们走上理解布局文件的正确道路。有关编辑结账文件的更多信息,请参阅:shopify.dev/themes/architecture/layouts/checkout-liquid

模板

我们主题目录列表的下一个点是Templates,这是一个允许我们一次性创建和管理多个页面外观的文件组。Templates文件包括两种类型的文件:

  • Templates文件的第一种类型是.json类型的文件,这是 Shopify 的新增功能。使用.json类型的模板,我们可以通过主题编辑器轻松控制任何页面的布局。然而,为了更好地理解,我们不会在这里过多地深入细节。在接下来的章节中,我们将更详细地介绍.json模板及其可能性。

  • 第二种类型是.liquid类型的文件,这是一种简单的标记类型文件,我们将立即熟悉它。

实践表明,每个主题都为每种页面类型提供了一个模板文件,例如,product.liquid,系统将自动将其分配给任何当前或未来创建的产品页面。考虑到 Shopify 是一个基于模板的文件,对特定模板的任何更改都将影响我们之前分配此模板的任何页面。然而,Shopify 还允许我们为每种页面类型创建额外的模板文件,并进一步自定义它们,而无需更改原始模板文件布局。

我们可以通过点击位于模板目录下方添加新模板按钮来创建一个新的模板文件,之后会弹出一个窗口,询问我们为新模板文件选择类型和名称:

![图 1.16 – 创建新的模板文件

![img/Figure_1.16_B17606.jpg]

图 1.16 – 创建新的模板文件

在成功创建一个新的模板文件后,我们现在可以将新模板分配给为我们创建的新模板的页面。我们可以通过打开管理面板中的任何页面来实现,具体取决于我们创建的页面模板类型,然后在主题模板区域下方的模板后缀下拉菜单中选择新的模板名称。

重要提示:

模板后缀 下拉菜单只能从当前实时主题中读取值。这意味着新创建的模板文件在我们发布我们的副本主题或在我们当前的实时团队中创建相同的模板文件之前将不可见。如果我们选择第二种选择,请注意,我们需要使用相同的名称创建文件;我们不需要对文件的内容进行任何更改。

如果在我们的管理界面中没有看到 主题模板 区域,我们应该通过检查我们创建了哪种模板类型来确认我们是否在正确的页面上,因为 模板后缀 下拉菜单仅在创建了多个模板的页面上可见。

部分(Sections)和片段(Snippets)

在我们的目录列表中下一个值得关注的点是名为 Sections 的目录,这是一种不同类型的模板文件,当与模板文件结合使用时,允许我们创建 Shopify 因其而闻名的真正可定制的功能。请注意,在部分内部创建的任何变量将无法在部分外部访问,反之亦然。此规则的唯一例外是,如果部分包含片段,则提供单向通信。

片段 文件允许我们通过引用它们的名称在 模板/部分 中重用重复的代码片段。除了允许我们重用代码的部分外,片段 还将使我们能够访问父元素内部的变量,只要我们将这些变量作为参数传递给片段。

资产

如其名所示,Assets 目录允许我们存储任何与主题相关的资产,包括图像、字体文件、JavaScript、CSS 文件,并可以在整个主题文件中轻松引用它们。

配置(Config)

Config 目录在我们的主题中至关重要。在此目录中,我们可以定义和管理主题的全局 JSON 值。该目录包含两个关键文件:

  • settings_schema.json 文件允许我们在主题编辑器中创建和管理主题内的内容,我们可以在整个主题文件中引用这些内容。

  • 另一方面,settings_data.json 文件记录了我们方案文件中定义的所有选项及其值。您可以将此文件视为您的主题数据库,它将允许我们预览当前 JSON 值或通过在主题编辑器下更新主题设置或直接编辑 settings_data.json 文件中的值来修改它们。

本地化

我们列表中的最后一个目录,名为 Locales,包含主题本地化文件,我们可以使用它来翻译主题的内容。此目录可能包含的文件数量可能会有所不同。它可以有一个默认文件 en.default.json,或者根据您希望在您的商店提供的语言数量,它可以包含多个文件。

摘要

在本章中,我们通过学习 Shopify 是什么以及如何在 Shopify 合作伙伴计划中创建账户,了解了 Shopify 的基本要素,这些内容将贯穿我们的学习过程,并用于我们在 Shopify 平台上的任何未来工作。

我们创建了一个开发商店,并且也理解了在做出任何重大更改之前创建一个副本主题的重要性和原因。虽然我们涵盖的一些内容可能听起来不相关,但每一项都将帮助我们更好地理解作为 Shopify 开发者我们将要定期执行的常规工作流程。

最后,我们对主题文件的内结构有了些了解,这将在下一章中对我们非常有用,我们将熟悉 Liquid 的基础知识,并进一步深化我们对主题定制的知识。

问题

  1. 合作伙伴计划是什么?

  2. 我们如何禁用开发类型商店的密码保护?

  3. 布局模板目录文件之间的区别是什么?

  4. 在什么情况下,新的模板文件将在您页面的管理部分可见?

  5. 什么类型的文件和什么条件允许我们访问父文件作用域内的变量?

进一步阅读

第二章:第二章:液体的基本流程

本章将帮助我们了解 Liquid 是什么,学习 Liquid 的基础知识,为我们提供掌握它所需的基本知识。我们将把本章分为以下主题:

  • 什么是 Liquid?

  • 理解 Liquid 及其定界符

  • 学习比较运算符

  • 使用逻辑运算符

  • 理解数据类型

  • 控制空白字符

到本章结束时,我们将对 Liquid 逻辑有更深入的理解,我们将使用哪些运算符来操作各种类型的数据,以及 Liquid 移除那些讨厌的空白字符的方式。通过学习如何使用逻辑运算符和操作处理属性,我们将获得有关生产各种动态功能的有价值知识,并将我们的技能引向编写高质量和复杂代码的正确路径。

技术要求

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter02

本章的“代码在行动”视频可以在这里找到:bit.ly/3ArKxia

什么是 Liquid?

在上一章中,我们获得了对 Shopify 的初步了解。我们学习了 Shopify 是什么,如何创建 Shopify 合作伙伴账户,以及如何管理我们的主题。最后,我们学习了主题结构、它包含的目录以及我们 Layout 目录中的一些基本文件,这时我们可能会注意到我们的大部分文件都包含 .liquid 扩展名。那么,Liquid 究竟是什么呢?

Liquid 是由 Shopify 联合创始人兼首席执行官 Tobias Lütke 创建的开源项目。作为一个模板语言,Liquid 变量将 Shopify 商店的数据库连接到我们主题中的静态 HTML 内容,使我们能够将静态模板页面转变为一个完全动态且功能强大的电子商务商店,并产生令人印象深刻的成果。我们还可以将 Liquid 元素视为占位符,这些占位符只有在文件内的代码编译并发送到浏览器后才会填充适当的数据。

自 2006 年以来,Liquid 一直在发展和演变。如今,许多不同的网络应用程序都依赖于 Liquid,Shopify 就是其中之一,这表明对 Liquid 的需求一直在增长。学习 Liquid 是进一步扩展知识的好方法,并且凭借其易于学习的语法,我们将迅速掌握 Liquid 并能够创建复杂的功能。

理解 Liquid 及其定界符

我们可以区分液体文件的两个方法之一是通过扩展名 .liquid。作为一个模板语言,液体文件是静态内容和动态内容的组合:

  • 我们在 HTML 中编写的元素被称为 静态内容,无论我们当前在哪个页面上,它们都保持不变。

  • 另一方面,用 Liquid 编写的元素被称为动态内容元素,其内容会根据我们所在的页面而变化。

虽然我们的浏览器可以快速处理 HTML 代码,但它们不知道如何处理 Liquid 代码,因为它们不理解它。我们可以将提交 Shopify URL 到浏览器时发生的事情的流程分解为五个逻辑步骤:

  1. Shopify 服务器试图确定我们正在尝试访问哪个商店。

  2. 根据我们当前请求信息的页面类型,Shopify 会尝试从活动主题目录中定位并选择合适的 Liquid 模板。

  3. 在成功识别所需的 Liquid 模板后,Shopify 服务器开始用 Shopify 平台存储的实际数据替换占位符。

  4. 一旦 Shopify 完成替换占位符和执行所选模板中定位的任何逻辑,我们的浏览器将接收到编译后的 HTML 文件。

  5. 现在浏览器已经接收到 HTML 文件作为响应,浏览器开始处理文件并获取所有其他必需的资产,包括 JavaScript、样式表、图像等。

我们可以区分液体文件和代码的第二种方式是通过其两个定界符:

  • {{ }} 双大括号用于表示我们期望一个输出。以下是一个期望输出的 Liquid 代码示例:

    Our collection name is {{ collection.title }}. As we can see, collection.title is encapsulated inside the double curly braces indicating that the result of the code will be output. After the Shopify server processes our Liquid code and returns us something that our browser can work with, we would receive the following string as a result:
    
    

    我们的收藏夹名称是冬季鞋子

    
    
  • 另一方面,如果我们想表示我们正在执行某种逻辑,则使用带有百分比的{% %}大括号。

在我们最后的例子中,我们能够看到使用{{ collection.title }}恢复收藏夹名称的结果。现在,如果我们想显示收藏夹描述,但出于某种原因, 收藏夹描述 字段没有返回任何内容? 我们最终会得到一个不完整的信息:

Our collection description is

为了确保这种情况不会发生,我们可以使用 Liquid 逻辑和比较操作符以及一些 Shopify 数据类型来检查数据值是否存在。

学习比较操作符

使用 Liquid,我们可以访问七个比较操作符,我们可以将它们组合起来创建任何类型的逻辑流程,我们的代码需要。让我们按以下方式回顾它们:

  • == 操作符允许我们检查我们正在比较的元素是否等于某个值:

    {% if collection.title collection.title is strictly equal to our string of "Winter Shows", the logic returns true, and we will see our message shown. Otherwise, the logic returns false. Note that comparison will only return true if the string is an exact match, including the text case.
    
  • != 操作符与前面的操作符类似。区别在于,这个操作符检查我们正在比较的元素是否不等于某个值:

    {% if collection.title collection.title, but in this case, we will be checking whether the collection's name is not equal to the "Winter Shows" string. If the result is that collection.title is not the same as our string, the logic returns true, and we will see our message shown. Otherwise, the logic returns false, and the message will not be visible.
    
  • > 操作符允许我们检查比较值是否大于比较值:

    {% if collection.all_products_count 25. If it is, the logic will return true, and we will see our message shown. Otherwise, the logic returns false, and the message will not be visible.
    
  • 类似于前面的例子,我们将检查我们收藏夹中的产品数量,然而,在这种情况下,我们将使用<操作符,它仅在比较值小于被比较值时返回true

    {% if collection.all_products_count 25, the logic will return true, and we will see our message shown. Otherwise, the logic returns false, and the message will not be visible.We have a general understanding of how the `<` and `>` operators works, but what if we had the following example:
    
    

    {% if collection.all_products_count > operator 允许我们检查比较值是否大于比较值。如果是,逻辑返回 true,我们的消息将显示。然而,如果我们现在运行我们的代码,逻辑将返回 false,我们不会看到我们的消息。为什么?如果我们看我们的例子,我们会注意到我们有一个包含比较值的引号,这意味着我们的比较到的值是一个字符串,与我们的 collection.all_products_count 相比,它返回一个数字。正如我们之前提到的,比较运算符只有在满足运算符要求的精确条件时才会返回 true。在我们的当前例子中,为了使我们的消息可见,必须满足两个条件。第一个条件是两个值必须是相同的数据类型,这意味着我们不能像刚才那样混合两种数据类型。我们需要从我们的比较到的值中移除引号,从而将其从字符串转换为数字数据类型。第二个条件是我们的比较值大于比较到的值。一旦我们满足这两个条件,逻辑返回 true,我们的消息将显示。

    
    
  • >= 运算符允许我们检查我们比较的元素是否大于或等于比较值:

    {% if collection.all_products_count true, and we will see our message shown. Otherwise, the logic returns false, and the message will not be visible.
    
  • <= 运算符允许我们检查比较值是否小于或等于比较值:

    {% if collection.all_products_count true, and our message will show. Otherwise, the logic will return false, and the message will not be visible.
    

使用逻辑运算符

除了比较运算符之外,我们还可以访问两个逻辑运算符,它们允许我们将多个条件组合起来创建复杂的语句。我们可以将它们分为以下两个组:

  • or 运算符允许我们设置多个条件,其中我们必须满足至少一个:

    {% if collection.title == "Winter Shoes" Winter Shoes or if the collection contains more than 25 products. If we have met at least one of these two conditions, the logic will return true, and our message will be shown. Otherwise, the logic will return false, and the message will not be visible.
    
  • 类似地,and 运算符允许我们设置多个条件。然而,要使此运算符返回 true,所有条件都必须满足:

    {% if collection.title == "Winter Shoes" Winter Shoes and the collection contains more than 25 products. If we have met both of these conditions, the logic will return true, and the message will be shown. Otherwise, the logic will return false, and the message will not be visible.Important note:We can now create a conditional with multiple comparison values. However, we should note the order in which our conditional will perform the check is from the right side. We cannot change this order using the parentheses, as parentheses are invalid characters in Liquid and will break our code.
    
  • contains 是我们列表中的最后一个运算符。这个特定的运算符与之前的运算符不同,因为它不检查比较值是否等于比较到的值。相反,它允许我们检查一个字符串是否包含子字符串:

    {% if collection.title collection.title, which returns a string, contains the word "Christmas", and if it does, it shows the message. Note that contains is case-sensitive and will only return true if the sub-string strictly matches the part of the string.Important note:We can use `contains` to search for the presence of a substring inside a string or even to check whether our string is a part of the array of strings. However, we cannot use it to check for the presence of an object inside the array of objects. We can only use `contains` to search for the presence of strings.
    

我们已经学习了关于使用逻辑和比较运算符的很多知识。然而,我们只学习了如何将一个值与另一个已知值进行比较。那么我们如何检查一个比较值,例如一个集合描述,是否存在呢? 由于 collection.description 返回我们不熟悉的内容,我们无法用我们目前的知识来进行这项检查。要执行此操作,我们首先需要了解我们可以与 Liquid 一起使用的不同类型的数据。

理解数据类型

到目前为止,我们已经提到了两种数据类型,字符串数字。然而,在 Liquid 中,有六种不同的数据类型可供我们使用:

  • 字符串

  • 数字

  • 布尔值

  • 空值

  • 数组

  • EmptyDrop

字符串

字符串是我们用来表示文本的数据类型。由于字符串可以是字母、数字或特殊字符的任意组合,我们应始终用引号将其括起来:

{% if product.title contains "Book" or product.title  contains "2021" %}
  We have found a product that contains the word Book or    the product that contains the word 2021.
{% endif %}

在上一个例子中,我们检查产品标题是否包含字符串"Book"或相同的标题包含字符串"2021",如果是,我们的消息将显示。

数字

数字是一种不需要引号的数据类型,我们用它来表示以下两种数值数据:

  • 浮点数是一个带有小数点的浮点数,这意味着数字包含小数点。

  • 另一方面,整数int是一个不带小数点的整数:

    {% if product.price > 25 and product.price < 3500 %}
      The number of products in a collection is greater    than 25, and the product's price is lower than 3500.
    {% endif %}
    

在这个例子中,我们检查产品价格是否大于25,同时,低于and比较器,两个条件都必须为真。如果其中任何一个返回false,我们的消息将不会显示。

注意,我们两个比较值25product.price都不会返回。

布尔值

truefalse

{% if customer.accepts_marketing == true %}
  The customer has signed up for our newsletter!
{% endif %}

如我们从示例中看到的那样,与数字数据类型类似,布尔值不使用引号。如果customer.accepts_marketing已注册我们的通讯简报,对象将为真,我们的消息将可见。

Nil是一种在代码没有结果时返回空值的数据类型。由于nil不返回任何值,我们可以用它来检查语句的真实性。然而,我们应该注意,我们只能使用nil来检查值是否存在。我们不能使用nil来检查值的内 容:

{% if customer %}
  Welcome {{ customer.name }}!
{% endif %}

在上一个例子中,我们使用条件来检查访问我们商店的客户是否在我们的商店有账户,如果客户对象存在。请注意,我们没有在我们的客户对象后添加任何操作符或数据类型。原因是nil是一个特殊的空值。它不是一个包含文本nil的字符串或变量,因此不需要任何视觉表示。

我们可以将 Liquid 内部的值分为两类:

  • 所有在条件中使用时默认为true的数据类型都被认为是真值。请注意,即使是空值,如字符串,也默认被认为是真值。

  • 另一方面,所有返回false的数据类型默认被认为是falsenil

由于nil被认为是假的,除非客户在我们的商店有账户,否则我们的消息不会显示。否则,条件将返回true,我们的消息将显示。

虽然nil非常有用,但它不能回答我们所有的问题。如果我们回想一下,在我们之前的某个例子中,我们使用了collection.description来查看描述集合的内容:

Our collection description is {{ collection.description }}.

如果我们的集合描述中包含一些定义的内容,我们的消息将正确显示。然而,如果我们没有为我们的集合定义描述,那么collection.description将返回空,我们最终会得到一个不完整的信息:

Out collection description is.

如我们所回忆的,我们只能使用 nil 来检查诸如对象之类的元素是否存在。它不能检查其内容,但这究竟意味着什么?

{% if collection.description %}
  <h3>{{ collection.description }}</h3>
{% endif %}

在这个例子中,我们使用 nil 数据类型在条件中包裹我们的元素,以检查collection.description实例是否存在。然而,正如我们所看到的,这不是我们希望得到的结果:

<h3></h3>

由于 nil 只检查元素是否存在,在我们的情况下,我们的条件已经发现collection.description虽然为空,但确实存在。请记住,Liquid 中的所有值(除了 false 和 nil)默认都是真值,这意味着空字符串也被认为是真值。因此,尽管我们的collection.description实例为空并返回了一个空字符串(因为我们在一个条件中使用 nil),但结果是该值是真值。因此,条件内的代码是可见的。

为了解决这个问题,我们不应该检查值是否存在,而应该检查我们的值是否不为空:

{% if collection.description != blank %}
  <h3>The collection description element will show only if    its content is not empty.<h3>
{% endif %}

在上一个例子中,我们使用不等于参数对blank进行检查,以查看我们的集合描述是否不为空。如果这个条件的结果是collection.description不为空,我们将显示定义的消息。否则,条件将返回false,并且条件内定义的消息将不会渲染。

数组

数组是一种包含元素列表的数据结构,通常元素类型相同。请注意,我们无法仅使用 Liquid 来初始化我们的数组,但我们可以将字符串分解为子字符串数组,我们可以使用这两种方法之一来访问这些数据:

  • 访问数组内部数据的第一种方法是通过直接访问每个项目。

    由于,正如我们提到的,我们无法仅使用 Liquid 来初始化数组,因此在这个例子中,我们可以使用product.tags,这将返回一个字符串数组:

    [] combined with product.tags and the index position to access each item individually:
    
    

    {{ product.tags[0] }}

    {{ product.tags[1] }}

    {{ product.tags[2] }}

    {{ product.tags[3] }}

    {{ product.tags[4] }}

    
    Note that array indexing starts at `0`, so we use `product.tags[0]` to access the first element in our array. After submitting our code to the Liquid server, we receive the following list of strings:
    
    

    learning

    with

    packt

    is

    awesome!

    
    While this method produces results, it is only applicable when we are entirely aware of the content of our array and the exact position of the element that we require. If, on the other hand, we are looking to output the entire content of an array without too much writing, we would need to use a different method.
    
  • 要访问数组中的所有项目,我们需要遍历数组中的每个项目并输出它们的内 容:

    {% for tag in product.tags %}
      <p>{{ tag }}</p>
    {% endfor %}
    

    我们可以使用for标签重复执行代码块或遍历数组中的所有值并输出它们:

    for loop are precisely the same as when we previously accessed each item individually.
    

虽然使用循环遍历数组来快速输出数组比逐个调用数组中的每个项目要容易,但我们必须意识到这两种方法,这将允许我们稍后创建一些复杂的功能。

EmptyDrop

我们列表中的最后一个数据类型是 EmptyDrop,它将出现在我们尝试使用句柄访问之前已删除的对象或禁用的属性的情况下。

在我们学习最终数据类型之前,我们首先需要了解什么是句柄,以及如何找到它。

查找句柄

- 用于替换任何特殊字符和空格。类似于我们学习如何使用位置索引访问数组中的单个项目,我们也可以使用页面句柄来访问液态对象的属性。

我们现在理解了页面句柄是什么,但让我们通过在我们的商店中创建一个页面来尝试将其放入上下文中,看看我们刚刚学到了什么:

  1. 要创建新页面,我们首先需要通过访问 www.my-store-name.myshopify.com/admin 并使用我们的 Shopify 合作伙伴凭据登录来导航到我们的商店。另一种方法是访问 Shopify 合作伙伴网站 www.shopify.com/partners,并使用我们在上一章中创建的合作伙伴账户登录,然后在左侧侧边栏中点击 商店 按钮,然后通过点击商店名称旁边的 登录 按钮来登录我们的商店。

  2. 在成功登录我们的商店后,在侧边栏中,在 销售渠道 下方点击 在线商店 以展开菜单的附加部分,然后点击 页面 链接,将我们重定向到商店的 页面 部分:![图 2.1 – 访问页面部分 图片

    图 2.1 – 访问页面部分

  3. 一旦进入,由于我们的商店是新的且没有内容,我们唯一能看到的就是 添加页面 按钮。否则,我们会看到我们可以在商店中访问的所有页面的列表。要继续创建我们的第一个页面,请点击 添加页面 按钮。

  4. 在初始化创建新页面的过程后,我们将看到可以定义新页面内容的页面,包括名称、描述、可见性和页面应使用的模板。对于我们的当前目的,我们只需要输入我们页面的标题,确保我们选择 了解页面句柄 作为标题。我们还确保选择了 可见 选项,所以剩下的就是点击 保存 按钮:![图 2.2 – 在 Shopify 管理界面中创建和发布新页面 图片

    图 2.2 – 在 Shopify 管理界面中创建和发布新页面

  5. 现在我们已经创建了页面,我们可以通过点击窗口下方的 编辑网站 SEO 按钮来查看页面句柄,这将展开并显示我们页面的 SEO 信息,其中我们可以看到页面的句柄(以及其他选项):

![图 2.3 – 更新页面 URL 和句柄信息图片

图 2.3 – 更新页面 URL 和句柄信息

如我们所回忆的,句柄是一个以小写字母书写的页面标题,其中特殊字符和空格由连字符(-)替换。在我们的例子中,我们页面的句柄是learning-about-the-page-handle

重要提示:

如果由于某种原因,我们更改了我们现在创建的页面的标题,这将只会影响页面标题。句柄将保持不变,因为更改页面名称不会自动更新句柄。更改句柄的唯一方法是点击编辑网站 SEO,并在URL 和句柄字段中手动编辑句柄。

现在我们已经学习了页面句柄是什么以及如何管理它,我们可以从 EmptyDrop 数据类型离开的地方继续前进。

EmptyDrop 数据类型

如我们所回忆的,EmptyDrop 是由于我们试图访问之前已删除或禁用的对象的属性而产生的。请注意,EmptyDrop,就像 nil 一样,不是一个包含文本EmptyDrop的字符串,也不是一个变量,因此它没有可视表示。

我们可以通过将我们要访问的对象的名称复数化,然后跟随一个方括号([])或点(.)表示法来使用句柄访问对象:

<h1>{{ pages.learning-about-the-page-handle.title }}</h1> \
<h1>{{ pages["learning-about-the-page-handle"].title}}</h1>

无论我们选择哪种表示法,结果都将相同,但重要的是要提到两者,因为它们各自都有其目的,我们将在后面的章节中介绍:

<h1>Learning about the page handle</h1>
<h1>Learning about the page handle</h1>

我们现在已经学会了如何通过页面句柄访问对象并读取其属性。然而,如果我们回到我们的管理界面,通过将可见性选项切换到隐藏来禁用之前创建的页面,会发生什么? 这样的操作结果将是 EmptyDrop:

<h1></h1>
<h1></h1>

EmptyDrop 属性只有一个名为empty?的属性,它始终为真值。为了避免这个问题,我们可以创建一个条件语句来检查 EmptyDrop 是否为empty

{% if pages["learning-about-the-page-handle"].title !=  empty %}
  <div class="tester">{{ pages["learning-about-the-page-     handle"].title }}</div>
{% endif %}

如果对象不等于空,对于 EmptyDrop 来说,这始终为真值,那么我们的条件内的代码将渲染。否则,如果代码等于空,我们正在寻找的对象是空的,代码将不会渲染。

控制空白

在前面的章节中,我们学习了如何使用变量数据类型的条件来确保我们始终收到正确的值。然而,即使有了我们的条件,相同的值也可能伴随着一些不需要的空白:

Collection info:
{% if collection %}
  The collection's name is {{ collection.title }} !
{% endif %}

在前面的例子中,我们创建了一个条件,如果我们的集合对象存在,它将返回true,确保我们的消息不会不完整。虽然我们的结果看起来是正确的,但如果我们要检查页面,我们会看到事情并不完美:

Collection info:
  The collection's name is Winter Shoes !

如我们从先前的例子中可以看到的,我们已经成功恢复了集合信息。然而,我们可以看到我们的消息周围有大量的空格,这是由处理 Liquid 代码产生的。即使不是每个 Liquid 代码都会输出 HTML,默认情况下,每一行都会为每一行 Liquid 代码生成一行。

为了解决这个问题,我们可以在我们的语法标签内引入一个破折号,作为{{- - }}{%- -%},这允许我们去除任何不需要的空白字符:

Collection info:
{%- if collection -%}
  The collection's name is {{ collection.title -}} !
{%- endif -%}

在引入之前代码块中看到的破折号后,我们成功移除了条件渲染的两个空行:

Collection info:
The collection's name is Winter Shoes!

注意我们添加在条件两端的破折号如何移除了两边的空白字符。然而,我们只在collection.title的闭合括号上添加了一个破折号来移除其右侧的空格。如果我们同时在左侧也添加一个破折号,我们将移除分隔动词"is"和集合名称的空格。

摘要

在本章中,我们通过学习什么是 Liquid 以及如何通过学习阅读和编写其语法来了解 Liquid 的基础。我们已经理解了所有的 Liquid 逻辑和比较运算符,结合我们已覆盖的所有 Liquid 数据类型,这将帮助我们确保我们总是只接收我们期望的数据。随着我们进一步学习,这一点将变得越来越重要。

最后,我们学习了如何单独创建和访问每一页,这在下一章中对我们非常有用,我们将学习更多关于在本章中几次提到的一些对象。

习题

  1. 如果我们期望得到一个输出结果,我们应该使用哪种类型的分隔符?

  2. 以下条件的结果将是什么,原因是什么?

    {% if collection.all_products_count > "20" %}
      The number of products in a collection is greater    than 20!
    {% endif %}
    
  3. 访问属性内的一项有哪些两种方法?

  4. 使用其句柄访问对象的方法是什么?

  5. 在以下代码块中存在哪两个问题?

    {% if customer != nil %}
      Welcome {{- customer.name -}} !
    {% endif %}
    

第二部分:探索液态核心

本节将深入液态核心,它由三个主要特性组成——对象、标签和过滤器——这些特性将帮助我们为未来的学习打下坚实的基础。我们将学习如何使用我们可用的不同类型的对象,结合液态编程逻辑,将静态模板转变为一个完全动态的存储库。最后,通过过滤器的作用,我们将学习如何操纵各种类型的数据,甚至将一组简单的数据转变为复杂的功能。

本节包含以下章节:

  • 第三章使用标签深入液态核心

  • 第四章使用对象深入液态核心

  • 第五章使用过滤器深入液态核心

第三章:第三章: 使用标签深入 Liquid 核心

在前面的章节中,我们看到了一些 Liquid 标签的实际应用,例如控制流标签。在本章中,我们将学习更多关于我们可以用来动态修改页面内容的不同标签。我们将学习如何创建变量标签和主题标签以及最佳使用方法。

通过了解各种类型的迭代标签和参数,我们将获得执行代码块重复的能力,这将帮助我们编写更高质量的代码。最后,我们将提到一些已弃用的标签;它们仍然出现在一些较旧的主题中,因此了解它们的功能和使用方法至关重要。

本章涵盖了以下主题:

  • 控制 Liquid 的流程(控制标签)

  • 变量标签

  • 迭代标签

  • 主题标签

  • 已弃用标签

在本章中,我们将通过探索 Liquid 编程逻辑来扩展我们对逻辑和比较运算符以及不同数据类型的知识。

技术要求

由于 Shopify 是一个托管服务,您需要互联网连接才能遵循本章中概述的步骤。

本章使用的*.csv格式的数据集可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/blob/main/Product-data.csv.

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter03.

本章的“代码在行动”视频可以在这里找到:bit.ly/3nP8uwG

准备工作

在我们继续之前,我们需要先创建一些产品和收藏夹页面,这些页面将在接下来的练习中使用。

创建产品页面

通常,创建一个页面或产品页面是一个简单直观的过程:

  1. 我们可以先通过导航到侧边栏中的产品部分并点击添加产品按钮开始,这将自动将我们重定向到定义我们的产品名称、描述、图片、价格和其他参数的页面。我们不会过多地介绍如何管理产品。然而,如果您想了解更多关于这个主题的信息,请参阅help.shopify.com/en/manual/products/add-update-products.

  2. 为了避免手动创建大量我们以后需要的产品,我们将使用 GitHub 上已经创建的产品。为此目的,我们创建了一个.csv文件,使我们能够轻松地为我们的开发商店创建许多产品。我们唯一需要做的就是从github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/blob/main/Product-data.csv下载文件并将其导入到我们的商店中。

  3. 下载文件后,点击侧边栏中的产品链接,这将自动定位到所有产品后展开。点击导入按钮以触发弹出窗口并开始进程:图 3.1 – 导入产品进程的示例

    图 3.1 – 导入产品进程的示例

  4. 选择Product-data.csv文件后,我们可以通过点击上传并继续按钮开始进程:图 3.2 – 选择并上传.csv 文件到商店

    图 3.2 – 选择并上传.csv 文件到商店

  5. 几秒钟后,我们将看到一个弹出窗口预览我们即将导入的产品之一。确认字段包含正确的信息后,我们可以通过点击导入产品按钮来最终完成导入进程:

图 3.3 – 从.csv 文件上传产品到商店的最终过程

图 3.3 – 从.csv 文件上传产品到商店的最终过程

创建集合页面

现在我们已经用产品填充了商店,是时候创建一些集合页面了,然后我们可以用新产品填充它们。创建集合页面的过程与产品页面一样简单:

  1. 我们可以通过点击侧边栏中的产品链接并随后点击扩展的产品菜单中的集合子链接来开始进程。

  2. 进入后,点击创建集合按钮,自动将我们重定向到定义集合名称、描述、分配产品和其他参数的页面。

  3. 考虑到我们有两种类型的产品,我们将创建两个集合,并将相同类型的产品分配给每个集合。点击“户外”作为集合名称,并将集合类型设置为自动,这样我们就不必手动分配每个产品。最后一步是设置条件,以确保只填充类型等于户外的产品:

图 3.4 – 创建集合并根据产品类型自动填充的示例

图 3.4 – 创建集合并根据产品类型自动填充的示例

保存更改后,我们的收藏页面将立即创建并填充符合我们条件的商品。注意,在我们继续之前,我们应该为室内收藏重复前面的步骤,我们将在接下来的章节中使用它。如果您想了解更多关于管理收藏页面的信息,请参阅help.shopify.com/en/manual/products/collections

更新导航菜单

为了更快的导航,我们可以在主菜单导航中包含我们两个收藏的链接:

  1. 我们可以通过点击侧边栏中的在线商店来展开它,并随后点击导航链接来做到这一点。

  2. 进入编辑器后,我们可以通过打开主菜单并点击添加菜单项来添加任意数量的链接。如果您想了解更多关于管理导航菜单的信息,请参阅help.shopify.com/en/manual/online-store/menus-and-links

现在我们已经完成了产品页面和收藏页面设置,我们可以学习关于 Liquid 编程逻辑。

控制 Liquid 的流程

在前面的章节中,我们看到了一些控制流程标签,如ifandor的实际应用;现在我们将进一步深入这个主题,并学习所有控制流程类型的标签以及如何使用它们。控制流程标签是一种 Liquid 编程逻辑,它通过允许我们在特定条件下有选择性地决定哪个代码块应该执行,来告诉我们的 Liquid 代码应该做什么。我们可以将控制流程标签分为四个不同的组:

  • if/else/elsif

  • 和/或

  • case/when

  • unless

if/else/elsif 标签

我们在前面的例子中已经愉快地看到了条件if语句,如果它被证明是真的,就会执行语句内的代码。让我们看看它是如何工作的。在上一章中,我们创建了了解页面处理页面。

然而,让我们尝试为这个练习创建一个新页面,以巩固我们的知识并使一切保持简洁:

  1. 让我们从创建一个名为控制 Liquid 的流程的新页面开始,我们将继续使用它。假设我们需要提醒自己如何创建新页面,我们可以回顾前面的章节,并查阅位于理解数据类型部分下的EmptyDrop子部分,我们之前在那里概述了创建新页面的过程。

  2. 在创建新页面后,是时候编辑我们新创建的页面模板文件了。我们需要导航到位于在线商店区域下的主题部分,点击我们创建的副本主题上的操作按钮,然后选择编辑代码选项,这将打开我们的代码编辑器。

  3. 进入代码编辑器后,找到分配给我们的页面当前模板。在我们的例子中,模板名称是page.liquid,因此点击它。

  4. 目前,我们的页面由两个 Liquid 元素组成:{{ page.title }},它生成页面的标题,以及{{ page.content }},它生成我们页面的内容,以及一些 HTML 元素。然而,如果我们的 {{ page.content }} 为空怎么办? 我们最终会得到一个空的div元素:

图 3.5 – 页面上空元素的示例

图 3.5 – 页面上空元素的示例

为了解决这个问题,我们可以在条件中包裹{{ page.content }}以及封装它的div,以检查页面内容是否为空:

{% if page.content != blank %}
  <div class="rte">
    {{ page.content }}
  </div>
{% endif %}

我们现在确信,在放置了条件之后,我们页面上不会出现任何额外的空字符串和元素。

现在我们尝试包含一些占位文本,并让访问我们页面的人知道我们将很快添加页面内容。这就是elseelsif语句帮助我们的时候。通过添加elseelsif,我们可以创建多个条件,以确保我们正确地执行我们的代码:

  1. 让我们先在{% endif %}之前添加else条件,以便在{{ page.content }}为空时执行不同的结果:

    {% if page.content != blank %}
      <div class="rte">
        {{ page.content }}
      </div>
    else condition in place, if someone accidentally visits our page, they won't think that we have a broken page, as we will have a message waiting for them:![Figure 3.6 – Example of the else condition getting executed    ](https://github.com/OpenDocCN/freelearn-php-zh/raw/master/docs/shopify-thm-cstm-liquid/img/Figure_3.06_B17606.jpg)Figure 3.6 – Example of the else condition getting executed
    
  2. 让我们尝试通过添加一个语句来检查我们是否已经开始编写页面内容,并相应地更新我们的通知消息来改进我们的页面。我们可以通过添加一个elsif语句来检查页面内容是否超过 100 个字符来实现这一点。然而,为了使这可行,我们还需要通过修改现有的if语句来改变现有的条件,以便在页面内容有100个或更多字符时显示页面内容:

    100 characters. If it does, it will return true, and our page content will show. The second if statement, which checks whether the page content has less than 100 characters, will only happen if the first statement returns false. Important note:It is impossible to execute multiple statements on different lines within the same block, even if their result was true.
    

在放置了两个条件之后,我们的页面应该有足够的信息供任何访问者了解。然而,如果我们仔细查看我们的语句,我们会看到{% else %}语句永远不会执行,因为页面内容要么超过 100 个字符,要么少于 100 个字符。正如我们回忆的那样,我们只能在单个块中执行一个语句。为了解决这个问题,我们需要使用and运算符来确保我们代码块中的所有语句都正常工作。

和/或标签

正如我们在上一章中回忆的那样,andor运算符允许我们在控制流标签中包含多个条件,从而允许我们创建复杂的条件。

使用and运算符,我们可以将另一个条件链接到现有语句,只有当语句的左右两边都为真时,它才会返回true。让我们在elsif语句中尝试包含另一个条件,该条件检查页面内容是否有内容:

{% if page.content.size > 100 %}
  <div class="rte">
    {{ page.content }}
  </div>
{% elsif page.content.size < 100 and page.content != blank %}
  <div class="rte">
    Our content writers are making final touches, and the       page content will be available shortly. 
  </div>
{% else %}
  <div class="rte">
    The page content is currently pending. Please check       again soon.
  </div>
{% endif %}

通过添加第二个条件,我们确保了我们的控制流标签将正确执行。第一个语句将在页面内容超过100个字符时返回true,第二个语句将在页面内容少于100个字符且页面不为空时返回true。最后,如果前两个语句都返回false,则else语句中的代码将执行。

and参数类似,or参数允许我们将另一个条件链接到标签上。然而,关键的区别在于,对于带有and参数的语句要返回true,条件表达式的左右两边都必须返回true。而使用or参数,我们只需要至少有一个条件返回true,这样该语句就会变为真值,并且代码块将执行。

case/when 标签

if/elsif/else条件一样,case/when是我们可以使用来创建switch语句的控制流标签,它允许我们仅在返回值与精确匹配时执行特定的代码块。我们可以使用case来初始化switch语句,并使用when来设置我们想要它们执行的特定顺序的条件。

让我们回到我们之前工作的page.liquid模板,并创建一个switch语句,该语句将检查页面内容的精确字符数,并根据哪个语句为真渲染相应的消息。我们可以在添加的第一个if语句之上包含此功能:

{% case page.content.size %}
  {% when 150 %}
    The page has only 150 characters, so this should not       take much time to read.
  {% when 350 %}
    We have bumped the page to 350 characters, but it       should not take that much time.
  {% when 1000 %}
    We now have 1000 characters written, and it is going to       take a few minutes to read everything carefully.
{% endcase %}

在前面的示例中,我们使用page.content.size初始化了case标签,这使得我们可以使用when语句来检查页面内容的字符数是否严格等于我们的值。请注意,when语句没有比较变量。这是因为when语句只接受一个值作为参数,并且只有在值是精确匹配时才会返回true

![Figure 3.7 – 执行 case/when 标签的示例

![Figure 3.07_B17606.jpg]

图 3.7 – 执行 case/when 标签的示例

在我们的控制流标签就位后,如果我们以150个字符作为页面内容,第一个when语句将返回true,因此我们可以看到我们的消息。相比之下,考虑到我们只能用它来匹配精确值,而不是if/elsif/else标签,这个标签可能看起来并不强大。然而,case/when标签是强大的编程逻辑的一部分;我们将在本书的后续章节中使用它们来创建复杂的功能。

正如我们所回忆的,直接添加到模板文件中的任何代码都会在所有使用该特定模板的页面上执行。由于我们已经将所有之前的代码添加到page.liquid模板中,我们应该确保我们只在对unless标签执行之前添加的代码。

unless标签

与允许我们检查特定条件状态的if标签类似,unless标签允许我们检查是否未满足条件:

{% unless page.title != "Controlling the flow of Liquid" %}
  Unless the page title is Controlling the Flow of Liquid,     the code inside this conditional will not execute. 
{% endunless %}

我们可以在case标签的上方添加unless标签的打开语句,并在if/elsif/else标签的关闭语句下方添加关闭语句:

图 3.8 – 与 Liquid 控制流相关的整个代码示例

图 3.8 – 与 Liquid 控制流相关的整个代码示例

图 3.8 – 与 Liquid 控制流相关的整个代码示例

通过放置unless语句,我们确保所有代码只在这个特定的页面上执行,并且不会影响使用相同page.liquid模板文件的任何其他页面。

通过了解所有不同类型的控制流标签,我们在掌握 Liquid 编程逻辑方面又迈出了坚实的一步,这将为我们通往更多重要且复杂的事物奠定基石。

在上一章中,我们提到了各种数据类型,例如字符串、数字和布尔值,以及我们如何使用它们。然而,如果我们想保存这些数据类型并在多个位置重复使用它们,而不必手动更新每一行,那会怎样呢?这就是变量发挥作用的地方。

变量标签

我们可以将变量视为数据容器,以保存我们希望在代码中稍后使用或按需覆盖的各种类型的信息。除了保存信息以供以后使用外,变量还允许我们使用描述性文本作为标签,使我们能够理解特定变量包含的信息类型。我们可以将变量标签分为以下四组:

  • assign

  • capture

  • increment

  • decrement

assign标签

assign标签允许我们声明一个变量,我们可以将字符串、数字或布尔数据分配给该变量。我们可以通过编写assign关键字后跟我们要声明的变量的名称,然后是等号和分配给该特定变量的数据,并将其封装在带有百分号符号的大括号定界符中来实现变量的声明:

{% assign stringVar = "This is a string!" %}
{% assign numberVar = 2021 %}
{% assign booleanVar = true %}

一旦我们声明了一个变量,我们就可以通过将变量的名称封装在双大括号定界符中来多次调用它:

{{ stringVar }}
{{ numberVar }}
{{ booleanVar }}

调用我们的三个变量将生成与我们最初分配给每个变量的相同类型的数据:

"This is a string!"
2021
true

注意,使用双大括号定界符调用变量时,当单独调用时将返回变量的值。如果我们想在for标签或if语句中使用变量,我们只需使用变量的名称调用变量,而不需要双大括号定界符:

{% if stringVar == "This is a String!" %}
  The variable content is a string value!
{% elsif stringVar == 2021 %}
  The variable content is a number value!
{% else %}
  The variable content is a boolean value!
{% endif %}

我们在多个if语句中使用了之前声明的变量来确定变量的值。由于我们的变量值等于第一个语句的比较值,第一个语句将返回true

到目前为止,我们已经学习了如何将单一类型的数据分配给一个变量,但如果我们想创建一个可以存储字符串和变量组合的变量呢? 要实现这一点,我们需要另一个类型的变量标签的帮助,称为 capture

capture 标签

与只允许我们捕获单个值的 assign 标签不同,capture 允许我们使用其起始和结束闭合标签捕获多个值。我们可以使用一组花括号定界符和围绕 capture 单词的百分号来声明 capture 变量,后面跟着我们声明的变量的名称:

{% assign percent = 15 %}
{% assign deal = "three or more" %}
{% capture promoMessage %}
  If you order {{ deal }} books, you can receive up to a {{     percent }}% discount!
{% endcapture %}
{{ promoMessage }}

在上一个例子中,我们最初使用 assign 标签定义了两个变量,一个包含数值,一个包含字符串。一旦我们声明了这两个变量,我们又使用 capture 标签声明了一个新变量。我们包含了一个包含先前定义的变量的字符串消息,最后,我们通过将 promoMessage 变量包裹在双花括号定界符中来调用它,以查看我们工作的结果:

"If you order three or more books, you can receive up to a 15% discount!"

正如我们所见,使用 capture 标签,我们成功创建了一个复杂的字符串消息,随着我们进一步学习知识,这将非常有用。

重要提示:

虽然 capture 标签接受所有数据类型,正如我们在前面的例子中所看到的那样,调用 capture 变量将始终返回字符串数据。

使用 assign 标签,我们学习了如何创建新的变量,我们可以存储单一类型的数据。使用 capture 变量,我们学习了如何使用不同的变量创建复杂的字符串,但我们应该如何创建一个内容为数值的变量,该数值充当计数器呢?

increment 标签

assigncapture 标签不同,increment 标签不需要先声明。相反,我们将在第一次调用它时自动创建变量。increment 标签允许我们自动创建变量,并在每次使用 increment 标签调用变量时增加它。我们可以使用一组花括号定界符和围绕 increment 单词的百分号来调用 increment 变量,后面跟着我们创建的变量的名称:

{% increment counter %}

使用 increment 标签创建的任何变量,包括这个变量的起始值,始终为零。

重要提示:

调用 increment 变量不仅会自动生成并增加从零开始的值,而且还会自动输出我们在模板中工作的变量的内容。

考虑到 increment 标签在标签被调用时自动输出值,它的用途相当有限。最常见的使用是自动生成 HTML 元素的唯一编号标识符:

<div class="product-count">
  <div id="product-item-{% increment counter %}"></div>
  <div id="product-item-{% increment counter %}"></div>
  <div id="product-item-{% increment counter %}"></div>
</div>

我们之前示例的结果将使我们能够为每个 div 元素创建一个唯一的 ID,从 increment 标签首次出现时的零开始,每次后续出现时增加一:

<div class="product-count">
  <div id="product-item-0"></div>
  <div id="product-item-1"></div>
  <div id="product-item-2"></div>
</div>

increment 标签的一个更关键方面是它独立于使用 assigncapture 标签创建的变量工作。让我们尝试使用 assign 标签创建一个变量,并看看当我们尝试增加它时会发生什么:

{% assign numberVar = 7 %}
{% increment numberVar %}
{% increment numberVar %}
{{ numberVar }}

最初,我们创建了变量并将其赋值为 7,之后我们使用 increment 标签两次调用具有相同名称的变量。最后,我们调用最初使用 assign 标签创建的变量:

0
1
7

如我们从结果中看到的那样,尽管我们已使用 assign 标签声明了 numberVar 变量并将其赋值为 7,但 increment 标签从 0 开始增加值。它们可能具有相同的名称,但它们是完全不同的变量。increment 变量不会以任何方式影响最初创建的变量,反之亦然。

重要提示:

注意,使用 increment 标签创建的变量不能在没有 increment 标签或用作逻辑参数的情况下独立调用,这与 assigncapture 标签的情况不同。

我们现在已经学会了如何创建一个独立的变量标签,为任意数量的元素创建一个唯一的元素标识符。然而,假设由于某种原因,我们需要一个输出大量元素负值的变量。为此功能,我们需要使用不同类型的变量标签,名为 decrement

decrement 标签

除了使用 decrement 关键字初始化变量外,decrement 标签与 increment 标签在两个关键方面有所不同:

  • 第一点是 decrement 允许我们每次出现时将变量值减少一。

  • 第二点是初始值从负一开始。

在以下示例中,我们可以看到一个使用相同变量调用 decrement 标签三次的例子:

{% decrement numberVar %}
{% decrement numberVar %}
{% decrement numberVar %}

在这个初始示例中,我们调用了 decrement 标签三次。由于 decrement 变量从负值开始,调用三次后,我们将收到以下值:

-1
-2
-3

重要提示:

除了我们做出的两个不同之处,decrementincrement 标签共享所有规则和限制,这意味着 decrement 标签独立于使用 assigncapture 方法创建的变量工作。我们无法在不初始化 decrement 标签的情况下独立调用它。

变量是强大的工具,与迭代标签结合使用,将使我们更接近编写更简洁和可重用的代码。

迭代标签

迭代标签是不同的液态编程逻辑类型,允许我们重复运行代码块。使用迭代标签将节省我们手动执行每个发生时所需的时间;此外,它将使我们的代码更加简洁和易于阅读。为了保持主题简洁,我们只将提到一些最常用的迭代标签及其参数,这比列出所有标签更重要,因为它们都是使用类似的概念创建的。我们可以将迭代标签分为四个不同的组:

  • for/else

  • 跳转语句

  • for参数

  • cycle

for/else 标签

在上一章中,当我们解释了理解数据类型主题中的数组时,我们有机会在我们的示例中使用for循环。然而,我们还没有机会解释for循环给我们带来的所有可能性。for循环是一种液态编程逻辑,允许我们遍历任何代码块或数组,并将该循环的结果输出以供进一步使用。

之前,我们一直在处理page.liquid模板,但现在,我们将继续到collection.liquid模板。

让我们首先编写一个for循环标签,该标签将列出所有产品名称,并将它们放在{% section 'collection-template' %}行下面:

{% for product in collection.products %}
  {{ product.title }}
{% endfor %}

在上一个示例中,我们使用了一个for标签,后面跟着product变量,来遍历collection.products对象,并返回分配给我们的集合的产品名称,我们可以在我们的室内集合页面上看到这些名称:

![图 3.9 – 使用 for 循环列出产品名称的示例

![img/Figure_3.09_B17606.jpg]

图 3.9 – 使用 for 循环列出产品名称的示例

虽然我们的for循环确实使它工作,并且我们可以看到我们的产品名称列表,但这并没有什么用处,因为我们已经列出了相同的产品。让我们使用我们在上一章中学到的知识,当时我们在EmptyDrop子节中讨论了访问页面处理对象的不同方法,该子节位于理解数据类型部分。

使用我们当前的代码,我们正在读取我们当前访问的集合的产品。相反,让我们尝试访问位于我们名为outdoor的第二个集合中的产品:

{% for product in collections["outdoor"].products %}
  {{ product.title }}
{% endfor %}

尽管我们目前正在预览Indoor集合,但我们现在已经获得了一个属于Outdoor集合的产品列表。

跳转语句

如其名所示,跳转语句允许我们设置条件,排除某些项目从我们的循环中,或者当我们遇到特定条件时停止我们的循环。我们可以将跳转语句分为以下两个组:

  • continue: {% continue %} 语句允许我们根据我们设定的条件跳过当前迭代。我们可以通过将其与 if 语句配对来使用它,仅接受价格低于 $100.00 的产品。否则,我们应该将产品排除在循环之外:

    {% for product in collections["outdoor"].products %}
      if statement in place, any product iteration whose price is higher than $100.00 will trigger our {% continue %} jump statement, consequently excluding that product from our iteration. Otherwise, any product that fails that statement will render the product name on our page.
    
  • break: 与之相反,{% break %} 语句允许我们在遇到特定条件时停止循环:

    {% for product in collections["outdoor"].products %}
      {% if product.price > 10000 %}
        {% continue %} statement, the result we have received from our loop was that we had excluded any product whose price was lower than $100.00 from the loop result. However, in our latest example, instead of excluding the products that match our condition, {% break %} will cause the loop to stop and not perform any other iteration the moment it finds the first occurrence that matches our condition.
    

例如,使用 {% continue %},我们的循环返回了六个符合我们条件的产品。然而,当我们用 {% break %} 替换 {% continue %} 时,我们的循环返回了零结果。由于我们使用的 Outdoor 收藏中的第一个产品的价格为 $200.00,我们在第一次迭代就满足了我们的条件,这导致 {% break %} 停止了迭代,防止了其他所有迭代。

for 参数

除了 jump 语句之外,我们还有各种参数可以进一步定义循环的限制和工作流程。我们可以将可以与 for 循环一起使用的参数分为以下四个组:

  • limit

  • offset

  • range

  • reversed

限制

如其名所示,limit 参数限制了循环应执行的迭代次数。我们可以在打开的 for 循环标签末尾添加 limit 参数,后面跟着一个冒号,然后是一个数字值。

当我们要求进行特定次数的迭代而不实现任何条件时,我们通常使用 limit 参数。这是因为 limit 参数只衡量迭代次数,并不检查迭代次数是否与我们的语句匹配。

让我们从调整之前的例子开始,将 {% break %} 替换为 {% continue %},并添加 limit 参数,其值为 4,这是我们希望作为结果获取的最大产品数量:

{% for product in collections["outdoor"].products limit: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {{ product.title }}
  {% endif %}
{% endfor %}

在我们之前的例子中,在添加 limit 参数之前,我们的 for 循环返回了六个产品迭代,其价格低于 $100.00。添加 limit 参数后,返回的迭代次数为 3,尽管我们将其限制设置为 4为什么?

与我们之前提到的 {% break %} 语句类似,limit 参数的目标是限制迭代次数到指定的值。让我们打开我们的 Outdoor 收藏,我们正在使用其句柄进行循环。我们会注意到该收藏中的第一个产品的价格为 $200.00,这触发了 if 语句,随后触发了 {% continue %} 语句,排除了第一个产品。

因此,我们的for循环没有打印出那个产品名称。然而,limit参数仍然将其计为一个迭代,这意味着它将执行另外三次迭代。由于接下来的三个产品的价格低于$100.00,我们的for循环在停止之前返回了这三个产品的名称。因此,limit参数通常只在没有其他语句的循环中使用。否则,我们可能会无法达到预期的迭代次数,甚至一个迭代都没有,因为如果没有匹配我们设置的语句,迭代次数也可能为零。

偏移参数

offset参数允许我们通过从特定索引开始循环来延迟for循环的开始。我们可以在打开的for循环标签的末尾添加一个offset参数,后面跟着一个冒号,然后是一个数字值。

让我们拿我们之前的例子,尝试用offset参数替换limit,并将其值设置为4

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {{ product.title }}
  {% endif %}
{% endfor %}

我们的for循环自动跳过了前四个产品迭代,并从索引号5开始迭代产品,导致我们的for循环返回三个产品迭代。然而,如果我们也在 offset 参数之后包含 limit 参数并将其值设置为 1,会怎样呢?

{% for product in collections["outdoor"].products offset: 4   limit: 1 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {{ product.title }}
  {% endif %}
{% endfor %}

虽然一开始看起来前面的例子可能不会工作,但这实际上是一个完全功能的循环,具有有效的参数。正如我们之前提到的,limit参数允许我们将for循环限制在特定的迭代次数。添加两个参数后,我们的for循环将从索引5开始迭代。它将按照limit参数设置的值进行一次迭代,然后停止for循环,无论这次迭代是否成功通过了我们的if语句。

范围参数

range参数为我们提供了与offset参数类似的功能。关键的区别在于,使用range,我们可以分配起始和结束索引位置。我们可以在打开的for循环标签的末尾添加一个range参数,通过编写开括号分隔符,然后是起始值,接着是两个点,最后是结束值和闭括号分隔符:

{% for item in (3..5) %}
  {{ item }}
{% endfor %}

正如我们回忆的那样,offset参数不包括循环中的起始位置索引。相反,它从下一个位置开始循环,范围对于起始和结束位置值都是包含的。我们将range参数设置为(3..5)for循环示例的结果如下:

3 4 5

除了接受数字作为其值之外,正如我们在前面的例子中所能看到的,range还允许我们将变量和对象作为其起始和结束值:

{% assign start = 3 %}
{% for item in (start..collections["outdoor"].all_products_  count) %}
  {{ item }}
{% endfor %}

我们的 for 循环示例的结果将返回从索引 3 开始的所有迭代,一直到我们集合中的产品数量。有了分配变量和对象作为起始或结束点的功能,我们现在可以创建可重用的 for 循环,从而生成更简洁、质量更高的代码。

reversed 参数

我们列表中的最后一个参数是 reversed,正如其名称所暗示的,它允许我们反转迭代的顺序。reversed 参数没有任何值表示,我们可以在 for 循环开标签的末尾包含它:

{% for item in (3..5) reversed %}
  {{ item }}
{% endfor %}

在前面的例子中,我们已经将 reversed 参数作为次要参数添加到我们的 range 参数旁边,使得我们的标签以相反的顺序执行迭代:

5 4 3 

正如我们有机会亲自看到的,参数是 for 标签的一个强大补充,我们可以使用它来实现所需的迭代类型,并只获取我们需要的成果。

cycle 标签

cycle 是另一个强大的标签,我们只能与 for 标签结合使用,以遍历一组字符串并将它们按最初定义的顺序输出到每个迭代中。我们可以通过使用带有百分号的开花括号、跟随单词 cycle、然后是任意数量的用逗号分隔的字符串来定义 cycle 标签。最后,我们用闭合花括号和百分号关闭 cycle 标签:

{% for product in collection.products limit: 4 %}
  <div class="product-item {% cycle "first", "", "", "last"     %}"></div>
{% endfor %}

在前面的例子中,我们在一个限制为四次迭代的 for 循环中包含了一个 cycle 标签,其中包含四个不同的字符串。通过实现 cycle 标签,我们确保了我们的第一个 div 元素将首先获得一个类,第四个元素将获得 last 类,而中间的两个元素将不会获得任何类,因为这些位置的两个字符串是空的:

<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>

从我们的结果中,我们可以看到 cycle 标签是一个非常宝贵的工具,我们可以用它将数据按我们定义的顺序传递到循环中的特定迭代。然而,如果我们从循环中移除限制或将其增加到九次迭代,会发生什么?

{% for product in collection.products limit: 9 %}
  <div class="product-item {% cycle "first", "", "", "last"     %}"></div>
{% endfor %}

在我们之前的例子中,迭代的次数与 cycle 标签内字符串的数量完全相同。在我们的最新例子中,迭代的次数超过了字符串的数量,这意味着循环将重置并再次按顺序应用字符串,直到所需的迭代次数:

<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>
<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>
<div class="product-item first"></div>

到目前为止,我们已经学习了 cycle 标签的基本用法,以及该标签将尽可能继续输出字符串,这对于我们只有一个 cycle 标签时非常有帮助。但是,如果我们有两个或更多 cycle 标签会怎样呢?

{% for product in collection.products limit: 6 %}
  <div class="product-item {% cycle "first", "", "", "last"    %}"></div>
{% endfor %}
{% for product in collection.products limit: 4 %}
  <div class="product-item {% cycle "first", "", "", "last"     %}"></div>
{% endfor %}

在前面的例子中,我们创建了两个独立的for循环,第一个循环限制为六次迭代,第二个循环限制为四次。正如我们刚刚学到的,无论我们在cycle标签内部定义了多少个字符串,标签将继续输出所需次数的字符串:

<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>
<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>
<div class="product-item first"></div>
<div class="product-item"></div>

从结果中我们可以看到,第一个for循环按照我们预期的顺序创建了六个迭代。然而,第二个for循环没有产生相同的结果。相反,它继续从上一个cycle标签停止的确切位置输出字符串。

考虑到 Liquid 目前无法区分不同类型的cycle标签,这种行为是逻辑上的。然而,我们可以通过引入一个名为cycle group的参数来轻松解决这个问题。

周期组参数允许我们通过确保每个周期都将输出以位置一开头的字符串来分离cycle标签,无论我们是否已经在同一页上使用了cycle标签。在cycle关键字之后,我们可以通过添加括号内的字符串名称,后跟一个冒号来包含周期组参数:

{% for product in collection.products limit: 6 %}
  <div class="product-item {% cycle "group1": "first", "",     "", "last" %}"></div>
{% endfor %}
{% for product in collection.products limit: 4 %}
  <div class="product-item {% cycle "group2": "first", "",     "", "last" %}"></div>
{% endfor %}

周期组参数的引入确保了每个cycle标签独立工作:

<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>
<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item first"></div>
<div class="product-item"></div>
<div class="product-item"></div>
<div class="product-item last"></div>

从我们的结果中我们可以看到,所有迭代都接收到了最初为它们设计的字符串,尽管我们有多于一个的cycle标签。

除了forcycle标签之外,我们还有一种称为tablerow的迭代标签,它的工作方式与for标签类似。这两个标签之间的唯一区别是tablerow返回格式化为 HTML 表格的结果。为了使本书简明扼要,我们不会在本书中介绍tablerow标签及其参数。但是,如果您想了解更多关于它的信息,请参阅shopify.dev/docs/themes/liquid/reference/tags/iteration-tags

主题标签

主题标签是一种特殊的标签,它让我们能够对未渲染和已渲染的代码进行特定控制。使用我们可用的各种类型的主题标签,我们可以为特定模板创建不同类型的 HTML 标记,这对于创建允许客户从我们的商店购买产品的表单至关重要。此外,它们允许我们为不同的页面选择不同的主题布局,定义我们可以用来创建可重用代码块的分区或片段文件,以及许多其他事情。我们可以将主题标签分为以下几组:

  • layout

  • liquidecho

  • form

  • paginate

  • render

  • raw

  • comment

布局标签

如我们从第一章回忆的那样,在讨论Layout目录时,我们提到了theme.liquid文件的重要性,因为我们将在这个文件中渲染所有文件和模板。正是在这个文件中,我们安排了我们页面的通用布局。

我们当前的页面由三个关键元素组成:页眉主要内容页脚。假设我们想要从我们的产品页面上移除页眉和页脚部分,或者至少用一组不同的页眉和页脚来替换它们。我们该如何操作?

  1. 实现这一目标的第一个步骤是创建一个替代布局文件,我们的主题产品页面将使用该文件。我们可以通过在代码编辑器中展开 布局 目录并点击 添加新布局 按钮,这将触发一个弹出窗口以选择我们想要创建的布局页面类型,并为我们的新页面选择名称。

  2. 在下拉菜单中,我们将选择 alternate 作为我们的文件名。一旦我们完成选择布局文件类型和名称,点击 创建布局 按钮以完成此过程:

![图 3.10 – 创建新布局文件的示例图片 3.10 – 创建新布局文件的示例

图 3.10 – 创建新布局文件的示例

通过这种方式,我们已经成功创建了一个名为 theme.alternate.liquid 的新布局文件。假设我们尝试将此文件与我们的原始 theme.liquid 文件进行比较;我们会发现它们完全相同。这是因为当我们创建一个新页面、布局或模板时,Shopify 不会完全创建一个新页面,而是会复制我们主题已经使用的默认页面。例如,如果我们展开 page.liquid 文件。

现在我们已经创建了一个新的布局文件,我们将将其分配给我们的产品页面。我们可以通过展开 product.liquid 文件来完成此操作,在那里我们可以定义此特定模板的布局,我们应该在文件的非常第一行包含它。我们可以使用一组带有百分号围绕 layout 单词的花括号定界符来定义 layout 标签,后跟布局文件的名称,该名称应包含在括号内:

{% layout "theme.alternate" %}

如果我们导航到 {% section 'header' %}{% section 'footer' %},页眉和页脚部分将不再在我们的产品页面上可见。然而,它们仍然会在使用原始 theme.liquid 布局的页面上可见。

重要提示:

除了布局文件的名称外,layout 标签还接受 none 关键字作为其值,无需括号,在这种情况下,页面将不使用任何布局文件。在这种情况下,使用此特定模板的特定页面将无法访问任何代码或文件,包括 .css.js 或其他我们最初通过 theme.liquid 文件加载的文件。

任何不包含 layout 标签的模板文件将默认使用原始的 theme.liquid 布局作为安全选项。

液体和回声标签

liquid标签是 Liquid 的最新补充之一,它相当强大,因为它允许我们在一组定界符内写入多个标签,使我们的代码更容易阅读和维护。我们可以使用花括号定界符和liquid一词左侧的百分号符号来定义liquid标签,然后跟随我们需要的任意多行Liquid代码。请注意,我们只需要定义一个开头的liquid标签,其中liquid闭合标签会自动关闭。

echo标签是liquid标签的一个补充,它允许我们输出最初必须用双花括号定界符包裹的表达式。我们可以通过简单地写一个echo关键字,然后跟随任何我们正在使用的表达式来定义echo标签。

让我们尝试使用我们的liquidecho标签来重构我们在学习不同类型迭代时添加到收藏模板的代码。

这里,我们可以看到我们在收藏页面最初创建的代码:

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {{ product.title }}
  {% endif %}
{% endfor %}

我们可以在以下示例中看到重构后使用liquidecho标签的相同代码:

{% liquid
for product in collections["outdoor"].products offset: 4
  if product.price > 10000
    continue
  else
    echo product.title
  endif
endfor %}

在重构我们的代码后,我们可以看到我们已经删除了所有带有百分号的花括号定界符,并用echo标签替换了所有双花括号定界符。剩下的带有百分号符号的花括号定界符仅属于liquid标签,包围着整个代码。

重要提示:

虽然liquidecho标签非常强大,但我们不应经常使用它们,因为虽然它们帮助我们在一组定界符内写入多个标签,但在处理字符串时并不那么友好。echo标签强制我们用括号包裹每个字符串,这使得在字符串中使用非字符串数据值变得几乎不可能。

表单标签

form标签允许我们根据我们调用的form标签的类型自动输出各种类型的 HTML <form>以及所需的<input>元素。我们可以在一对花括号定界符内定义form标签,然后是form关键字,接着是括号内的表单名称,最后是包围在带有百分号的花括号定界符中的endfor闭合标签:

{% form "product", product %}
{% endform %}

在上一个示例中,我们选择通过在括号内使用product关键字来生成产品表单标签。然而,请注意,在逗号之后,我们还有一个没有括号的product关键字。某些形式,例如我们目前使用的产品形式,需要我们传递一个参数来生成适当的内容。在这种情况下,该参数是product对象。

这最常见的使用是在产品页面上。然而,我们也可以在收藏页面或任何我们有权限访问product对象的地方使用form标签。让我们尝试在我们的收藏页面之前创建的循环中包含一个form标签。

由于我们将处理字符串和非字符串数据的混合,让我们首先撤销之前所做的liquidecho更改。我们可以通过点击位于文件名和删除文件按钮之间的旧版本按钮来完成此操作:

图 3.11 – 文件中撤销更改的示例

图 3.11 – 文件中撤销更改的示例

点击旧版本按钮将显示下拉菜单,我们可以看到我们在这个特定文件中做出的所有更改的日期和时间。每个日期代表我们最后一次按下保存按钮以保存我们的更改。选择任何选项将自动将我们的代码回滚到那个特定点。

在撤销我们的更改后,我们就可以将form标签添加到我们的for循环中:

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {% form "product", product %}
      {{ product.title }}
    {% endform %}
  {% endif %}
{% endfor %}

通过在我们的for循环中引入form标签,我们已经生成了一个可以通过检查我们Indoor集合页面上的字符串元素看到的 HTML 表单标签。虽然form标签是功能性的,但我们仍然缺少一个关键成分来使我们的表单可用,我们将在下一章中学习这一点。

通过检查我们的集合页面,我们可以看到我们的form标签已经有相当多的属性。但是,如果我们想添加一些自己的或修改现有的属性怎么办? 我们可以通过简单地添加属性名,然后是逗号,然后是属性值来完成此操作:

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {% capture productId %}productId-{% increment counter       %}{% endcapture %}
    {% form "product", product, id: productId, data-      location: "collection" %}
      {{ product.title }}
    {% endform %}
  {% endif %}
{% endfor %}

在前面的示例中,我们使用字符串在我们的表单中包含了一个数据属性。然而,我们也使用先前定义的变量修改了现有的 ID 值。

注意,虽然我们可以在属性值中直接包含变量,但我们不能在form标签本身中将字符串和变量混合。因此,我们之前使用capture标签捕获了组合的字符串和increment标签,并使用变量名将其称为属性值。

除了可以将对象作为form参数传递的能力之外,form标签还为我们提供了一个名为return_to的特殊参数。默认情况下,当我们提交产品表单时,我们通常会被重定向到购物车页面。然而,使用return_to参数,我们可以选择在提交表单后应着陆的页面:

{% form "product", return_to: "back" %}
{% endform %}

在我们之前的示例中,我们将back值作为字符串分配给return_to参数,这将自动将我们返回到提交表单之前所在的同一页面。除了back字符串之外,我们还可以使用相对路径或routes属性生成一个动态 URL,以便在提交表单后到达该页面。

我们可以使用许多类型的表单标签。然而,由于它们都是使用相同的格式创建的,我们只会覆盖一个示例。有关不同类型的 form 标签及其参数的更多信息,请参阅 shopify.dev/docs/themes/liquid/reference/tags/theme-tags#form

分页标签

之前,我们已经学习了所有关于迭代标签的知识,我们可以使用它们来重复执行代码块。然而,迭代标签有一个限制,意味着它们每页只能输出最多 50 个结果。对于任何更高的迭代次数,我们需要使用 paginate 标签来将结果分散到多个页面。

paginate 标签必须始终围绕一个正在遍历数组的 for 标签来使用,以便能够将内容分成多个页面。我们在这里可以看到一个 paginate 标签围绕 for 循环的示例:

{% paginate collection.products by 5 %} 
  {% for product in collection.products %}
  {% endfor %}
{% endpaginate %}

从我们的示例中我们可以看到,paginate 标签包含 paginate 关键字,后面跟着返回产品数组的对象,然后是 by 参数和数值。根据我们与 by 参数一起包含的数值,这个数值可以从一到五十,paginate 标签将知道它应该每页显示的最大项目数。在 paginate 标签内部,我们可以访问 paginate 对象的属性。为了使本书简明扼要,我们不会详细介绍 paginate 对象。有关详细信息,请参阅 shopify.dev/docs/themes/liquid/reference/objects/paginate

渲染标签

render 标签允许我们将片段文件的内容渲染到我们选择的位置。正如我们回忆起 第一章开始使用 Shopify,片段文件允许我们通过引用它们的名称在 模板/部分中重用重复的代码块:

{% render "snippet-name" %}

从我们的示例中我们可以看到,我们只需要使用 render 关键字,后面跟着片段文件的名称,我们就可以渲染片段文件中的任何内容。通过使用片段文件,我们不仅使我们的代码更易于阅读,而且通过重用相同的代码块,我们还使它更容易维护。

重要提示:

在渲染片段之后,我们不会自动访问在父元素中创建的变量。此外,父元素不能访问在片段中定义的变量。

让我们创建一个名为 collection-form 的新片段文件,我们将把 else 语句的整个内容从我们在集合页面内创建的 for 循环中移动过来:

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {% render "collection-form" %}
  {% endif %}
{% endfor %}

虽然前面的代码看起来是正确的,但如果我们尝试执行它,最终会出错。为什么?

正如我们所回忆的,代码片段文件无法访问父元素中定义的变量。在这种情况下,该变量是我们定义在for循环中的产品,我们在代码片段中将其称为form参数。为了解决这个问题,我们需要将变量作为参数传递给代码片段:

{% for product in collections["outdoor"].products offset: 4 %}
  {% if product.price > 10000 %}
    {% continue %}
  {% else %}
    {% render "collection-form", product: product %}
  {% endif %}
{% endfor %}

我们创建了一个名为product的参数,并将其分配给最初创建的变量。我们本可以给我们的参数起任何我们喜欢的名字,例如collection_product。然而,在这种情况下,我们也必须更新form标签内的product参数,从product更改为collection_product,以及将product.title更改为collection_product.title,目前位于我们的代码片段文件中,以匹配新的对象关键字。

一旦我们通过参数将变量分配给代码片段,我们就可以独立于父模板中的值修改代码片段内的变量值。即使我们在代码片段内覆盖了变量值,该变量在父模板中仍将保持其值。请注意,我们可以使用其参数将所需数量的变量传递给我们的代码片段。然而,如果我们想将一个对象传递给我们的代码片段怎么办呢?

就像变量一样,我们可以使用withas参数将对象传递给代码片段文件。然而,我们只能传递一个对象作为参数:

{% render "snippet-file" with collections["collection-handle"] as featured_collection %}

在我们之前的例子中,我们使用了render参数将collection对象传递给代码片段文件,在那里我们可以使用featured_collection关键字访问它。

我们可以使用render标签的最后一个参数是for参数。使用foras参数,我们可以为每个出现渲染一个代码片段。让我们尝试重构我们的集合页面上的代码,通过将其移动到collection-form代码片段内部,并使用for参数来渲染产品。然而,请注意,我们无法复制原始for标签上目前拥有的参数,因为render不接受额外的参数。此外,我们还需要从表单标签中删除captureproductId参数,因为increment标签与render参数不兼容。

我们需要做的第一件事是将ifelse语句移动到代码片段文件中,并相应地放置它们:

{% if product.price > 10000 %}
  {% continue %}
{% else %}
  {% form "product", product, data-location: "collection" %}
    {{ product.title }}
  {% endform %}
{% endif %}

剩下的唯一事情就是向render标签添加额外的参数。在下面的例子中,我们包括了foras参数与render标签一起,这使得我们能够迭代collections["outdoor"].products数组,并为我们的集合中的每个产品迭代渲染代码片段。此外,我们将iteration对象作为参数传递给我们的代码片段,我们可以在其中使用它:

{% render "collection-form" for collections["outdoor"].products as product %}

由于我们已经在我们的代码片段文件中使用了product关键字,我们成功地将对象传递给了产品表单参数和产品标题。如果我们现在运行我们的代码,我们会看到结果与之前完全相同,但我们的代码看起来要干净得多。

原始标签

raw标签允许我们直接在页面上输出未解析的 Liquid 代码。我们可以通过将需要未解析的内容包裹在rawendraw标签中来使用raw标签:

{% raw %}
  The name of our collection is {{ collection.title }}.
{% endraw %}

在我们之前的例子中,我们创建了一条消息以动态更新收藏夹名称。然而,由于我们将整个消息包裹在raw标签中,Liquid 将不会处理它。相反,它会以完全相同的方式返回给我们:

The name of our collection is {{ collection.title }}.

这种功能非常有用,尤其是在处理冲突的语法,如 Handlebars 时。

注释标签

如其名所示,comment标签允许我们在 Liquid 模板文件中留下注释。位于开闭注释标签之间的任何文本都不会在 Liquid 模板文件中渲染:

We are currently learning about the comment tags. {% comment %} Comment tags are a smart way to comment on our code. {% endcomment %}

由于我们将消息的第二部分包裹在comment标签中,Liquid 只会渲染第一部分:

We are currently learning about the comment tags.

注释标签非常有用,因为它们允许我们在文件中留下必要的信息,而不会用可见的注释污染 DOM。

已弃用的标签

已弃用的标签是被认为是过时的 Liquid 标签,我们不应再在我们的开发过程中使用它们。然而,我们可能在一些较旧的主题中遇到它们,因此识别它们并了解它们的功能是很重要的。

Shopify 弃用的唯一标签是include标签,它的工作方式与之前我们提到的render标签类似:

{% include "snippet-name" %}

两者之间唯一的区别在于,当使用include标签渲染代码片段时,我们的代码片段内部的代码可以自动访问和修改其父模板内的变量。这不仅会引发许多性能问题,还会使我们的代码更难以维护,这就是 Shopify 用render标签替换它的原因。

摘要

在本章的第三部分,我们学习了 Liquid 编程逻辑,它允许我们在特定条件下选择应该执行哪个代码块。我们了解了变量和迭代标签,以及如何将它们与不同的编程逻辑结合使用,以重复执行代码块并仅恢复特定结果。

最后,我们学习了不同类型的主题标签以及我们如何使用它们来输出特定模板的 HTML 标记。通过学习如何使用代码片段使我们的代码更易于阅读和维护,我们已经向前迈出了一步,以编写更高质量的代码。这将在下一章特别重要,我们将进一步深入了解 Liquid 核心,并学习更多关于 Liquid 对象及其属性的知识。

问题

  1. 如果我们想在for循环中显示最多七次迭代,同时跳过前三次迭代,我们应该使用哪些参数?

  2. 我们可以将哪些类型的数据分配给使用capture标签创建的变量?

  3. 在以下代码块中存在哪两个问题?

    Liquid for product in collections["outdoor"].products
      if product.price > 10000
        continue
      else
        product.title
      endif
    endfor
    
  4. 我们应该采取什么方法来通过用字符串和变量的组合替换现有的类属性来修改由 HTML 生成的产品表单?

  5. 如果我们想从父元素传递一个对象,应该使用哪个参数?

第四章:第四章:使用对象深入 Liquid 核心

在前三章中,我们已经提到了对象。然而,在本章中,我们将学习关于对象、它们的属性以及一些最佳使用方法。通过学习对象,我们将完成一些已经开始的项目,并着手新的项目以进一步发展我们的知识。

在本章中,我们将涵盖以下主题:

  • 使用全局对象

  • 使用元字段改进工作流程

  • 内容和特殊对象

完成本章后,我们将了解内容对象是什么,为什么它们是必需的,以及如何使用它们,这是创建未来模板的第一步。我们还将了解更多关于我们迄今为止一直在引用的全局对象。

然而,由于它们提供了大量的全局对象和属性,我们不会涵盖所有这些。相反,我们只会解释一些基本的全局对象和属性,这将帮助我们正确理解对象。通过学习对象,我们还将学习理解元字段,这允许我们在我们的商店中存储和动态输出额外的数据。最后,我们将学习关于将帮助我们在我们的商店上输出一些有用功能的特殊对象。

技术要求

虽然我们将解释每个主题,并用相应的图形展示,但考虑到 Shopify 是一个托管服务,我们需要网络连接来跟随本章中概述的步骤。

本章的代码可在 GitHub 上找到,网址为 https://github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter04。

本章的“代码实战”视频可在此处找到:bit.ly/3u7hqyB

使用全局对象

在上一章中,我们提到了对象及其属性。但对象究竟是什么呢?

这些对象,或所谓的 {{ collection.title }},其中 collection 关键字将作为我们的对象,而 title 将是属性。

我们可以直接访问我们想要恢复内容的页面,并调用对象,或者通过其 handle 手动调用特定页面的对象,并将其与变量标签结合,来在任意文件中引用这些全局对象。让我们看看实际操作。

如您从上一章中回忆的那样,当我们正在我们的室内集合页面上进行项目工作时,我们最初使用 collection.title 来恢复集合的名称:

{{ collection.title }}

当在集合页面内部使用时,前面的代码将为我们提供我们所需的数据。然而,如果我们想在访问户外集合页面时访问 indoor 集合对象,该怎么办呢?

这是我们使用页面句柄访问页面对象的知识所在,我们在第二章“理解数据类型”部分的EmptyDrop子节中介绍了这个知识,它对我们有所帮助。

我们可以通过将我们要访问的对象的名称复数化,然后使用方括号[]或点.表示法来通过句柄访问对象:

{% assign customCollection = collections["indoor"].title %}
{% assign customCollection = collections.indoor.title %}

通过定义一个包含室内收藏对象的变量,我们可以通过简单地调用对象,然后是所需的属性,在任何页面上输出该收藏的数据:

{{ customCollection.title }}

虽然我们的对象名称与原始对象名称不同,但它将允许我们输出与我们在收藏模板中使用{{ collection.title }}时相同的信息。

重要提示:

在上一个例子中,我们使用customCollection名称创建了一个变量。然而,请注意,您可以使用任何您选择的名称来创建这个变量,包括收藏本身。虽然collection关键字不是保留的,但我们应该在使用已经使用的关键字时格外小心,因为这可能会导致意外结果。

考虑到全局对象是一个广泛的话题,单独解释每个对象及其属性并不会很有成效。相反,我们将创建几个项目,以便亲自了解如何处理不同类型的对象及其返回的数据类型。

正如我们已经提到的,一个对象,结合属性,允许我们从管理员那里读取信息,并动态输出以创建各种功能。让我们首先熟悉一下收藏和产品对象,我们将使用这些对象来完成上一章中开始创建的Custom collection项目。

自定义收藏

在上一章中,我们创建了一个for循环,输出了户外收藏中价格低于$100.00 的产品名称。我们通过创建一个collection-form片段来实现这一点,我们使用for参数输出其内容,并结合render标签。我们将这个片段放置在我们的collection.liquid模板文件的底部:

{% render "collection-form" for collections["outdoor"]  .products as product %}

在我们的片段内部,我们添加了一个if语句来检查产品价格是否高于10000。如果是,我们应该输出产品form标签和产品标题:

{% if product.price > 10000 %}
  {% continue %}
{% else %}
  {% form "product", product, data-location: "collection" %}
    {{ product.title }}
  {% endform %}
{% endif %}

如果我们通过访问室内收藏页面来预览我们的代码,我们只会看到一系列的名称,所以让我们尝试通过编写一些代码来改进这个片段,使其输出整个产品块而不是仅输出产品的名称。

让我们从重构片段内部的代码开始,通过删除continuecapture标签,通过删除else语句来精炼我们的语句,将{{ product.title }}包裹起来,将其放置在产品表单上方,并最终从我们的产品form标签中删除额外的参数:

{% if product.price < 10000 %}
  <p>{{ product.title }}</p>
  {% form "product", product %}
  {% endform %}
{% endif %}

新重构的代码将执行与之前相同的功能,但现在,它将更容易理解和维护。

让我们继续创建我们的产品块。目前,我们只有一个产品名称,所以让我们包括一个超链接,当我们点击产品名称时,它将重定向我们到实际的产品名称。

我们可以通过将{{ product.title }}包裹在超链接标签内,并设置其href属性为{{ product.url }}来实现这一点。这将返回相对路径:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <p>{{ product.title }}</p>
  </a>
  {% form "product", product %}
  {% endform %}
{% endif %}

添加超链接确保了我们通过点击产品名称将被重定向到实际的产品页面。接下来,我们需要做的是包括我们每个产品的图片。

我们可以通过在超链接标签内创建一个图像 HTML 标签,位于产品标题上方,并将它的src属性设置为{{ product | img_url }}来实现这一点。这将返回一个字符串,指向 Shopify 的内容分发网络CDN)上的产品图片位置:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
  </a>
  {% form "product", product %}
  {% endform %}
{% endif %}

我们的产品块开始看起来好多了,但我们仍然需要显示产品的价格。我们可以通过将{{ product.price | money }}包裹在pHTML 标签内来包括产品价格:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
    <p>{{ product.price | money }}</p>
  </a>
  {% form "product", product %}
  {% endform %}
{% endif %}

小贴士:

注意,我们在imageprice对象之间通过管道字符添加了一个小的修改。这个修改被称为过滤器,它帮助我们修改我们本应从对象接收到的输出。

例如,如果我们调用具有price属性的product对象,我们会收到一个没有格式的数字值,例如2599。然而,通过将money过滤器应用于对象,我们自动将原本无意义的数字转换为字符串数据类型,并按照我们选择的货币格式进行格式化,结果是一个$25.99字符串。

我们现在不会过多地详细介绍过滤器,因为我们将在下一章学习它们。目前,关于过滤器的基本信息将足够。让我们回到我们的例子。

到目前为止,我们已经包括了产品名称、图片和价格,这些检查了展示产品所需的大部分必要框。然而,请注意,产品的form目前是空的。让我们通过引入一个input元素来改变这一点,使其为submit类型,这样我们就可以直接从收藏页面购买产品:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
    <p>{{ product.price | money }}</p>
  </a>
  {% form "product", product %}
    <input type="submit" value="Add to Cart"/>
  {% endform %}
{% endif %}

通过添加添加到购物车按钮,我们创建了一个按钮,它应该允许我们直接从收藏页面购买产品,而无需导航到产品页面。然而,如果我们现在点击它,我们会遇到一个错误,指出参数缺失或无效:缺少或无效的必需参数:items

![图 4.1 – 缺少参数提交产品表单的结果图片

图 4.1 – 缺少参数提交产品表单的结果

我们缺少的参数是我们想要购买的变体的 id 属性。

Shopify 上的每个产品最多可以有三种不同的选项集。例如,一个产品可以有多个尺寸、颜色和材料。这三个选择的每一种组合都会生成一个唯一的数字,称为变体 id,它告诉我们产品 form 应该将哪种选项组合放入购物车中。

注意,这些选项完全是可选的,因为我们甚至可以有一个没有任何变体选项的产品。然而,即使在这种情况下,我们仍然需要包含变体 id。我们应该将这个变体 id 作为 hidden HTML 输入元素的 value 属性,其 id 作为其 name 属性:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
    <p>{{ product.price | money }}</p>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{ product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
{% endif %}

通过添加变体 id,我们现在拥有了一个完全功能的产品 form。通过点击 id 并将其添加到我们的购物车中。

注意,点击 添加到购物车 按钮后,我们也会自动重定向到购物车页面。这是我们可以在两种方式中纠正的默认行为。

第一种方法需要我们在产品 form 标签中添加 return_to 参数,这将允许我们设置在提交表单后应返回的页面。我们可以通过回到前面的章节并重新访问位于 Theme tags 部分的 The form tag 子部分来提醒自己如何使用 return_to 参数。另一种方法是使用 Shopify Ajax API,我们将在本书的后面部分学习。

让我们暂时回到室内集合。让我们查看整个集合页面,并将初始集合产品与底部的自定义集合进行比较。你会注意到,除了比我们的集合样式好得多之外,初始集合产品还包含一个红色销售徽章,以及常规和折扣价格。

在管理员中,每个产品都包含两个不同的价格字段,位于具有 compare_at_price 属性的 product 对象下。

让我们回到集合页面的代码,并对其进行修改,使其包括销售徽章和比较价格,以匹配初始集合。我们可以通过将 {{ product.compare_at_price | money }} 包裹在 span 元素中,并将其放置在 {{ product.price | money }} 之后来显示比较价格。此外,我们可以通过在 span 标签内创建一个简单的字符串消息并将其放置在 price 元素下方来实现销售徽章:

{% if product.price < 10000 %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
    <p>{{ product.price | money }}<span>{{         product.compare_at_price | money }}</span></p>
    <span>sale</span>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{         product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
{% endif %}

我们已经为 Custom collection 项包含了所有必要的元素。然而,相当多的产品没有显示其比较价格和销售徽章,这是由于我们第一行上的 if 语句导致的,以便显示具有定义的比较价格的产品:

{% if product.compare_at_price != blank %}
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url }}"/>
    <p>{{ product.title }}</p>
    <p>{{ product.price | money }}<span>{{         product.compare_at_price | money }}</span></p>
      <span>sale</span>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{         product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
{% endif %}

通过修改if语句,我们确保只显示正在销售的产品,从而为我们的初始集合创建一个自定义的促销添加。剩下要做的就是更新 HTML 格式和添加一些 CSS 样式。虽然格式化和样式完全是可选的,但我们建议您使用相同的格式,这样在未来的更改中会更容易跟进。HTML 格式化代码和一些基本的 CSS 样式可以在以下 GitHub 链接中找到,位于Learning Projects目录下,名称为Custom collectionhttps://github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter04/Learning Projects/Custom collection。

这个 GitHub 仓库包含三个文件,每个文件都根据代码应包含的位置命名。

如果我们现在预览我们的室内集合,我们将看到与开始时相比有显著的改进:

![图 4.2 – 完整自定义集合项目的示例图 4.2 – 完整自定义集合项目的示例

图 4.2 – 完整自定义集合项目的示例

到目前为止,我们只提到了返回单个值的属性的对象,例如product.priceproduct.titleproduct.first_available_variant.id。然而,它们也可以返回数组,甚至可以作为我们编程逻辑的辅助工具。

返回数组的对象中最常用的是linklist对象。结合link对象,它将帮助我们读取导航管理员中的菜单数据,并帮助我们创建自定义导航菜单。

自定义导航

对于我们的下一个项目,我们将创建一个专门针对我们的集合页面的多级导航菜单。然而,在我们了解linklistlink对象之前,我们需要创建一个导航菜单。让我们开始吧:

  1. 我们可以通过导航到管理员,点击在线商店以展开它,然后点击导航链接来创建一个新的导航菜单,我们可以通过点击添加菜单按钮来创建新菜单:![图 4.3 – 访问导航菜单的示例 图 4.3 – 访问导航菜单的示例

    图 4.3 – 访问导航菜单的示例

  2. 点击添加菜单按钮后,将菜单的标题设置为室内导航。一旦我们设置了菜单的名称,页面的句柄将自动填充,因此无需手动修改。

  3. 我们现在已准备好填充菜单。点击添加菜单项按钮,然后点击链接字段,这将自动显示一个下拉菜单。对于第一个菜单项,点击集合,然后点击所有集合,这将立即填充菜单项的名称和链接字段。点击添加按钮完成添加菜单项。

  4. 通过创建两个名为并链接到两个集合室内室外的菜单项,然后为我们的商店中的任意四个产品创建四个菜单项,重复执行前一个步骤六次。

  5. 一旦我们创建了额外的菜单项,将室内室外菜单项移动到所有集合菜单项下以创建嵌套菜单。我们可以通过点击菜单项名称前的六个点来完成此操作,然后按住并移动它们到所有集合菜单项上,直到缩进的蓝色线条出现,此时我们应该释放点击:![图 4.4 – 在导航菜单内创建下拉菜单的示例 图片

    图 4.4 – 在导航菜单内创建下拉菜单的示例

  6. 重复前面的步骤,将两个产品菜单项移动到已经移动到所有集合菜单项下的室内集合菜单项下。对室外集合菜单项和剩余的两个产品菜单项重复相同的操作。

如果我们一切操作正确,最终应该得到一个包含单个菜单项的室内导航菜单,该菜单项包含所有集合,其中包含两个集合菜单项,每个集合菜单项包含两个产品菜单项。假设按照前面的步骤操作后,菜单格式看起来并不相同。在这种情况下,我们可以查阅 Shopify 关于嵌套菜单项的文档,在那里我们可以找到更详细的说明和关于此主题的 YouTube 链接。

关于创建嵌套导航的更多信息,请参阅 https://help.shopify.com/en/manual/online-store/menus-and-links/drop-down-menus。

重要提示:

在单个菜单内我们可以有的最大嵌套菜单项数是三个层级长。我们可以将我们之前创建的菜单视为一个三级导航菜单,这是极限。

现在我们已经成功创建了导航菜单,我们可以开始创建一个变量,我们将把linklist对象的值分配给这个变量,然后是我们要访问的导航的句柄。记住,当我们使用句柄访问对象时,我们需要通过在对象末尾添加字母s来使对象复数化:

{% assign collection-menu = linklists.indoor-navigation %}

有了这个,我们已经成功将室内导航的对象保存到了collection-menu变量中。现在让我们在collection.template内部测试它。为此,我们将调用collection-menu对象,然后是titlelevels属性,就在{% section 'collection-template' %}上方,这样我们就可以看到我们的导航名称以及它包含的嵌套菜单数量:

{% assign collection-menu = linklists.indoor-navigation %}
{{ collection-menu.title }} - {{ collection-menu.levels }}

如果我们预览我们的室内集合,我们将能够看到我们的导航名称以及导航菜单的嵌套层级数:

Indoor Navigation - 3

现在我们已经确认我们已经恢复了正确的菜单对象,我们可以删除collection-menu.titlecollection-menu.levels行。对于下一步,我们需要使用for标签遍历对象内部的链接数组,我们可以通过调用collection-menu对象,然后是links属性来恢复它:

{% assign collection-menu = linklists.indoor-navigation %}
  {% for link in collection-menu.links %}
    {{ link.title }}
  {% endfor %}

如果我们现在预览室内集合,我们会注意到页面上唯一可见的菜单项是第一级项,所有集合。假设我们想要遍历位于所有集合菜单项内部的额外嵌套菜单。在这种情况下,我们将在第一个循环内部创建第二个循环来恢复第二级的数据。

关键区别在于,我们不会使用collection-menu作为我们的对象。相反,我们将使用第一个for循环中的link对象,结合links属性,这将使我们能够访问link对象内部的链接数组:

{% assign collection-menu = linklists.indoor-navigation %}
{% for link in collection-menu.links %}
  {{ link.title }}
  {% for sub_link in link.links %}
    {{ sub_link.title }}
  {% endfor %}
{% endfor %}

通过遍历link对象,我们能够恢复嵌套在所有集合菜单项中的数组链接。使用同样的技术,我们可以遍历室内导航菜单最后一层的链接数组:

{% assign collection-menu = linklists.indoor-navigation %}
{% for link in collection-menu.links %}
  {{ link.title }}
  {% for sub_link in link.links %}
    {{ sub_link.title }}
    {% for sub_sub_link in sub_link.links %}
      {{ sub_sub_link.title }}
    {% endfor %}
  {% endfor %}
{% endfor %}

现在我们已经拥有了创建工作悬浮导航菜单所需的所有必要元素。唯一剩下的事情就是添加一些HTML标签并应用必要的样式来创建悬浮下拉效果。然而,在我们进行样式设计之前,让我们尝试变得更有创意。

注意,我们的菜单链接仅仅是链接。但如果我们想通过在每个产品菜单项中显示图片来更有创意呢?为了实现这一点,我们需要确定哪些菜单项指向产品,这可以通过使用type属性并比较返回值是否等于product_link字符串来完成。由于我们已经知道导航的第三级只包含产品菜单项,因此我们只将此功能包含在第三个for循环中:

{% assign collection-menu = linklists.indoor-navigation %}
{% for link in collection-menu.links %}
  {{ link.title }}
  {% for sub_link in link.links %}
    {{ sub_link.title }}
{% for sub_sub_link in sub_link.links %}
      {% if sub_sub_link.type == "product_link" %}
        <img src="img/{{ sub_sub_link | img_url: "250x250"}}"/>
      {% endif %}
{{ sub_sub_link.title }}
    {% endfor %}
  {% endfor %}
{% endfor %}

现在,这个方法应该能够工作,并且我们应该能够在导航菜单中看到我们产品的四张图片。然而,请注意,sub_sub_link仍然只是一个link对象,而link对象并没有附加图片。为了显示附加到产品上的图片,我们必须恢复链接指向的产品对象。

我们可以通过修改IMG标签内的sub_sub_link对象来实现这一点,使其包含object属性:

{% assign collection-menu = linklists.indoor-navigation %}
{% for link in collection-menu.links %}
  {{ link.title }}
  {% for sub_link in link.links %}
    {{ sub_link.title }}
{% for sub_sub_link in sub_link.links %}
      {% if sub_sub_link.type == "product_link" %}
        <img src="img/strong>| img_url:**
                "250x250" }}"/>**
**      {% endif %}**
**      {{ sub_sub_link.title }}**
**    {% endfor %}**
  {% endfor %}
{% endfor %}

随着object属性的添加,我们现在可以访问整个产品对象,包括其标题、价格、图片以及所有其他数据。相比之下,sub_sub_link.object.price将返回与直接编写product.price相同的结果,这是我们之前在项目中用来显示自定义集合产品价格的方法。

现在唯一剩下的事情就是为我们的代码提供一些 HTML 格式并应用必要的样式。HTML 格式的代码以及一些基本的 CSS 样式可以在以下 GitHub 链接中找到,名称为Custom navigation

https://github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter04/Learning Projects/Custom Navigation.

这个 GitHub 仓库包含两个文件,每个文件都按照代码应包含的位置命名。

在进行这个项目时,我们能够创建一个基础版本的巨量菜单,使我们能够轻松输出我们页面上的任何图像。虽然它可能看起来并不令人印象深刻,但我们从这个项目中学到的知识教会了我们如何创建任何导航菜单,以及一个自定义子集合页面,我们可以输出其他集合。

到目前为止,我们所有的项目都与从我们的管理后台恢复预定义数据有关。然而,如果我们需要捕获有关特定产品定制的相关数据,显示在购物车页面上,然后随订单转发捕获的数据呢?

产品定制

在我们的下一个项目中,我们将在产品页面上创建一个自定义 HTML 输入,这将允许我们捕获客户可能输入的任何数据,并学习如何转发输入的值,包括订单本身。

为了实现这个功能,我们将使用line_item对象。line_item代表我们购物车中的每个项目。我们可以通过cart对象访问line_item对象,然后是items属性,这将为我们提供访问每个产品的line_item对象的权限。

在我们能够使用line_item object在购物车页面上输出数据之前,我们需要创建一个字段来捕获这些数据。让我们首先导航到我们的product.liquid模板,并找到product表单的起始标签。正如我们所见,product表单标签不在这个文件中,但我们确实有一个section标签,正如你可能从上一章回忆起来的,它允许我们渲染一个静态部分。

让我们通过导航到product-template.liquid部分文件来继续操作。我们可以通过悬停在部分标签的名称上并点击小箭头来访问它。或者,我们可以在Sections目录中找到它。在找到product表单标签后,我们可以通过添加一个text类型的 HTML 输入标签来开始创建定制功能,我们将使用它来捕获与每个特定产品相关的信息。我们可以在product表单标签的顶部添加此字段,就在第一个unless语句之上:

{% form 'product', product, class:form_classes, novalidate:   'novalidate', data-product-form: '' %}
            {{ form | payment_terms }}
            <input type="text" placeholder="Your Name"/>
            {% unless product.has_only_default_variant %}

如果我们尝试预览我们的产品页面,我们会看到一个带有您的姓名作为占位符的输入字段:

![图 4.5 – 产品页面上自定义字段的示例]

![Figure 4.05_B17606.jpg]

图 4.5 – 产品页面上自定义字段的示例

然而,如果我们填写输入,将产品添加到购物车,并通过点击右上角的购物车图标访问我们的购物车页面,我们会注意到我们没有成功捕获与产品相关的数据。为了保存line_item数据值,我们需要通过添加name属性以name="properties[Your Name]"格式修改 HTML 输入:

<input type="text" name="properties[Your Name]" placeholder="Your Name"/>

name属性是一个预定义的属性,它允许我们捕获input元素的值,后面跟着一个强制性的关键字,称为properties,以及一对方括号。方括号内的任何值(在我们的情况下,您的姓名)将作为该属性名称。

在放置了name属性后,如果我们填写输入字段,点击line_item属性名称,名称和值就成功捕获并链接到每个产品。

当处理较新的主题,例如我们正在使用的主题时,这将是在每个产品中捕获自定义的最后一步。然而,对于一些仍然相当普遍的旧主题,我们可能需要编写一些代码来在购物页面上显示line_item属性。

在编写此代码之前,我们需要确定我们应该在哪里添加我们的代码。正如我们之前提到的,为了访问line_item对象,我们需要使用cart.items来恢复购物车中的产品数组,这些产品应该已经存在于我们的cart.liquid部分文件中:

{% for item in cart.items %}
{% endfor %}

通过cart.items循环,我们获得了每个产品的对象,这与product对象类似,为我们提供了访问各种属性的能力,例如product对象的标题、价格以及我们添加到购物车中的数量,以及其属性。

我们可以通过使用item作为我们的对象,然后是properties属性来访问每个产品的属性。然而,由于我们可以为每个产品捕获多个属性,item.properties将返回一个数据数组,这意味着我们需要使用for标签:

{% for property in item.properties %}
  {{ property }}
{% endfor %}

注意,{{ property }}被认为是一个数组数据类型,因为它包含line_item属性名称及其值。如果我们使用此代码在页面上输出line_item代码,我们最终会得到两个值粘在一起。由于使用另一个for标签遍历包含两个元素的数组会过于冗余,我们可以使用firstlast过滤器来输出分割的值:

{% for property in item.properties %}
  <span>{{ property.first }}</span>:<span>{{ property.last     }}</span>
{% endfor %}

firstlast过滤器,正如其名称所暗示的,允许我们访问数组中的第一个和最后一个元素。然而,由于我们希望将我们的数组拆分为两个单独的元素,这非常完美,因为它帮助我们避免了编写另一个for循环。我们将在下一章中了解更多关于此和其他过滤器的内容。

重要提示:

将相同的产品变体多次添加到购物车中,并且具有不同的line_item属性值,不会覆盖我们之前添加到购物车中的产品,也不会影响其line_item属性值。相反,每个产品将位于新的一行上,就像它是不同的产品一样。

唯一一次我们可以在同一行包含同一产品的多个变体的情况是,如果产品变体包含相同的数据,包括line_item属性。以下是一个示例:

![图 4.6 – 将行项目属性链接到不同产品变体的示例Figure 4.06_B17606.jpg

图 4.6 – 将行项目属性链接到不同产品变体的示例

如果我们点击line_item,那么在结账摘要中也会显示其值。请注意,如果我们通过我们的结账页面完成购买,相同的line_item属性将在订单管理界面中可见。

重要提示:

我们只能通过 Shopify 的默认结账来捕获line_item属性。如果我们通过任何其他结账完成支付,例如将line_item属性添加到我们的产品中,它们将不会在订单管理界面中可见。

有了这些,我们已经学会了如何为我们的每个产品捕获自定义数据并在购物车和结账页面上显示它们。然而,有时我们被迫恢复line_item数据,并从购物车和结账页面上视觉上隐藏它。我们可以很容易地使用一些 CSS 代码从购物车页面隐藏line_item数据,但是考虑到我们没有访问结账页面的权限,我们应该如何修改代码呢?

这就是下划线字符能帮到我们的地方。如果我们想要收集line_items并在我们的管理界面中看到它们,同时从结账页面视觉上隐藏line_items属性,我们需要修改product.liquid部分文件中的name属性,在方括号内包含下划线:

<input type="text" name="properties[_Your Name]" placeholder="Your Name"/>

任何以下划线作为方括号内第一个字符的属性名在结账页面上将不可见。然而,一旦我们在管理界面收到订单,它仍然可见。

除了允许我们自动从结账页面隐藏line_items属性外,下划线字符还帮助我们创建一个更自动化的过程来隐藏购物车页面上的line_items属性,而无需为每个想要隐藏的line_items属性编写 CSS 代码。

随着下划线字符的引入,我们现在有一个独特的字符,我们可以用它来过滤我们想要显示和隐藏的line_items属性。

由于我们处理的是字符串类型的数据,我们可以使用truncate过滤器,正如其名称所暗示的,它允许我们截断字符串。truncate过滤器接受以下两个参数:

  • 第一个参数是一个必需的数字值,它允许我们设置期望truncate过滤器返回的字符数。

  • 第二个参数是一个可选参数,它允许我们设置要附加到每个返回字符串值上的特定字符串值。请注意,如果我们不包括第二个参数,默认情况下,truncate过滤器将在字符串末尾附加三个点,这将计入之前参数数值的count

由于我们想要检查 line_items 键值是否以下划线字符作为字符串的第一个字符,我们可以应用 truncate 过滤器并将第一个参数设置为 1。然而,我们还需要包含第二个参数并将其设置为空字符串,以避免之前提到的省略号:

{% for property in item.properties %}
  {% assign first_character_in_key = p.first | truncate: 1,     '' %}
  {% unless first_character_in_key == '_' %}
    <span>{{ property.first }}</span>:<span>{{         property.last }}</span>
  {% endunless %}
{% endfor %}

注意,在整个项目过程中,我们只使用了单个 text 输入类型。然而,我们可以自由使用任何可用的输入类型,包括 datecolorradioselect 输入。唯一的限制是不能使用 file 上传输入类型与 Ajax 一起使用。产品 form 标签还需要包含 enctype="multipart/form-data" 属性以捕获文件上传输入值。

通过这个项目,我们学习了如何创建一个有价值的特性,这将使我们能够为每个产品模板或每个单独的产品创建特定的定制。我们可以将其设计为一个简单的具有单个输入的功能,或者创建一个包含各种输入的完整表单,在购买产品之前填写。

我们还可以轻松地使用 line_items 并将其简单地粘贴到我们的产品模板中。我们可以在 https://ui-elements-generator.myshopify.com/pages/line-item-property 找到 Shopify UI 元素生成器。

假设我们想要保存我们管理中某些页面的页面特定数据。然而,如果我们导航到我们的管理界面并打开一个产品、收藏夹或任何其他页面,我们会注意到每个页面都包含预定义的一定数量的字段来存储数据。这就是元字段对象大显身手的地方。

使用元字段改进工作流程

元字段是全球对象,允许我们在管理界面中存储额外的数据并将其输出到店面。因此,它们是创建具有独特内容的复杂设计的强大且必要的工具。

元字段由三个必填元素组成:

  • 命名空间,我们可以用它来分组不同的元字段,只要它们共享相同的命名空间。

  • key 属性,它允许我们访问特定的元字段。

  • 一种 json_string 数据类型。

我们还可以使用 description 字段来为元字段的简短描述,与之前的三个相比,这是可选的。

元字段虽然功能强大,但有一个相当大的缺点,那就是它们只能通过第三方应用程序访问。然而,自从 Shopify Unite 2021 大会以来,元字段功能已经原生地集成在 Shopify 控制面板中,不仅如此,它还得到了相当大的升级!

在撰写本文时,metafields 功能虽然可用,但仅部分可用,因为我们只能访问产品和变体元字段。pageblogarticlecollectionordercustomershop 元字段尚未发布。因此,我们将学习如何通过第三方应用程序和 Shopify 控制面板来处理这些元字段。

考虑到今天的大多数主题仍然依赖于第三方应用来满足他们的 metafields 需求,让我们先学习如何利用 metafields 应用为我们页面创建自定义内容。

设置 metafields 应用

为了使用和访问 metafields 对象,我们需要安装一个应用或一个允许我们使用此功能的浏览器扩展。出于本书的目的,我们将通过安装一个应用来熟悉 Shopify 应用市场:

  1. 要在我们的商店中安装应用,我们需要导航到我们的管理员界面,点击Apps,位于Sales Channels正上方。在Apps部分,我们将能够看到我们在商店上安装的所有应用的列表。然而,由于我们目前没有任何应用,列表是空的。让我们通过点击右上角的Shop for apps按钮来改变这种情况:图 4.7 – 开始应用安装过程的示例

    图 4.7 – 开始应用安装过程的示例

  2. 在搜索字段内点击metafields

  3. 提交搜索后,我们将收到大量我们可以使用的应用,其中大多数都提供免费计划。出于我们的目的,我们将使用Metafields Guru应用。虽然选择哪个应用完全取决于你,因为它们都提供相同的功能,但我们建议你在这里使用相同的应用,这样在未来的开发中跟进会更容易。点击应用后,你将被重定向到应用窗口。

  4. 在打开应用的着陆页后,我们将能够看到有关页面的更多信息,我们应该始终阅读这些信息,以检查应用是否提供了我们需要的功能。让我们点击Add app按钮,它应该立即将我们重定向到我们的商店并开始应用安装过程。另一方面,如果我们没有登录,我们需要在弹出窗口内提交商店 URL 并点击Log in按钮,这将开始应用安装过程:图 4.8 – Shopify 应用商店中登录弹出窗口的示例

    图 4.8 – Shopify 应用商店中登录弹出窗口的示例

  5. 在开始安装过程后,我们将看到一个最后的窗口,我们需要向应用提供请求的访问权限。由于我们正在开发商店上工作,我们可以立即通过点击Install app按钮来继续。然而,如果我们正在为客户管理一个商店,强烈建议我们永远不要在别人的商店上安装应用,即使他们要求我们这样做。商店所有者应该是在他们了解应用将收集的所有个人信息后安装任何必要的应用的人。否则,我们可能对任何可能的问题负责。

现在安装了元字段大师应用后,我们将在应用内看到第一个视图,其中我们可以看到我们可以为创建元字段的所有不同类型的页面:

图 4.9 – 我们可以使用元字段对象的不同页面类型

图 4.9 – 我们可以使用元字段对象的不同页面类型

如前图所示,元字段是强大的工具,允许我们自定义我们商店的任何部分。因此,让我们从为产品页面创建元字段开始:

  1. 我们需要做的第一件事是导航到我们的管理员中的产品部分,选择我们选择的任何产品,然后点击它。

  2. 打开产品页面后,点击右上角的更多操作按钮,以显示编辑元字段按钮。点击它以启动元字段大师应用:图 4.10 – 为特定页面启动元字段大师应用的示例

    图 4.10 – 为特定页面启动元字段大师应用的示例

  3. 一旦进入应用,我们将能够看到所有针对此特定产品(或如果我们选择变体卡,甚至是变体)的元字段列表。然而,由于我们没有,屏幕是空的。让我们通过点击创建元字段按钮来创建我们的第一个元字段。

  4. 在这个第一个字段中,我们可以选择我们将要保存在这个特定元字段中的数据类型。这就是我们将使用keynamespacemetafield值来填充其他三个字段的地方。在我们的示例中,我们可以选择String值,然后在key字段中输入单词example

    namespace字段已经包含global关键字,但我们可以将任何类型的文本输入到最后一个字段中。例如,我们将使用Metafields are awesome!。填写完所有字段后,通过点击右上角的保存按钮保存您的更改:

图 4.11 – 创建产品元字段的示例

图 4.11 – 创建产品元字段的示例

现在我们已经学习了如何创建元字段,是时候学习如何将之前保存的数据输出到我们的店面上了。

渲染元字段值

我们可以通过为我们创建元字段的页面对象访问metafields对象,然后是metafields对象,然后是namespace,然后是key

{{ product.metafields.global.example }}

我们可以在任何我们可以访问product对象的地方输入元字段代码。然而,出于我们的目的,我们应该将代码放置在product.liquid部分中,正好在我们之前项目中包含的line_item输入之上。

现在我们已经创建了产品元字段并且已经放置了元字段对象代码,我们所需做的只是测试它。然而,请记住,我们只为特定产品创建了一个元字段,这意味着我们只能在那个特定产品上预览它。如果我们预览为创建元字段的产品,我们将在页面上正确显示以下元字段字符串值:

Metafields are awesome!

通过这样,我们已经成功创建并显示了一个单个元字段值。然而,如果我们有更多类似的元字段怎么办?我们该如何输出所有这些字段呢? 我们最初提到,只要它们共享相同的namespace,我们就可以使用namespace属性将不同的元字段分组在一起。使用namespacefor标签结合,我们可以遍历具有相同namespace的所有元字段并恢复它们的值:

{% for value in product.metafields.global %}
  {{ value }}
{% endfor %}

运行前面的代码将恢复任何具有global关键字作为其namespace的元字段。然而,由于这次我们没有使用key属性,我们将以数组格式接收结果:

exampleMetafields are awesome!

为了分割数组,我们需要使用firstlast过滤器,就像我们之前做的那样,来分割购物车页面上的line_item对象:

{% for value in product.metafields.global %}
  {{ value | first }}:{{ value | last }}
{% endfor %}

使用firstlast过滤器,我们已经成功将数组分割成两个独立的元素,我们现在可以按需使用它们。

通过这样,我们已经通过第三方应用学习了如何输出单个和多个元字段值,迈出了重要的一步。

虽然有帮助,但通过第三方应用添加元字段需要相当多的硬编码,我们可以通过通过 Shopify 仪表板创建元字段来避免这一点。然而,尽管元字段得到了强大的升级,它们也有一个先决条件,我们之前已经了解过。因此,在接下来的章节中,我们将学习如何通过 Shopify 仪表板设置和处理元字段,以更好地理解这一点。

使用元字段,我们现在可以为每个页面添加组织良好、独特的内 容,甚至创建复杂的功能。例如,使用元字段,我们可以创建产品折叠面板,展示精选的推荐产品,并使用它们来显示每个产品/变体的预期交货时间和其他功能。我们只受限于我们的灵感。

到目前为止,我们涵盖的几个项目可能看起来与我们可以使用的不同类型的全局对象数量相比显得不足。然而,通过这些项目获得的知识已经让我们走上了完全理解对象的正确道路。我们的主要焦点不仅仅是列出对象及其属性,我们可以快速从 Shopify 文档中获得这些信息,而是学习如何通过我们明天将作为 Shopify 专家从事的实际项目来使用不同的对象和属性。

在我们可以说我们理解对象的工作原理之前,我们需要更多地了解内容对象,没有它们,我们就无法在我们的页面上输出任何内容。

内容和特殊对象

之前,我们学习了如何使用全局对象在模板和部分文件中输出管理员的数据。现在,是时候学习如何使用内容对象来输出模板和部分文件的内容,以及 Shopify 店铺运行所需的任何其他资产。我们可以将内容对象分为三个不同的组:content_for_headercontent_for_indexcontent_for_layout

所有三种内容对象都有描述性的名称,告诉我们它们的功能。然而,为了确保我们完全理解它们的重要性,我们将为每个对象提供简短的解释。

content_for_header 对象

content_for_header 是位于 theme.liquid 布局文件内的一个强制对象——更具体地说,位于 HTML <head> 标签内:

{{ content_for_header }}

这个对象的主要目的是动态地将 Shopify 所需的所有脚本加载到文档的头部。在这些脚本中,我们可以找到 Shopify 和 Google Analytics 脚本,甚至是一些 Shopify 应用所需的脚本。

content_for_index 对象

content_for_index 是一个非强制对象,它位于 index.liquid 模板中,我们可以找到这个模板在 Template 目录下:

{{ content_for_index }}

然而,这个对象允许我们动态地从主题编辑器输出动态部分,因此它是强制性的。没有它,我们就无法从我们的主题编辑器输出任何内容。

content_for_layout 对象

content_for_layout 是位于 theme.liquid 布局文件内的最后一个强制对象。这个对象允许我们加载由其他模板(如 index.liquidcollection.liquid)动态生成的内容:

{{ content_for_layout }}

注意,无法从它们各自的位置删除 content_for_headercontent_for_layout 对象,所以我们不必过于担心它们。然而,了解每个内容对象的功能是至关重要的,因为尽管我们无法删除它们,但我们仍然可以取消注释它们,这可能会对我们的店面造成问题。

最后,除了全局和内容对象之外,我们还有另一组只能在特定情况下使用的对象。我们目前可用的唯一两个对象是 additional_checkout_buttonscontent_for_additional_checkout_buttons 对象,它们将为我们提供一种动态生成一组按钮的方法,这些按钮可以链接到第三方支付提供商的结账页面。

然而,请注意,决定哪些按钮可见取决于我们在管理员中设置的支付方式以及一些其他参数。例如,为了让 Apple Pay 结账按钮可见,除了在管理员中启用支付方式外,客户还必须使用苹果设备才能看到它。

“额外结账按钮”对象允许我们检查商店是否提供使用第三方支付提供商(如 PayPal、Apple 等)的支付方式。假设我们已经为某些第三方支付提供商启用了支付网关,那么“额外结账按钮”对象与一个if语句结合,将返回true,这将允许我们使用我们的下一个特殊对象;即content_for_additional_checkout_buttons

在确认我们已经与第三方启用了这些支付方式后,我们可以使用content_for_additional_checkout_buttons对象为那些支付提供商生成按钮:

{% if additional_checkout_buttons %}
  {{ content_for_additional_checkout_buttons }}
{% endif %}

虽然代码的位置完全是可选的,但它通常放置在购物页面上,紧邻默认的结账按钮旁边。

摘要

在本章中,我们在考虑各种项目的同时,学习了不同类型的对象。在我们的第一个项目“自定义集合”中,我们学会了如何通过创建一个功能齐全的产品表单来自定义集合功能,以访问“产品”对象中的单值属性。第二个项目“自定义导航”教会了我们如何访问和处理那些属性返回数组的对象。通过“产品定制”项目,我们了解了如何捕获多种数据类型,将它们与选定的变体链接起来,并在购物车和结账页面上输出相同的数据。

除了参与不同的项目,我们还很荣幸地学习了如何从商店安装新应用,以及如何使用第三方应用在我们的管理后台中创建额外的输入字段,这使我们能够访问“元字段”对象。

最后,我们学习了不同类型的内容和特殊对象,为什么有些是必需的,在哪里可以找到它们,以及我们如何使用其中一些将我们的商店连接到 Shopify 之外的各个支付提供商。

我们在本章中获得的知识将在下一章特别有用,在下一章中,我们将学习更多关于我们在项目中引用的过滤器。

问题

  1. 在以下代码块中,我们缺少什么来使“表单”功能正常?

    {% form "product", product %}
      <input type="hidden" value="{{    product.first_available_variant.id }}" />
        <input type="submit" value="Add to Cart"/>
    {% endform %}
    
  2. 我们如何通过管理导航中定义的链接来获取“产品”对象的访问权限?

  3. 访问单值和多个“元字段”对象有哪些两种方法?

  4. 如果我们想要捕获“行项目”值并在结账页面上隐藏它,我们需要对输入元素进行哪些调整?

    <input type="text" name="properties[Your Name]" placeholder="Your Name"/>
    

熟能生巧

在上一章中,我们通过各种项目和实现一起工作。然而,只有通过亲自参与项目并强迫自己迈出下一步,我们才能获得正确的理解。到目前为止,我们还没有完成任何个人项目,因为我们还在学习基础知识。然而,随着我们对对象的理解,我们现在可以开始创建自己的解决方案了。

这里我们将涵盖的几个小型项目将帮助我们巩固我们从上一章中获得的一些知识。它还将通过迫使我们跳出思维定势并找到我们尚未遇到的问题的解决方案来挑战我们的极限。

每个项目都将包含关于我们需要做什么的详细信息,以帮助我们实现结果。

我们建议独立于前几章完成每个项目,因为这将确保我们真正理解了我们迄今为止所学的内容。

没有一个单独的项目有正确或错误的解决方案。然而,如果我们有任何困难,我们总是可以查阅本书末尾的项目解决方案。

项目 1

对于我们的第一个项目,我们将在一个通用页面上创建一个自定义收藏夹。然而,这个项目与我们之前工作的项目之间的区别在于,这个自定义收藏夹将是一个动态且可重用的代码块。根据页面名称,我们应该在特色收藏夹中看到不同的产品。

下面是资产步骤:

  1. 创建一个名为 featured-collection.liquid 的新页面模板。

  2. 创建一个新页面,将其命名为与我们商店中的某个收藏页面类似,并分配我们之前创建的新页面模板。

  3. 在当前主菜单导航中创建一个新的菜单项,命名为“项目”,并将新创建的页面作为嵌套菜单添加到“项目”菜单项中。

  4. 创建一个名为 custom-collection 的新片段文件。

下面是分配步骤:

  1. featured-collection.liquid 中包含 custom-collection 片段,并将 collection 作为参数。由于页面名称与我们的收藏夹相似,我们应该使用页面处理程序来创建 collection 对象。

  2. 使用我们之前传递的 collection 对象,使用 for 标签创建一个自定义收藏夹,显示不超过四个产品。

  3. 产品应包含图片、标题、供应商、常规价格、比较价格可见以及一个工作的产品表单。

  4. 如果一个产品有多个变体,包括一个下拉菜单,以便我们可以选择我们想要选择的精确变体。

  5. 在提交表单后,返回我们之前所在的页面。

  6. 一旦完成,任何将 featured-collection.liquid 分配为其模板的页面都应该根据页面名称显示不同类型的产品;例如,室内或室外。

项目 2

对于我们的第二个项目,我们将创建一个子收藏模板页面,我们将能够输出不同的收藏页面。我们将创建的代码,类似于前一个项目,应该是可重用的。我们应该根据我们分配模板的页面名称以及分配给具有相同名称的导航菜单的收藏来获得不同的结果。

下面是资产步骤:

  1. 创建一个名为 page-subcollection.liquid 的新页面模板。

  2. 创建一个新页面并将其分配给我们之前创建的新页面模板。

  3. 创建新的导航菜单,并给它起与我们之前创建的页面相同的名字。

  4. 在新的导航菜单中,至少包含六个收藏菜单项。

  5. 创建一个名为custom-subcollection的新片段文件。

这里是分配步骤:

  1. page-subcollection.liquid中包含custom-subcollection片段,并将subcollection作为参数。由于页面名称与我们的导航菜单相似,我们应该使用页面处理程序来创建subcollection对象。

  2. 使用我们之前传递的subcollection对象,使用for标签创建特定导航菜单内所有收藏页面的列表。

  3. 收藏列表应包含一张图片和标题。

  4. 如果该收藏没有分配图片,我们应该取该收藏中第一个产品的图片并将其作为收藏图片显示。

  5. 完成后,任何被分配custom-subcollection.liquid作为模板的页面应根据其名称以及分配给具有相同名称导航菜单的收藏显示不同的结果。**

第五章:第五章: 深入了解带有过滤器的 Liquid 核心

在前两章中,我们学习了所有的不同 Liquid 标签和对象,现在,我们将关注 Liquid 核心功能的最后一部分,即 |,通过它可以操作不同的数据类型,包括字符串、数字、变量,甚至是对象,使其成为一个引人注目的功能。

我们可以将本章分为以下主题:

  • HTML 和 URL 过滤器

  • 优化产品媒体库

  • 构建产品折叠面板

  • 数学和货币过滤器

  • 探索附加过滤器

到我们完成这一章的时候,我们将精确地了解过滤器为我们提供了多少力量。同样地,就像上一章一样,我们不会简单地列出并逐一解释所有过滤器,而是通过一系列小项目来解释一些基本过滤器。

首先,我们将学习如何通过 HTML 和 URL 过滤器 生成 HTML 元素。其次,理解 媒体过滤器 将帮助我们处理产品媒体库,这是 Shopify 的最新功能之一,也是最受欢迎的功能之一。第三,通过在产品折叠面板项目中工作,我们将学习如何通过各自的过滤器操作字符串和数组类型的数据。最后,通过在产品价格折扣项目中工作,我们将获得必要的 数学和货币过滤器 知识。

技术要求

虽然我们将解释每个主题并附上相应的截图,但考虑到 Shopify 是一个托管服务,你需要一个互联网连接来跟随本章中概述的步骤。

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter05

本章的“代码在行动”视频可在此处找到:bit.ly/3zmum4j

使用 HTML 和 URL 过滤器

在上一章中,当我们处理输出产品图片时,有机会看到一种 URL 过滤器,{{ image | img_url: "400x400" }}然而,URL 过滤器究竟是什么?

image 标签,在其中我们可以添加特定资产的字符串路径作为 href 属性。或者,我们可以将 URL 过滤器与 HTML 过滤器 结合起来,自动生成必要的 HTML 元素及其属性。现在让我们看看它们在实际中的应用。

第一章 Shopify 入门中,我们学习了主题文件中的Assets目录以及它包含我们主题所需的所有内部资产,例如样式表、JavaScript 文件、字体文件和图像。然而,我们首先需要在主题中加载这些文件,按照以下步骤进行,因为它们不会自动在我们将它们上传到Assets目录后对我们可用:

  1. 我们可以通过将文件名用引号括起来,然后跟一个管道符号,再跟asset_url过滤器,来检索Assets目录中文件的路径:

    {{ "theme.css" | asset_url }}
    
  2. 在之前的例子中,我们使用了存储中样式表文件的名称,结合asset_url,这将为我们提供指向此特定文件的字符串路径:cdn.shopify.com/s/files/1/0559/0089/7434/t/4/assets/theme.css?v=10188701410004355449

  3. 现在我们已经恢复了指向我们位置的路由,正如之前提到的,我们有两种选择。第一种选择是使用 HTML link 标签将 CSS 文件与我们的主题链接:

    <link rel="stylesheet" href="theme.css file and its rules with the link tag in place:
    
    

    <link rel="stylesheet" href="//cdn.shopify.com/s/files/1/0559/0089/7434/t/4/assets/theme.css?v=10188701410004355449">

    
    
  4. 然而,正如我们之前提到的,除了使用 HTML link 标签外,我们还可以将 URL 过滤器与 HTML 过滤器结合使用,以生成必要的 HTML 属性:

    {{ "theme.css" | asset_url | stylesheet_tag }}
    

在之前的例子中,我们使用了想要恢复路径的资产名称,然后跟asset_url过滤器,它通常会只返回字符串路径。然而,与stylesheet_tag结合使用时,它将自动生成带有所有必要属性的 HTML link 标签:

<link href="//cdn.shopify.com/s/files/1/0559/0089/7434/t/4/assets/theme.css?v=10188701410004355449" rel="stylesheet" type="text/css" media="all">

除了更简洁之外,这两种方法的主要区别在于stylesheet_tag 不接受额外的参数。例如,如果我们想将rel属性更改为 preload,修改media属性,或者甚至包括defer属性,我们就必须使用第一种方法,并使用 HTML link 标签包含资产文件。

我们现在已经学会了如何将我们的主题文件与必要的样式表连接起来。然而,请注意,我们将根据我们想要访问的文件类型使用不同类型的 HTML 过滤器。

例如,如果我们想将theme.js文件的内容输出到我们的主题中,我们会使用类似的方法,主题文件名后跟asset_url以获取其路径,但我们将使用script_tag而不是stylesheet_tag

{{ "theme.js" | asset_url | script_tag }}

使用script_tag,我们将自动生成并包含我们的主题中的 HTML script 标签。然而,请注意,与stylesheet_tag类似,script_tag也不接受任何参数:

<script src="img/theme.js?v=2017768116492187958" type="text/javascript"></script>

除了 stylesheet_tagscript_tag 之外,我们还可以访问 img_tag。使用 asset_url 后跟图像文件名,然后是 img_tag,可以在资产内访问图像文件:

{{ "ajax-loader.gif" | asset_url | img_tag }}

img_tag 与前两个过滤器之间的关键区别在于 img_tag 接受额外的参数。

第四章“使用对象深入液态核心”,我们有机会通过使用它来返回产品图像 URL 字符串,并将其与 HTML img 标签结合,将产品图像输出到我们的店面,来看到 img_url 过滤器的实际应用:

<img src="img/{{ product | img_url: "300x300" }}"/>

注意,除了 img_url 过滤器外,我们还使用了 size 参数来设置图像的限制大小,这是我们可以在 img_urlimg_tag 过滤器中使用的三种参数之一。然而,请注意,这两种类型的过滤器使用不同的参数,我们将在以下迷你项目中简要解释。

构建产品展示库

在这个迷你项目中,我们将学习如何输出产品展示库所需的所有元素:

  1. 让我们从创建一个名为 Product Gallery 的新页面和一个名为 product-gallery 的新页面模板开始,我们将将其分配给之前创建的页面。

  2. 一旦我们创建了页面并分配了适当的模板,我们应该确定一个具有多个图像的产品,并恢复其处理程序。我们将选择园艺手套,这是我们之前在第三章“使用标签深入液态核心”中从 product-data.csv 文件导入的产品之一。我们可以通过预览产品页面并从页面 URL 中复制页面处理程序来检索产品处理程序。或者,我们可以在管理部分导航到产品页面,并从页面底部的页面 URL 链接中复制页面处理程序。

  3. 现在我们已经恢复了产品处理程序,让我们首先通过其处理程序创建一个产品对象并将其分配给一个变量。为了实现这一点,我们可以使用之前学到的通过其处理程序访问页面对象的方法。然而,与之前的章节相比,在那里我们学习了如何通过其集合访问产品对象,直接使用其处理程序访问产品对象略有不同。

    我们不会将产品对象名称复数化,而将使用一个全局对象标签 all_products,它为我们提供了访问我们商店中所有产品的权限。

    重要提示:

    虽然 all_products 是一种相当实用的方法,允许我们通过其处理程序直接访问任何产品,但它有一个限制,即我们只能在每页上运行它 20 次。这意味着如果我们需要在单页上恢复超过 20 个特定的产品,我们将需要通过遍历集合来恢复它们。

  4. 我们可以通过使用all_products全局对象,然后是我们要访问的产品的句柄来通过句柄访问产品对象。在我们的例子中,我们将使用方括号[]。然而,我们也可以使用点(.)注解:

    {% assign product_object = all_products["gardening-  gloves"] %}
    
  5. 通过这种方式,我们现在可以访问园艺手套产品对象。要访问附加到产品上的所有图片,我们将使用product_object变量作为我们的对象,然后是images属性,以恢复特定产品的图片数组。由于我们正在处理一个数组,我们必须使用for标签来遍历它们:

    {% assign product_object = all_products["gardening-  gloves"] %}
    {% for image_item in product_object.images %}
      <img src="img/{{ image_item | img_url }}"/>
    {% endfor %}
    

我们现在已经成功提取了一系列的产品图片,我们可以使用这些图片结合各种滑块插件来创建强大的相册。然而,请注意,我们店面上的图片尺寸相对较小。

这是因为如果没有引入size参数,Liquid 总是默认为100x100的大小。让我们通过将图像尺寸限制为宽 300 像素和高度 300 像素来引入size参数:

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  <img src="img/{{ image_item | img_url: "300x300" }}"/>
{% endfor %}

注意,尽管我们指定了高度为 300 像素,但我们的图片现在宽度为 300 像素,高度仅为 200 像素。这是因为size参数只能通过减小图像大小来限制图像大小以匹配指定的值。它不能改变图像的宽高比,也不能将图像的大小增加到原始图像大小之外。

在上一个示例中,我们使用了"300x300"来限制产品图片的宽度和高度。然而,我们也可以只限制一边,使用"300x"将图片宽度设置为 300 像素或使用"x300"将高度限制为 300 像素。如果我们只指定这两个值中的一个,Shopify 将自动计算图像的尺寸,同时保持图像的宽高比。

我们可以使用与img_url过滤器配合的第二个参数是crop参数,它允许我们在与size参数结合时将图片裁剪到指定的大小。crop参数有五个不同的值:

  • top

  • center

  • bottom

  • left

  • right

使用正确的选项,我们可以指定我们想要裁剪图像的哪一侧。在我们的例子中,我们可以使用center选项来确保图像从每侧裁剪得一样:

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  <img src="img/{{ image_item | img_url: "300x300", crop:     "center" }}"/>
{% endfor %}

通过使用crop参数更改图像大小,我们也改变了图像的宽高比,因为所有图像的宽度和高度现在都是正好 300 像素。

我们可以使用与img_url过滤器配合的最后两个参数是scale,它允许我们使用23作为其选项值来指定图片的像素密度,以及format,这是一个相当有趣的参数,允许我们指定显示图片的格式。format参数的两个可接受值是jpgpjpg

使用 pjpg,我们可以将图像格式转换为渐进式 JPEG,自动加载全尺寸图像并逐渐提高其质量,而不是像传统 JPEG 那样从顶部到底部加载图像(我们可以在以下链接中了解更多关于渐进式 JPEG 的信息:en.wikipedia.org/wiki/JPEG#JPEG_compression):

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  <img src="img/{{ image_item | img_url: "300x300", crop:     "center", format: "pjpg" }}"/>
{% endfor %}

现在我们已经熟悉了使用img_url过滤器可访问的参数,是时候了解img_tag过滤器可用的参数了。让我们从修改我们的最后一个示例开始,使用img_tag生成 HTML 标签:

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  {{ image_item | img_tag }}
{% endfor %}

注意,当我们第一次提到img_tag时,我们将其与asset_url结合使用来恢复Assets目录内图像位置的 URL 字符串。然而,由于我们不是访问Assets目录,而是访问产品图像,我们已经有从使用product_object.images获取的 URL 字符串数组,所以我们不需要使用除img_tag之外的任何其他过滤器。

通过查看结果,我们可以看到我们已经成功为图像数组中的每个图像创建了 HTML img标签。由于我们没有声明图像大小,Shopify 默认将我们的图像调整为100x100

img_tag只接受三个参数。与只能应用所需参数的img_url不同,对于img_tag,我们需要按照特定顺序应用所有参数。这意味着如果我们想使用classalt标签参数,我们首先必须使用其他参数。

由于我们需要按特定顺序添加所有参数,参数不需要表示。我们只需要分配它们的值。第一个值是 alt 文本,在其中我们可以使用固定字符串值或 Liquid 值,如image_item.alt,以恢复实际的图像 alt 文本。第二个参数我们可以用来为每个图像标签分配特定的类,而只有在第三个参数中我们可以分配大小值:

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  {{ image_item | img_tag: image_item.alt, "class1 class2",     "300x300" }}
{% endfor %}

正如我们所看到的,image_urlimg_tag过滤器都有它们有用的参数,虽然img_tag更简洁,但它有局限性,因为我们包括在生成的 HTML img标签中的属性数量有限。

假设我们想要从Assets目录中提取一个图像文件,并应用适当的size参数以将其用作背景图像。我们不能使用img_tag,如前所述,因为这会返回一个 HTML img标签。我们也不能单独使用asset_url,因为asset_url不接受任何附加参数,包括size参数。

类似于stylesheet_tagscript_tag,我们有权限使用特殊的asset_img_url过滤器,它允许我们包含size参数以从Assets目录中恢复图像:

{{ "ajax-loader.gif" | asset_img_url: "300x300", scale: 2,   crop: "center" }}

注意,asset_img_url允许我们包含size参数和其他之前通过img_url过滤器可用的参数,包括sizecropscaleformat

到目前为止,我们已经学习了如何访问Assets目录中的不同类型的文件并为每个文件生成适当的 HTML 标签。我们还通过一个小项目学习了如何使用img_url过滤器,该项目涉及输出创建基本产品库所需的所有元素。虽然我们没有涵盖 Liquid 提供的所有 URL 和 HTML 过滤器,但我们现在已经为使用 HTML 和 URL 过滤器奠定了适当的基础,这种知识在 Liquid 中至关重要,并将对我们未来的工作大有裨益。

小贴士:

关于所有可用的 HTML 过滤器,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/html-filters,关于可用的 URL 过滤器,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/url-filters

提升产品媒体库

在之前的练习中,我们学习了如何输出创建仅包含图像的基本产品库所需的图像元素。在接下来的项目中,我们将学习如何使用媒体对象和过滤器创建一个多功能库,该库将支持图像、3D 模型和 Shopify 上托管的内嵌视频。此外,我们还将嵌入一些最受欢迎的视频平台(Vimeo 和 YouTube)的外部视频链接,并为两者自动生成适当的视频播放器。

今天大多数最新的主题已经包含了产品媒体库。然而,许多商店仍在使用过时的主题文件,因此了解如何从头开始创建这个功能是至关重要的。

让我们从导航到我们在上一节构建产品库中创建的产品库页面开始,该页面位于使用 HTML 和 URL 过滤器部分之下,并修改之前包含的代码以接受除了图像之外的其他媒体类型:

{% assign product_object = all_products["gardening-gloves"] %}
{% for image_item in product_object.images %}
  {{ image_item | img_tag: image_item.alt, "class1 class2",     "300x300" }}
{% endfor %}

最初,我们使用product_object变量来捕获园艺手套产品的产品对象,之后我们使用for标签遍历从product_object.images接收到的图像数组。鉴于我们正在处理各种媒体类型,我们需要使用media属性来恢复媒体数组,并将image_item变量替换为media以保持一切的一致性:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {{ media | img_tag: media.alt, "class1 class2", "300x300" }}
{% endfor %}

使用media属性,我们现在已经恢复了一个包含所有不同媒体对象的数组,它可以包含以下媒体类型:

  • 图像

  • 外部视频

  • 视频

  • 模型

然而,拥有多种媒体类型也意味着我们现在有一个混合的对象数组,因此在我们做任何事情之前,我们需要过滤掉它们。

我们将使用case/when标签与media_type属性结合,这是媒体对象的一部分,它将允许我们创建一个switch语句来恢复特定类型的所有媒体。我们可以通过访问控制 Liquid 流程部分来提醒自己case/when标签,这部分我们可以在第三章深入 Liquid 核心与标签中找到。

让我们创建一个case标签来过滤media_type,并为每个媒体类型编写一个switch语句:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
    {% when "external_video" %}
    {% when "video" %}
    {% when "model" %}
    {% else %}
  {% endcase %}
  {{ media | img_tag: media.alt, "class1 class2", "300x300" }}
{% endfor %}

case/when标签就位后,我们已经成功过滤出媒体类型,并获得了访问每个媒体类型对象的权利,我们将需要输出媒体标签。

如果我们查看我们的代码,我们会注意到我们之前用于在先前的示例中输出图像的代码仍然存在。由于我们现在可以在第一个switch语句中访问image对象,我们可以简单地重新定位代码,这将是在输出图像媒体文件的第一步:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
    {% when "video" %}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

img_tag就位后,我们现在已经成功输出了我们产品的所有图像文件。然而,我们仍然缺少其他媒体类型的标签。所以,让我们继续处理外部视频。

external_video对象为我们提供了有关与特定产品相关联的 Vimeo 或 YouTube 视频的信息。同样,与图像对象一样,为了输出external_video媒体类型,我们需要使用external_video_tag来生成必要的iframe元素,无论是 Vimeo 还是 YouTube:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300"}}
    {% when "external_video" %}
      {{ media | external_video_tag }}
    {% when "video" %}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

external_video_tag就位后,我们将自动为每个external_video媒体类型生成一个包含所有必要属性的 iframe,所以让我们快速测试一下:

  1. 为了测试external_video_tag是否工作,我们首先需要在我们的产品页面媒体中包含一个 Vimeo 或 YouTube 视频。

  2. 我们需要点击我们的管理侧边栏中的产品部分,并导航到我们目前正在查看媒体文件的产品。在我们的例子中,这个产品是园艺手套。

  3. 在打开特定的产品页面后,我们需要滚动到媒体部分,在右上角,我们会找到一个名为从 URL 添加媒体的下拉链接,我们应该点击以显示下拉选项。在下拉菜单中,我们会找到两个选项,第一个选项允许我们向产品添加外部图像,第二个选项允许我们嵌入 Vimeo 或 YouTube 视频。![Figure 5.1 – 将外部资产嵌入到产品媒体中的示例

    ![Figure 5.01_B17606.jpg]

    图 5.1 – 将外部资产嵌入到产品媒体中的示例

  4. 我们应该通过点击嵌入视频来继续,这将触发一个弹出窗口,我们可以在这里包含 Vimeo 或 YouTube 视频的 URL。粘贴链接后,点击嵌入视频按钮来完成此过程。经过几秒钟的处理,媒体视频将在媒体部分可见。

图 5.2 – 产品管理页面上的各种媒体文件示例

图 5.2 – 产品管理页面上的各种媒体文件示例

视频现在已成功加载到产品媒体资源中,剩下要测试的是 external_video_tag 是否工作良好。

点击媒体视频资源,将触发一个弹出窗口,其中一侧显示预览,另一侧提供包含 alt 文本的选项。此外,在右上角,我们可以看到三个图标。第一个是一个垃圾桶图标,允许我们删除特定的媒体。中间的图标,由三个点表示,当点击时将显示一个包含 替换缩略图 选项的下拉菜单。

点击此选项将允许我们上传一个作为 iframe 视频海报图片的缩略图,避免视频的第一帧作为海报。请注意,用三个点表示的图标仅在视频资源上可见,在常规图像资源上不可见。

图 5.3 – 视频媒体资源弹出工具示例

图 5.3 – 视频媒体资源弹出工具示例

预览 iframe 元素:

<iframe frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen" src="img/neFK-pv2sKY?controls=1&amp;enablejsapi=1&amp;modestbranding=1&amp;origin=https%3A%2F%2Flearn-liquid-with-packt.myshopify.com&amp;playsinline=1&amp;rel=0" title="Gardening Gloves"></iframe>

如我们所见,默认情况下,我们的 YouTube 视频嵌入中包含了许多属性,例如可见控件和品牌。但如果我们想修改这些属性或包含一些新的属性呢?

要修改现有的外部视频属性或包含新的属性,我们需要引入一个新的媒体过滤器,external_video_url。通过包含 external_video_url 过滤器,结合 external_video_tag,我们将能够修改 YouTube 嵌入的任何属性:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {{ media | external_video_url: controls: 0, color:             "white" | external_video_tag }}
    {% when "video" %}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

如我们所见,我们可以从官方 YouTube 文档中包含任意数量的属性。虽然这些属性对 YouTube 视频嵌入很有帮助,但当我们处理 Vimeo 视频时,这些属性将不会对我们有所帮助,因为 Vimeo 根据其自己的文档使用不同的属性。

为了区分哪个嵌入链接属于 YouTube,哪个属于 Vimeo,我们需要通过 external_video 对象引入 host 属性,该对象返回两个值之一:

  • youtube

  • vimeo

通过结合使用 host 属性和 if 语句,我们可以轻松地区分两个 iframe 元素,并将适当的属性应用于它们:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {% if media.host == "youtube" %}
        {{ media | external_video_url: controls: 0, color:                 "white" | external_video_tag }}
      {% else %}
        {{ media | external_video_url: loop: 1, muted: 1 |                 external_video_tag }}
      {% endif %}
    {% when "video" %}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

通过引入 host 属性,我们已经确保每种类型的外部视频嵌入都将根据其文档接收适当的属性。

小贴士:

关于 YouTube 视频的可用属性,请参阅 developers.google.com/youtube/player_parameters#Parameters

关于 Vimeo 视频的可用属性,请参阅 vimeo.zendesk.com/hc/en-us/articles/360001494447-Using-Player-Parameters

我们现在已经学会了如何包含两种类型的外部视频,并为每种类型包含任何属性。现在我们可以继续到下一个 switch 语句,学习如何输出托管在 Shopify 平台本身上的视频。

将视频作为产品媒体资产上传与上传图像相同。要上传视频,请点击管理侧边栏中的 产品 部分,并选择我们上传视频资产的产品。进入后,滚动到 媒体 部分,然后点击 添加媒体 按钮开始上传过程。

重要提示:

除了外部视频外,上传到 Shopify 平台的资产(包括图像和视频)都有特定的限制。除了我们可以使用的特定格式类型外,图像文件的大小不能超过 20 MP,20 MB,并且必须是 .jpeg.png 格式,而视频文件的最大长度为 60 秒,大小为 20 MB,并且必须是 .mp4.mov 视频格式。

一旦我们上传了视频,我们还可以通过包含替代文本和海报图像以与外部媒体相同的方式添加可选信息。

要输出上传到产品媒体文件的视频,我们需要使用 video_tag,通过 video 对象访问:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {% if media.host == "youtube" %}
        {{ media | external_video_url: controls: 0, color:                 "white" | external_video_tag }}
      {% else %}
        {{ media | external_video_url: loop: 1, muted: 1 |                 external_video_tag }}
      {% endif %}
    {% when "video" %}
      {{ media | video_tag }}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

通过预览 video 标签。然而,由于我们没有启用控件,它们默认被隐藏。此外,如果我们仔细观察,我们会注意到 HTML video 标签中 poster 属性的 URL 有一个小尺寸,这就是为什么店面上的图像也很小。

就像我们回忆起学习图像过滤器时一样,如果我们尝试输出未定义图像大小的图像,Shopify 会自动将图像大小调整为 100x100

使用 video_tag,我们可以包含任何可以与常规 HTML video 标签一起使用的属性,包括 image_size 参数,允许我们修改视频 poster 属性的大小。让我们修改 video_tag 以使控件可见,设置海报图像大小,并将视频大小设置为 300x 以匹配海报大小:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {% if media.host == "youtube" %}
        {{ media | external_video_url: controls: 0, color:                 "white" | external_video_tag:                         class: "youtube_video" }}
      {% else %}
        {{ media | external_video_url: loop: 1, muted: 1 |                 external_video_tag: class: "vimeo_video" }}
      {% endif %}
    {% when "video" %}
      {{ media | video_tag: controls: true, image_size:             "300x300", width: "300x" }}
    {% when "model" %}
    {% else %}
  {% endcase %}
{% endfor %}

包含了额外的属性后,我们现在已经成功生成了 HTML video 标签,同时保留了根据需要修改任何属性的能力。

我们需要关注的下一个媒体类型是 3D 模型,我们可以使用 model_viewer_tag 输出它,通过 model 对象访问。只需将 model_viewer_tagmedia 对象一起包含,就会自动生成适当的模型查看器:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {% if media.host == "youtube" %}
        {{ media | external_video_url: controls: 0, color:                 "white" | external_video_tag:                         class: "youtube_video" }}
      {% else %}
        {{ media | external_video_url: loop: 1, muted: 1 |                 external_video_tag: class: "vimeo_video" }}
      {% endif %}
    {% when "video" %}
      {{ media | video_tag: controls: true, image_size:             "300x300", width: "300x" }}
    {% when "model" %}
      {{ media | model_viewer_tag }}
    {% else %}
  {% endcase %}
{% endfor %}

注意,模型查看器将默认自动包含特定属性,就像之前的 media 标签一样。尽管如此,如果我们选择这样做,我们可以很容易地更新或包括新的属性,只需遵循之前 media 标签中提到的相同格式即可。

model_viewer_tag到位的情况下,我们已经涵盖了所有四种媒体类型,并确保我们将每种媒体类型用适当的media标签表示。然而,请注意,我们仍然有一个没有media标签的最后的switch语句。

我们可以将最后的switch语句视为一种安全措施,以防万一之前的任何switch语句或媒体无法产生结果,我们将与media_tag一起使用它。

media_tag过滤器是一种特殊的过滤器,因为这个特定的过滤器将自动为之前提到的四种媒体类型中的任何一种生成适当的media标签。所以,如果我们可以用 media_tag 自动生成所有 media 标签, 为什么我们还要用 media_tag 来生成所有媒体文件呢?为什么我们到现在还要费心学习所有 media 标签呢?

虽然media_tag确实会自动检测每种媒体类型并为其渲染适当的media标签,但通过使用media_tag,我们将失去为特定媒体类型分配自定义类名和属性的能力。因此,我们永远不应该将media_tag作为渲染媒体资产的主要选项。相反,我们应该将其用作后备方案,以确保在店面正确渲染媒体资产:

{% assign product_object = all_products["gardening-gloves"] %}
{% for media in product_object.media %}
  {% case media.media_type %}
    {% when "image" %}
      {{ media | img_tag: media.alt, "class1 class2",             "300x300" }}
    {% when "external_video" %}
      {% if media.host == "youtube" %}
        {{ media | external_video_url: controls: 0, color:                 "white" | external_video_tag:                         class: "youtube_video" }}
      {% else %}
        {{ media | external_video_url: loop: 1, muted: 1 |                 external_video_tag: class: "vimeo_video" }}
      {% endif %}
    {% when "video" %}
      {{ media | video_tag: controls: true, image_size:             "300x300", width: "300x" }}
    {% when "model" %}
      {{ media | model_viewer_tag }}
    {% else %}
    {{ media | media_tag }}
  {% endcase %}
{% endfor %}

到目前为止,我们已经学习了如何输出图像类型的媒体文件,区分不同类型的外部媒体资产,渲染 Shopify 平台上的视频的媒体标签,并生成 3D 模型的适当媒体标签。最后,通过media_tag,我们涵盖了 Shopify 目前覆盖的所有媒体类型,并确保我们将在店面正确展示每个媒体资产。

现在我们已经拥有了创建媒体库所需的所有必要资源,接下来要做的就是通过添加一些 HTML 元素来正确格式化代码,从而进一步优化我们的代码。除了代码格式,我们还可以根据我们想要创建的画廊类型,使用一些滑动插件,例如Slick。为了保持内容简洁并直击要点,我们不会涵盖媒体库的样式和功能。然而,对于那些想要通过完成项目来测试自己技能的人来说,我们可以在以下 Shopify 文章中找到关于样式和功能的必要建议(shopify.dev/tutorials/add-theme-support-for-rich-media-3d-and-video)。

在文章中,我们可以找到有关使用宽高比框创建响应性或回答一些关于功能性的常见问题解答的信息,例如将缩略图图像连接到主画廊或变体本身。

小贴士:

对于所有可用媒体过滤器的更多信息,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/media-filters

在进行这个项目的过程中,我们创建了一个产品媒体库的基本版本,这使得我们能够将任何类型的产品媒体输出到任何页面。虽然它可能看起来并不令人印象深刻,但通过这个项目我们学到的知识教会了我们如何创建今天最受欢迎的功能之一,并且随着我们知识的进一步增长,它将对我们创建更高级的功能大有裨益。

构建产品折叠面板

在以下项目中,我们将通过处理和创建产品折叠面板功能来学习字符串和数组过滤器。但是,在我们继续项目之前,字符串和数组过滤器究竟做什么呢?

字符串过滤器是允许我们操纵 Liquid 代码的输出或变量本身的方法,只要变量是字符串类型,而数组过滤器允许我们操纵数组的输出。

对于这个项目,我们首先需要找到一个具有冗长描述的产品。为了节省时间,我们已经在product-data.csv文件中包含了必要的描述,在第三章 深入 Liquid 核心标签中。

图 5.4 – 长产品描述的示例

图 5.4 – 长产品描述的示例

如我们从之前的屏幕截图中所见,拥有冗长的产品描述可能相当低效,因为它占据了大量的空间。虽然我们可以轻松地格式化产品描述代码,包括创建产品折叠面板所需的必要 HTML 标签,但手动调整每个产品的代码将是一个漫长的过程。维护它甚至更糟糕。幸运的是,通过使用字符串和数组过滤器,我们可以轻松地操纵产品描述输出,将其拆分并按任何我们需要的方式格式化:

  1. 首先,让我们从识别渲染当前产品描述的代码片段开始。我们可以在product-template.liquid文件中找到产品描述,该文件位于Sections目录下,我们目前正在使用product.description来渲染它:

    <div class="product-single__description rte">
      {{ product.description }}
    </div>
    
  2. 由于我们希望创建可重用的代码,我们应该做的第一件事是创建一个变量来保存product.description的输出,这样我们就可以避免多次调用product.description

    <div class="product-single__description rte">
      {% assign productDescription = product.description%}
    </div>
    

现在我们已经设置了变量,我们应该概述我们试图实现的具体目标。再次查看当前的产品描述,我们可以清楚地看到我们有三个坚实的块,所以让我们说我们希望将整个产品描述分成三个甚至更多的独特的产品折叠面板。

现在我们知道了什么,我们需要考虑如何。虽然我们可以轻松地硬编码产品折叠面板的标题,例如描述成分说明,但我们希望创建一个动态功能,允许我们轻松地包括任意数量的产品折叠面板,而无需修改代码本身。我们将使用以下步骤来实现这一点:

  • 首先,将DescriptionIngredientsInstructions作为 h6 标题导航到每个文本块之前。为此,我们可以使用标题来包含任意数量的产品标签标题,并为以后设置适当的标记。

  • 然后,我们可以通过简单地突出显示我们想要格式化的文本并点击A按钮来使用富文本编辑器应用标题,这将触发一个下拉菜单,我们可以从中选择所需的 h6 标题:

图 5.5 – 在产品描述中应用标题的示例

图 5.5 – 在产品描述中应用标题的示例

在设置好标题后,我们现在有了适当的标记,我们可以使用它来将文本块分隔成单独的块,这可以通过以下字符串过滤器split来实现。

分割过滤器

split过滤器使用一个单独的子字符串作为参数,它作为分隔符,将字符串分割成一个数组,我们可以稍后使用数组过滤器输出这些数组项。但是它究竟是如何工作的呢?

{% assign methods = "Strings and Filters" %}

在这个例子中,我们创建了一个名为methods的变量,并给它分配了一个字符串消息。现在让我们使用split过滤器将字符串消息分割成数组,并立即调用它以查看结果:

{% assign methods = "Strings and Filters" | split: " and " %}
{{ methods }}
StringsFilters

如我们所见,在应用split过滤器后,我们用作分隔符的子字符串被完全从初始字符串中移除,我们最终得到了一个数组结果。因此,分配给split过滤器子字符串参数的任何值不仅将作为标记分隔符,而且还将自动从字符串中删除任何子字符串值的出现。

在使用split过滤器后,我们现在已经将methods变量修改为数组。然而,这并不明显,因为我们还在分隔符内包含了空格,因此结果是我们两个单词紧挨着写在一起而没有空格。为了测试我们的methods变量是否为数组,我们需要使用for标签运行它。但是如果我们只想避免使用循环,因为数组中只有两个项目呢?

这就是first数组过滤器发挥作用的地方。正如我们回忆的那样,我们在上一章中提到过firstlast过滤器,当时我们在进行Product Customization项目时。使用firstlast过滤器将自动恢复数组中的第一个或最后一个元素。由于使用split过滤器后我们的数组只有两个元素,所以这是一个完美的匹配。否则,我们需要包含一个for标签来遍历数组以恢复正确的值。让我们通过只恢复分隔符之前的数据来观察它的实际效果:

{% assign methods = "Strings and Filters" | split: " and "   | first %}
{{ methods }}
Strings

我们现在可以看到,我们已经成功地将初始的字符串类型变量修改为数组类型,并且已经成功恢复了数组的第一项。虽然这个具体的方法看起来可能并不重要,但我们所获得的知识将在我们未来的工作中非常有用。

在我们继续我们的项目之前,让我们再看一个例子:

{% assign message  = "This is a short string message." |   split: " " %}

在这个例子中,我们在message变量中分配了一个简短的字符串。然后我们应用了split过滤器并将子字符串设置为空格值,这意味着我们正在寻找在每个空格出现时分割初始字符串。让我们创建一个简短的循环来确认message变量现在是一个数组类型:

{% assign message  = "This is a short string message." |   split: " " %}
{% for item in message %}
  {{ item }}
{% endfor %}
This
is
a
short
string
message.

如我们所见,通过引入split过滤器,我们已成功使用子字符串参数将初始字符串消息分割,并创建了一个数组,我们使用for标签进行了确认。现在让我们回到我们的项目,并使用我们新获得的知识将产品描述分成三个独立的块。

由于我们正在寻找创建一个动态功能,我们需要将子字符串参数设置为一个我们知道将在每个产品描述中出现的值。记住,我们之前已经添加了标题,使用了h6标题进行格式化。因此,让我们使用split过滤器并将子字符串值设置为开头的h6标题:

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
</div>

为了理解使用h6标题作为子字符串参数的意义,我们首先需要查看我们产品描述的当前 HTML 格式。如果我们在使用split过滤器之前检查产品描述,我们会注意到以下 HTML 格式:

<h6>Description</h6>
<p>Lorem ipsum content<p>
<h6>Ingredients</h6>
<p>Lorem ipsum content<p>
<h6>Instructions</h6>
<p>Lorem ipsum content<p>

如标记所示,每个标题都位于文本块之上,提供了完美的标记。使用开头的h6标题作为分隔符,我们应该在数组中有四个项目。现在让我们看看当我们使用开头的h6标题作为分隔符应用split过滤器时会发生什么:

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
{% for item in productDescription %}
  <div class="product-single__description-item">
    {{ item }}
  </div>
{% endfor %}
</div>

根据我们的标记,在应用以开头的h6标题作为分隔符的split过滤器后,数组中的第一个项目应该是空的,因为没有内容在分隔符第一次出现之前。其他三个应该包含标题以及第一个和后续分隔符出现之间的任何内容:

<div class="product-single__description-item"></div>
<div class="product-single__description-item">
<span>Description</span>
<p>Lorem ipsum content<p>
</div>
<div class="product-single__description-item">
<span>Ingredients</span>
<p>Lorem ipsum content<p>
</div>
<div class="product-single__description-item">
<span>Instructions</span>
<p>Lorem ipsum content<p>
</div>

检查结果,我们可以看到我们成功地提取了每个产品描述块。然而,还有一些事情需要讨论。例如,我们将有一个空的div,我们可以通过引入offset参数并将其值设置为1来快速解决,这样我们就可以在for循环中跳过第一次迭代。我们可以通过访问位于第三章迭代标签部分来提醒自己offset参数,深入 Liquid 核心使用标签

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
{% for item in productDescription offset: 1 %}
  <div class="product-single__description-item">
    {{ item }}
  </div>
{% endfor %}
</div>

offset参数添加到for标签中将会跳过第一次迭代,否则将返回一个空值。此外,我们已经确保我们的数组只包含我们最初打算的三个产品描述块。然而,让我们更仔细地看看在应用了offset参数后的数组当前结果:

<div class="product-single__description-item">
<span>Description</span>
<p>Lorem ipsum content<p>
</div>
<div class="product-single__description-item">
<span>Ingredients</span>
<p>Lorem ipsum content<p>
</div>
<div class="product-single__description-item">
<span>Instructions</span>
<p>Lorem ipsum content<p>
</div>

如我们所回忆的,通过使用打开的 h6 标题作为 split 过滤器的子字符串,我们将自动移除初始字符串中的任何打开的 h6 标题。然而,请注意,我们不仅移除了打开的 h6 标签,我们还移除了关闭的 h6 标签。

从技术上讲,关闭的 h6 标签仍然存在,位于关闭的 span 标签之后。然而,由于我们移除了打开的 h6 标签,浏览器将其解释为错误并自动移除了关闭的 h6 标签。因此,我们不要依赖浏览器来清理,而是使用关闭的 h6 标签进一步划分我们的三个块。

目前,for 循环内部的每个 item 变量都包含标题和内容文本。通过使用关闭的 h6 标题应用 split 过滤器,我们将 item 变量修改为一个包含标题和内容的数组。因此,我们不必使用另一个 for 循环来遍历这些内容,让我们回忆一下之前提到的 firstlast 过滤器,并在这里使用它们来分别恢复每个值:

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
{% for item in productDescription offset: 1 %}
  <div class="product-single__description--item">
    <div class="product-single__description-title">
      {{ item | split: "</h6>" | first }}
    </div>
    <div class="product-single__description-content">
      {{ item | split: "</h6>" | last }}
    </div>
  </div>
{% endfor %}
</div>

通过再次应用 split 过滤器,我们移除了剩余的关闭 h6 标签。此外,我们更清晰地分离了内容,这使得我们可以更轻松地使用结果来完成我们的迷你项目。

到目前为止,我们已经拥有了完成产品折叠项目所需的所有必要元素。唯一剩下的事情就是添加一些样式并引入处理输入动画的脚本。为了保持简洁,我们不会在这个项目中涵盖样式和功能。然而,我们将提供最终期望,以帮助我们可视化一个清晰的目标,这将作为未来工作的优秀实践:

![图 5.6 – 完整产品折叠项目的示例

![图片 5.06 – Figure_5.06_B17606.jpg]

图 5.6 – 完整产品折叠项目的示例

到目前为止,我们已经学会了如何通过将字符串变量转换为数组类型变量并使用它们来创建产品折叠功能来操作字符串变量。此外,我们还了解了使用管道字符包含的 splitfirst/last 过滤器。然而,根据情况,特定的过滤器,如 size,可以与管道和点注释一起使用:

{% assign message  = "This is a short string message." %}
{{ message | size }}
{% if message.size >= 10 %}
  This message contains more than 10 characters.
{% endif %}

在上一个示例中,我们创建了一个 message 变量并将其分配了一个简短的字符串。使用 size 过滤器和 message 变量,我们将渲染消息字符串中的字符总数。然而,通过使用点注释的 size,我们获得了将 size 过滤器作为 Liquid 逻辑一部分的能力:

31
This message contains more than 10 characters.

我们还可以使用 size 过滤器通过包含一个检查产品描述是否超过 1 个代码块的 if 语句来改进我们的产品折叠。如果没有,它应该简单地输出常规的产品描述:

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
{% if productDescription.size > 1 %}
  {% for item in productDescription offset: 1 %}
    <div class="product-single__description--item">
      <div class="product-single__description-title">
        {{ item | split: "</h6>" | first }}
      </div>
      <div class="product-single__description-content">
        {{ item | split: "</h6>" | last }}
      </div>
    </div>
  {% endfor %}
{% else %}
  <div class="rte">{{ product.description }}</div>
{% endif %}
</div>

随着size过滤器的引入,我们使代码变得更加简洁和优化,因为我们不需要在数组类型变量中的单个项目上运行for循环。

如我们所见,仅通过使用日常过滤器,我们就可以显著提高我们的代码质量。例如,如果我们想将产品描述拆分并渲染到各个地方,而不是一次性输出所有内容,那会怎么样?为此,我们需要使用splitfirst/lastindex过滤器的组合。我们已经涵盖了splitfirst/last过滤器,index过滤器究竟做什么呢?

索引过滤器

正如其名称所暗示的,index过滤器允许我们通过其索引位置访问特定的数组,起始位置为 0。让我们尝试将索引位置应用于我们的产品折叠项目。请注意,我们将不再需要for标签,因为我们将通过index过滤器直接访问productDescription变量:

<div class="product-single__description rte">
  {% assign productDescription = product.description |     split: "<h6>" %}
  <div class="product-single__description--item">
    <div class="product-single__description-title">
      {{ item[1] | split: "</h6>" | first }}
    </div>
    <div class="product-single__description-content">
      {{ item[1] | split: "</h6>" | last }}
    </div>
  </div>
</div>

之前的代码将返回产品描述中的第一个标题和文本块。然而,我们提到索引位置从0开始,那么为什么位置1返回的是第一个块的输出结果?

正如我们所回忆的,因为我们已经使用h6标签作为split过滤器的分隔符,所以我们的数组中的第一个项目是空的。之前我们通过包含offset参数来跳过数组中的第一个项目,而如今我们将简单地跳过第一个索引位置,即0。这种方法的不利之处在于,我们将不得不重复代码来分别恢复每个块。然而,从积极的一面来看,我们获得了在任意位置定位它们的灵活性,这在某些情况下可能是必要的。

小贴士:

如需了解所有可用的字符串过滤器信息,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/string-filters

如需了解所有可用的数组过滤器信息,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/array-filters

我们看到了一些看似微不足道的过滤器如何成为一个强大的工具,它将使我们能够创建商家经常寻求的复杂特征。通过这种学习过程,我们在实际项目上工作,获得了宝贵的经验教训,同时也学会了如何处理各种类型的过滤器,这比简单地列出它们要重要得多。

数学与货币过滤器

在上一章中,我们在Custom collections项目上工作时有机会看到货币过滤器在实际中的应用。货币过滤器是一种简单的过滤器类型,其唯一任务是根据货币格式化选项格式化数值,但这究竟意味着什么?

为了更好地理解,让我们导航到我们的管理页面,并点击左下角的设置按钮。随后,点击常规选项以打开我们将能够更新商店的基本信息。一旦进入,向下滚动直到你到达名为商店货币的部分。这就是我们可以更改商店默认货币的地方,我们的客户将使用它来购买商品。而不是更改商店货币,让我们点击更改格式化按钮。

![Figure 5.7 – 商店货币格式化的位置

![img/Figure_5.07_B17606.jpg]

图 5.7 – 商店货币格式化的位置

通过点击更改格式化按钮,我们将揭示额外的货币格式化选项,在这里我们可以定义全局标记,该标记将供货币过滤器稍后用于格式化任何数值。

如果我们将货币过滤器应用于任何数值,根据格式化设置,我们唯一会看到的变化是在价格前有一个美元符号,在价格后有一个 USD。所以,让我们尝试修改这两个字段以包含一些更有帮助的标记。

我们可以编辑名为span标签的money类的第一个两个字段。然而,请注意,在编写money类时,我们不应包括引号。否则,我们可能会破坏货币格式化。

![Figure 5.8 – 更新商店货币格式的示例

![img/Figure_5.08_B17606.jpg]

图 5.8 – 更新商店货币格式的示例

通过在货币格式化中包含带有money类的span标签,我们现在确保了我们的商店中的每个定价元素都将包含相同的选择器,这有助于我们在需要动态更新定价时。

另一方面,数学过滤器是自我解释的,因为它们允许我们执行特定的数学任务。同样,与字符串和数组过滤器一样,我们可以在一行中链式调用多个数学过滤器,在这种情况下,过滤器将按从左到右的顺序应用。

现在我们已经熟悉了货币和数学过滤器,让我们看看它们在实际中的应用,并开始我们的下一个迷你项目。

产品折扣价格

在以下项目中,我们将通过工作于我们在第四章中开始的项目之一,使用对象深入 Liquid 核心,来学习数学和货币过滤器,该项目是位于全局对象部分的自定义收藏夹

我们的目标是创建产品折扣价格并更新销售徽章以显示实际百分比折扣。让我们首先导航到我们之前开发的代码位置。

我们通过在Templates目录下的collection.liquid文件中包含collection-form.liquid片段来创建Custom collection功能。在collection-form.liquid片段中,我们将看到以下代码:

{% if product.compare_at_price != blank %}
<div class="custom-collection--item">
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url: "300x300" }}"/>
    <p class="h4 custom-collection--title">{{ product.title         }}</p>
    <p class="custom-collection--price">
      {{ product.price | money }}
      <span>{{ product.compare_at_price | money }}</span>
    </p>
    <span class="custom-collection--sale-badge">sale</span>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{         product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
</div>
{% endif %}

根据当前设置,我们同时显示正常价格和比较价格,所以让我们先修改比较价格,并用实际折扣价格替换它。要输出两个价格之间的折扣,应该相对简单,因为我们只需要从比较价格中减去正常价格。

要从比较价格中减去正常价格,我们需要使用minus数学过滤器:

<p class="custom-collection--price">
  {{ product.price | money }}
  <span>{{ product.compare_at_price | minus: product.price     }}</span>
</p>

注意,随着minus过滤器的引入,我们必须完全删除money过滤器。如前所述,我们只能在所有值都是数值值的情况下使用数学过滤器。如果我们包含money过滤器,由于我们之前设置的货币格式化,这两个值都会变成字符串。

现在我们已经成功应用了minus过滤器,我们需要考虑如何包含money过滤器。根据当前设置,我们收到了一个没有货币格式的数值。然而,如前所述,如果我们对任何两个值应用money过滤器,即使是在product.price之后,money过滤器只会影响product.price,而不会影响最终结果。因此,数学过滤器将不再工作。

为了解决这个问题,我们需要使用assigncapture关键字引入一个变量来计算两个数字之间的差异,然后使用money过滤器调用该变量:

<p class="custom-collection--price">
  {{ product.price | money }}
  {% assign discount-price = product.compare_at_price |     minus: product.price %}
  <span>{{ discount-price | money }}</span>
</p>

如果我们现在预览我们的收藏页面,我们将看到我们已经正确执行了折扣价格计算和货币格式化。所以现在我们可以转到我们项目的第二部分,即用实际百分比值折扣替换销售徽章。

我们可以在同一个collection-form.liquid代码片段文件中找到销售徽章的 HTML 代码:

{% if product.compare_at_price != blank %}
<div class="custom-collection--item">
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url: "300x300" }}"/>
    <p class="custom-collection--price">
      {{ product.price | money }}
      {% assign discount-price = product.compare_at_price |             minus: product.price %}
      <span>Save {{ discount-price | money }}</span>
    </p>
    <span class="custom-collection--sale-badge">sale</span>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{         product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
</div>
{% endif %}

要计算折扣百分比,我们可以使用以下公式,该公式将返回折扣百分比:

{{ product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price }}

在上一个例子中,我们必须在变量内包含计算,以便稍后可以应用money过滤器。然而,由于这次我们不需要货币,我们可以简单地包含计算,并在末尾添加百分比字符串。现在让我们把它全部放在一起:

{% if product.compare_at_price != blank %}
<div class="custom-collection--item">
  <a href="{{ product.url }}">
    <img src="img/{{ product | img_url: "300x300" }}"/>
    <p class="h4 custom-collection--title">{{ product.title         }}</p>
    <p class="custom-collection--price">
      {{ product.price | money }}
      {% assign discount-price = product.compare_at_price |             minus: product.price %}
      <span>Save {{ discount-price | money }}</span>
    </p>
    <span class="custom-collection--sale-badge">{{         product.compare_at_price | minus: product.price |                 times: 100 | divided_by: product.compare_                        at_price }}%</span>
  </a>
  {% form "product", product %}
    <input type="hidden" name="id" value="{{         product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
  {% endform %}
</div>
{% endif %}

正如我们所看到的,与字符串过滤器类似,我们可以轻松地链式调用多个数学过滤器以执行所需的计算。然而,值得注意的是,除了仅与数值值一起工作外,数学过滤器不接受任何类型的括号,我们通常使用括号来执行具有特定优先级的数学计算。

如果我们需要执行计算,我们有两个选择。第一个是我们让 Liquid 从左到右执行数学计算。另一方面,如果我们需要按照特定顺序执行计算,我们需要将公式拆分为多个变量,并在稍后组合结果。

小贴士:

关于所有可用数学过滤器的更多信息,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/math-filters

关于所有可用货币过滤器的更多信息,我们可以参考shopify.dev/docs/themes/liquid/reference/filters/money-filters

虽然我们没有涵盖所有的数学和货币过滤器类型,但我们通过这个项目对数学和货币过滤器的工作原理有了坚实的理解。这种知识将作为我们在 Shopify 专家工作中将要开发的特性的垫脚石。

探索附加过滤器

附加过滤器是一组不属于任何其他过滤器组的过滤器。然而,这并不意味着它们不重要。虽然我们可以命名许多类型的过滤器,但我们将只提到其中三个最重要的,因为我们将经常使用它们。

默认过滤器

如其名所示,nilfalse或一个空字符串。如果变量包含空白字符,我们将无法返回default值:

Hello {{ customer.name | default: "customer" }}

通过在前面示例中引入default值,我们确保了即使客户没有提供他们的名字,我们也不会得到一个损坏的字符串。此外,我们还使我们的代码看起来更加整洁。如果没有default过滤器,我们就必须使用if语句来检查customer.name是否存在,并根据结果输出值。

t(翻译)过滤器

Locales目录。如果我们导航到Locales目录,我们会注意到大量的文件。然而,其中之一将包含默认字符串,这是我们商店中当前活动的语言。

让我们回顾一下我们之前的一个项目,自定义收藏夹,我们在第四章,“使用对象深入液态核心”部分进行了工作,在使用全局对象部分,我们开发了一个提交按钮,其值我们硬编码为文本添加到购物车。这对于现在来说效果不错,但如果我们更改了我们的商店语言呢?我们就必须手动更新整个主题中添加到购物车字符串的任何出现。

使用t过滤器,我们可以通过更新单个值来更新整个主题中的任何字符串值。我们首先需要做的是,根据 Shopify 的命名和分组指南,在当前活动的语言文件中定义三级 JSON。由于我们正在修改产品的提交按钮,我们可以将第一级设置为产品,第二级设置为更具体的内容,即表单本身,最后,第三级指向我们要翻译的字符串:

{
  "product": {
    "form": {
      "submit": "Add to Cart"
    }
  }
}

一旦我们在语言文件中成功创建了 JSON,剩下的就是使用 t 过滤器来读取翻译键值并将其渲染在店面上了:

{{ product.form.submit | t }}

通过在所有产品表单上实现 t 过滤器,我们将获得自动翻译所有字符串的能力,而无需手动在文件中搜索它们。此外,在其它语言文件中使用相同的 JSON 命名和分组将允许我们通过简单地更改商店语言来快速翻译整个主题。

t 过滤器是一个强大的工具,它允许我们通过逗号分隔来传递多个参数并进行插值:

{{ header.general.customer | t: customer: customer.name }}

在上一个示例中,我们试图访问语言文件中的客户字符串。然而,我们还将 customer.name 值作为参数传递,然后在语言 JSON 文件中进行了插值:

{
  "header": {
    "general": {
      "customer": "Welcome {{ customer }}!"
    }
  }
}

除了提供变量插值的能力外,我们还可以转义翻译内容,将 HTML 包含在翻译键中,并对翻译键进行复数化,使其成为我们可用的相当强大的工具。

小贴士:

关于 t 过滤器命名和分组指南的更多信息,我们可以参考 shopify.dev/tutorials/develop-theme-localization-organize-translation-keys

关于 t 过滤器所有可用参数的更多信息,我们可以参考 shopify.dev/tutorials/develop-theme-localization-use-translation-keys

一旦我们创建了必要的翻译键,我们也可以通过导航到我们的管理页面,点击侧边栏中展开的 在线商店 选项内的 主题 来更新翻译。点击主题文件上的 操作 按钮将触发一个下拉菜单,点击 编辑语言 将快速更新语言 JSON 文件中的任何翻译。

JSON 过滤器

如其名称所示,JSON 过滤器允许我们将字符串转换为 JSON,更重要的是,它将使 Liquid 代码对 JavaScript 可读:

var product_JSON = {{ product | json }};
var cart_JSON = {{ cart | json }};

注意,当在 Liquid 输出中使用 JSON 过滤器时,无需包含引号,因为 JSON 过滤器会自动包含它们。然而,请注意,特定的值,例如 inventory_quantityinventory_policy 字段,不是我们可以通过 JSON 返回的内容,因为 Shopify 由于安全原因已弃用这些字段。

小贴士:

关于所有可用附加过滤器的更多信息,我们可以参考 shopify.dev/docs/themes/liquid/reference/filters/additional-filters

摘要

在本章中,我们学习了如何使用一些琐碎的东西,如过滤器来操作不同的数据类型,从而创建强大的功能。我们学习了如何使用 URL 和 HTML 过滤器为我们提供访问 Shopify 中各种类型资产的方法,并帮助我们使用相应的 HTML 标签在店面中生成它们。

在产品媒体画廊项目中工作,使我们更深入地理解了媒体对象和过滤器,这是每个开发者都需要熟悉的。产品折叠面板项目教会了我们如何使用字符串和数组过滤器轻松地操作数据,以创建独特且易于维护的页面内容元素。继续到数学和货币过滤器,我们获得了通过 Shopify 执行复杂计算以及根据我们店铺中设置的货币格式化价格所需的重要见解。

最后,我们学习了额外的过滤器,这些过滤器为我们提供了如何使用没有指定值的默认值变量以及如何使 Liquid 代码可通过 JavaScript 读取的基本知识。通过理解如何使用翻译键,我们现在拥有了快速更新主题中任何值所需的知识,而无需手动更新每个值。

通过本章所学到的知识,在下一章中我们将学习更多关于 JSON 设置以及如何使用它们来创建商家可以通过主题编辑器访问的设置时,将特别有用。

问题

  1. 假设我们有一个名为 product_handles 的数组,包含 30 个产品的处理程序。以下代码中的哪个问题会阻止我们成功输出所有 30 个产品的图片?

    {% for handle in product_handles %}
      {% assign product_object = all_products[handle] %}
      {% for image_item in product_object.images %}
        <img src="img/{{ image_item | img_url }}"/>
      {% endfor %}
    {% endfor %}
    
  2. 为什么不建议在创建产品媒体画廊时仅使用 model_viewer_tag 标签?

    {% for media in product_object.media %}
      {% case media.media_type %}
        {{ media | model_viewer_tag }}
      {% endcase %}
    {% endfor %}
    
  3. 如果我们想要访问数组中的特定位置的项,可以使用哪个过滤器?

  4. 我们可以使用哪个过滤器来快速更新主题文件中任何字符串值的出现?

熟能生巧

项目 3

在我们之前的练习中,我们学习了如何通过输出所有类型的商品媒体类型来创建基本和复杂的产品画廊。然而,如果我们一次只需要输出与特定变体相关的几个图片呢?

在我们的第三个项目中,我们将致力于渲染一个具有独特标记的产品媒体画廊,这将使我们能够仅显示当前所选变体的缩略图。

以下是资产的说明:

  1. 使用名称为 variant-thumbnails.liquid 的新页面模板。

  2. 创建一个名为 Product Variant Thumbnails 的新页面。

  3. 使用名称为 alternate.liquid 的新布局文件。

  4. 创建至少包含三种颜色变体的新产品,并为每个变体上传至少三种媒体资产。在创建新产品时,我们应该将产品状态设置为“活动”,而不是将其保留为“草稿”。否则,我们以后将无法访问它。

  5. 创建两个新的资产文件,一个名为 custom.css 的样式表文件和一个名为 script.js 的脚本文件。

  6. 创建一个名为 custom-media 的新代码片段文件。

以下是为任务分配的步骤:

  1. 通过包含我们在此项目中所使用的两个资产文件来编辑新的布局文件。

  2. 将布局文件包含到之前创建的页面模板中,并将新的页面模板分配给新创建的页面。

  3. 导航到 red_This 是一个图像描述。为所有图像重复此过程。请注意,每个变体至少应有三个图像。

  4. variant-thumbnails.liquid 内部,创建一个变量,我们将使用该变量通过产品处理来访问之前创建的产品对象。

  5. 使用 for 循环访问产品媒体对象,并将媒体对象作为参数传递到 custom-media 代码片段中。

  6. 在代码片段内部,使用情况控制流标签和作为参数传递的对象,编写代码以输出每种媒体文件类型。

  7. 编辑每个 media 标签,包括三个属性。第一个属性,data-variant,将包含下划线之前媒体 alt 文本的第一个部分。第二个属性,alt,将包含媒体 alt 文本的第二个部分。最后,第三个属性,命名为 index,将包含媒体文件的索引位置值。如果我们直接在代码片段内部调用 forloop 对象的索引值有困难,我们可能需要通过代码片段将其作为参数传递。

在设置好属性后,我们应该拥有过滤产品媒体文件并仅使用 data-variant 属性值显示与当前所选颜色变体相关的缩略图所需的所有必要元素。

对于那些想要完成项目的人来说,请遵循以下步骤:

  1. custom.css 中应用必要的样式,并在 script.js 中应用必要的脚本,以隐藏或仅显示具有正确 data-attribute 值的媒体文件。

  2. 此外,我们可以创建一个新的 for 循环,作为主要媒体库使用。

  3. 包含任何类型的滑块插件,例如 Slick 滑块,并将其连接到主要媒体库。

  4. 点击缩略图媒体资产应自动使主滑块滚动到所选资产。

第三部分:幕后

本节教导我们如何使用 JSON 创建不同的设置,使商家能够通过主题编辑器轻松地自定义他们的主题。我们还将了解新引入的 JSON 类型模板,结合元字段,将使我们能够为任意数量的页面创建独特且易于配置的内容。最后,我们将深入了解 Shopify Ajax API,学习如何实现高级功能并使店铺更加动态。

本节包含以下章节:

  • 第五章配置主题设置

  • 第七章处理静态和动态部分

  • 第八章探索 Shopify Ajax API

第六章:第六章:配置主题设置

在前面的章节中,我们一直在学习 Shopify 作为平台,熟悉 Liquid 基础知识,并使用 Liquid 核心来创建店面上的各种功能。然而,除非店主是开发者,否则他们对我们为他们创建的任何功能将不会有太多控制权。

在本章中,我们将学习如何使用 JSON 创建通过主题编辑器可访问的设置,这将允许店主轻松地定制主题,而无需在整个主题中进行代码调整。本章将涵盖以下主题:

  • 探索 JSON 设置

  • 了解输入设置属性

  • 基本和特殊输入类型

  • 组织主题编辑器

  • 查看已弃用的设置

到我们完成这一章的时候,我们将对 JSON 的重要性有更深入的理解,以及我们如何使用它来创建在整个主题的任何页面上都可访问的主题设置。我们可以使用这些设置来修改 CSS 值,更改某些功能的内容,甚至可以使用这些设置来完全启用或禁用某个特定功能。通过学习如何使用 JSON 来创建这些设置,我们将朝着创建一个真正动态和可定制的功能店面迈出另一步,这正是 Shopify 所追求的。

技术要求

虽然我们将解释每个主题并配合相关图形进行展示,但考虑到 Shopify 是一个托管服务,我们需要互联网连接来遵循本章中概述的步骤。

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter06

本章的“代码实战”视频可在此找到:bit.ly/3nLQgMf

探索 JSON 设置

第一章 开始使用 Shopify 中,我们简要提到了 Config 目录,我们可以在其中定义和管理整个主题的全局 JSON 值。让我们回顾一下我们可以在这个目录中找到的两个基本和重要的 .json 文件:

  • settings_schema.json 文件允许我们在主题编辑器中创建和管理主题内的内容,我们可以在整个主题文件中引用这些内容。

  • 另一方面,settings_data.json 文件记录了我们方案文件中定义的所有选项及其值。我们可以将此文件视为我们的主题数据库,我们可以通过更新主题编辑器中的主题设置或直接编辑 settings_data.json 文件内的值来管理它。

我们可以将全局设置选项分组到不同的类别中,以便更直观地导航,这可以通过使用namesettings属性来完成:

{
  "name": "Category",
  "settings": [
  ]
}

正如我们所见,与前面章节中提到的Locales目录中的.json文件一样,settings_schema.json文件有一个我们必须遵守的特定格式。使用name属性,我们可以设置类别的名称,而settings属性将包含该类别将包含的设置数组:

{
  "name": "Category",
  "settings": [
    {
      "type": "color",
      "id": "store_background_color",
      "label": "Background Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "store_border_color",
      "label": "Border Color",
      "default": "#cccccc"
    }
  ]
}

在前面的例子中,我们包含了两种类型的颜色设置,一种将控制我们商店的背景颜色,另一种将负责设置我们在整个商店中使用的边框颜色。正如我们所见,我们将每个settings选项都包含在大括号内,并用逗号分隔。然而,请注意,类别中最后一个settings选项后面没有逗号。

重要提示:

settings块中的最后一个属性后面包含逗号,或者在类别中最后一个settings块后面包含逗号,将导致错误,我们将无法保存我们的工作。

现在我们已经定义了全局设置,我们需要学习如何访问它们并恢复它们的值。我们可以使用settings关键字和输入的 ID 来恢复任何全局输入设置的值,这些值我们想要恢复,用点分隔并放在双大括号内:

{{ settings.store_background_color }}
{{ settings.store_border_color }}

我们现在有机会看到我们如何定义输入设置选项并读取它们的值,但我们究竟在哪里渲染这个选项以及如何修改它?

我们可以通过导航到管理区域的在线主题部分,然后点击我们想要定制的主题上的自定义按钮来访问主题编辑器:

![图 6.1 – 通过在线主题部分访问主题编辑器的示例图片

图 6.1 – 通过在线主题部分访问主题编辑器的示例

注意,在整个主题编辑器中做出的更改都是针对特定主题的,因此我们应该记得点击我们想要定制的主题上的自定义按钮。

或者,我们可以通过点击位于右上角的自定义主题按钮,通过代码编辑器访问主题编辑器:

![图 6.2 – 通过代码编辑器访问主题编辑器的另一种方式图片

![图 6.2 – 通过代码编辑器访问主题编辑器的另一种方式一旦进入主题编辑器,我们将在侧边栏中看到一系列类别选项。然而,这些选项中的大多数都是我们将要在下一章中了解更多内容的章节和块的一部分。现在,我们可以通过点击位于右下角的主题设置按钮来访问我们在settings_schema.json文件中定义的全局设置:![图 6.3 – 在主题编辑器中访问全局设置![图 6.3 – 图 6.3 的示例图 6.3 – 在主题编辑器中访问全局设置在settings_schema.json文件中,每个输入集包含一组属性。虽然其中一些是必需的,但其他的是可选的。让我们更深入地了解它们。# 了解输入设置属性每个输入设置选项可以包含以下五个属性,也称为标准属性:+ 如其名称所示,type属性允许我们设置输入设置的类型,可以是基本类型或专用类型。type属性是强制的。+ id属性是另一个我们将要使用的强制属性,用于访问和读取设置值。+ label属性允许我们在主题编辑器中设置输入设置的标签。label属性是强制的。+ default值作为安全措施,允许我们为输入设置设置默认选项。然而,它不是强制的。+ 最后一个属性,info,允许我们包含有关输入设置的额外说明,并且也不是强制的。虽然大多数输入设置将只包含之前提到的属性,但根据输入类型,我们可能需要包含一些额外的属性。我们之前提到,类型属性允许我们在两种不同的输入设置类型之间进行选择,基本和专用,但它们究竟是什么呢?## 基本输入类型基本输入类型是一组选项,允许我们在主题编辑器中包含各种类型的输入设置。在基本类别下,我们可以使用以下选项:+ checkbox+ number+ radio+ range+ select+ text+ textarea正如我们之前提到的,大多数输入设置将只包含标准属性。然而,一些专用输入甚至基本输入将需要额外的属性。现在让我们查看每种输入类型,学习如何使用它,以及我们可以期待什么类型的成果。### 复选框输入如其名所示,checkbox类型的输入是一个布尔类型的字段,允许我们在主题编辑器中创建复选框选项:php{  "type": "checkbox",  "id": "enable_popup",  "label": "Enable popup",  "default": true}正如我们所见,checkbox输入包含三个强制属性和一个可选属性,我们将它们的值设置为true。否则,如果我们移除default属性,复选框的默认状态将是false。以下截图展示了复选框输入类型的示例:![图 6.4 – 复选框基本输入类型的示例![图 6.4 – 复选框基本输入类型的示例图 6.4 – 复选框基本输入类型的示例我们可以使用布尔输入类型来切换功能的开和关,这可以通过获取复选框输入值并使用if语句检查其当前状态来实现:php{% if settings.enable_popup == true %}{% endif %}通过检查checkbox输入值是否等于true,我们创建了一个简单的功能,可以轻松启用或禁用商店中的某个功能,因为语句内部的代码块仅在语句为true时渲染:php{% if settings.enable_popup != blank %}{% endif %}注意,我们可以通过将复选框输入值与blank变量进行比较来实现相同的结果。### 数字输入number类型的输入是 Shopify 的最新添加,正如其名称所暗示的,它是一个数字类型的字段,允许我们在主题编辑器内部创建一个数字选择器输入。除了标准属性外,我们还可以使用可选的placeholder属性,这允许我们在文本输入中包含一个占位符值:php{  "type": "number",  "id": "number_of_products",  "label": "Number of products",  "placeholder": "24"}注意,number类型的输入只能包含一个数值。在以下屏幕截图中,我们可以看到一个number输入类型的示例:图 6.5 – 基本数字输入类型的示例

图 6.5 – 基本数字输入类型的示例

一旦我们定义了数字输入,我们可以通过将settings关键字与文本输入的 ID 配对来访问它:

{{ settings.number_of_products }}

number输入值将始终返回一个数值,除非它是空的,在这种情况下,它将返回一个EmptyDrop值。我们可以通过重新阅读第二章液体的基本流程,并检查理解数据类型部分中的EmptyDrop子部分来提醒自己EmptyDrop

单选输入

使用radio输入类型,我们可以输出一个radio选项字段,这允许我们进行多选项选择。radio输入使用标准属性,并增加了必需的options属性。

options属性接受一个包含valuelabel属性的数组,这些属性是必需的:

{
  "type": "radio",
  "id": "heading_alignment",
  "label": "Heading alignment",
  "options": [
    {
      "value": "left",
      "label": "Left"
    },
    {
      "value": "center",
      "label": "Center"
    },
    {
      "value": "right",
      "label": "Right"
    }
  ],
  "default": "center"
}

注意,我们需要将default属性设置为options数组内部之前定义的其中一个值。否则,如果未定义default属性,则默认选择第一个单选按钮。以下是一个radio输入类型的示例:

图 6.6 – 基本单选输入类型的示例

图 6.6 – 基本单选输入类型的示例

一旦我们定义了radio输入,我们可以通过将settings关键字与单选输入的 ID 配对来访问它:

.heading {
  text-align: {{ settings.heading_alignment }};
}

单选按钮值将始终返回一个字符串值。

范围输入

使用range输入类型,我们可以创建一个范围滑块字段。与之前的输入相比,range输入有四个额外的属性和一个对标准属性的改变。我们可以以下这种方式列出额外的属性:

  • min属性允许我们设置范围输入的最小值。min值是必需的。

  • max值也是一个必需的属性,允许我们设置范围输入的最大值。

  • step值允许我们设置滑块步骤之间的增量值。step滑块是必需的。

  • 第四个和最后一个额外属性unit是一个可选属性,它允许我们为范围滑块值设置视觉单位,例如px。请注意,unit属性只接受最多三个字符,并且将在主题编辑器中纯视觉地输出 px。实际值将返回一个不带单位的数值。

标准属性集的一个额外变化是default属性现在是必需的:

{
  "type": "range",
  "id": "logo_size",
  "min": 120,
  "max": 220,
  "step": 1,
  "unit": "px",
  "label": "Logo Size",
  "default": 140
}

注意,minmaxstepdefault属性都是数值类型。在这些属性中包含字符串值将导致错误。

与之前的属性相比,range属性有一些我们必须遵守的规则。第一条规则是default值必须在minmax值之间。第二条且更为重要的规则是,每个范围滑块最多可以有 100 个步骤,但这究竟意味着什么呢?

例如,在上面的例子中,我们设置了min值为120max值为220。由于我们设置了step值为1,我们在两个值之间有精确的 100 个步骤。

另一方面,如果我们把max值设置为320,我们也必须更新unit值为2以保留minmax值之间的 100 个步骤。在下面的屏幕截图中,我们可以看到range输入类型的示例:

图 6.7 – 范围基本输入类型的示例

图 6.7 – 范围基本输入类型的示例

图 6.7 – 范围基本输入类型的示例

一旦我们定义了range输入,我们可以通过将settings关键字与range输入的 ID 配对来访问它。然而,请记住,unit属性完全是视觉上的。由于range输入返回一个数值,我们需要在样式表中手动包含unit值:

.logo-img {
  max-width: {{ settings.logo_size }}px;
}

这是我们应该始终在定义样式设置时包含default属性,或者至少将整个 CSS 行包裹在一个检查值是否存在的语句中的原因之一。否则,如果输入的值未定义,我们可能会破坏样式表。

选择输入

select输入类型允许我们创建一个下拉选择字段。除了标准属性集之外,select输入类型还有两个额外属性。我们可以以下列方式列出额外属性:

  • options属性与range输入类似,允许我们创建一个包含valuelabel属性的数组来定义下拉选项。选项以及数组内的valuelabel都是必需属性。

  • group属性是一个可选属性,它允许我们在下拉菜单中分组不同的选项。

radio输入类型一样,如果我们没有定义default属性,则默认选择第一个选项:

{
  "type": "select",
  "id": "font_family",
  "label": "Font Family",
  "options": [
    {
      "value": "raleway-light",
      "label": "Raleway - Light",
      "group": "Raleway"
    },
    {
      "value": "raleway-regular",
      "label": "Raleway - Regular",
      "group": "Raleway"
    },
    {
      "value": "playfair-display-regular",
      "label": "Playfair Display - Regular",
      "group": "Playfair Display"
    }
  ],
  "default": "playfair-display-regular"
}

由于select输入返回一个字符串值,最常见的一种用途是在主题中包含一个自定义字体族,正如我们在前面的例子中看到的那样:

图 6.8 – select 基本输入类型的示例

图 6.8 – select 基本输入类型的示例

此外,通过使用group属性,我们已经成功地将属于同一家族的所有选项分组:

.heading {
  font-family: {{ settings.font_family }};
}

Shopify 还拥有一个广泛的字体库,我们可以使用,以及一个专门提供对我们提到的库的访问权限的输入,我们将在稍后学习。然而,如果我们想包含 Shopify 字体库中不可用的自定义字体,那么我们需要通过包含一个select类型下拉菜单来使用自定义解决方案来包含它。

文本输入

如其名所示,text类型的输入是一个字符串类型的字段,它允许我们在主题编辑器中创建一个单行文本选项。除了标准属性外,我们还可以使用placeholder属性,这允许我们为文本输入包含一个占位符值:

{
  "type": "text",
  "id": "header_announcement",
  "label": "Header Announcement",
  "placeholder": "Enter a short announcement.",
  "default": "Spend more than 100$ to qualify for a 10%     discount!"
}

注意,text类型的输入只能包含字符串值,不能包含任何 HTML 标签。在下面的屏幕截图中,我们可以看到一个text输入类型的示例:

图 6.9 – text 基本输入类型的示例

图 6.9 – text 基本输入类型的示例

一旦我们定义了文本输入,我们可以通过将settings关键字和文本输入的 ID 配对来访问它:

{{ settings.header_announcement }}

text输入值始终返回一个字符串值,除非它是空的,在这种情况下,它将返回一个EmptyDrop值。

textarea 输入

输入类型textarea与文本输入类似,唯一的区别是textarea是一个多行文本字段,而text输入是一个单行字段。除了标准属性外,我们还可以使用placeholder属性来为textarea输入包含一个占位符值:

{
  "type": "textarea",
  "id": "header_announcement_textarea",
  "label": "Header Announcement",
  "placeholder": "Enter a short announcement.",
  "default": "Spend more than 100$ to qualify for a 10%     discount!"
}

同样,与text输入值一样,textarea输入值始终返回一个字符串值,除非它是空的,在这种情况下,它将返回一个EmptyDrop值。我们可以在下面的屏幕截图中看到textarea输入的示例:

图 6.10 – textarea 基本输入类型的示例

图 6.10 – textarea 基本输入类型的示例

通过textarea,我们现在已经涵盖了所有基本输入设置,这使我们走上了理解和处理主题编辑器.json文件的坚实道路。然而,要真正可以说我们掌握了.json文件的工作知识,我们还需要学习有关专门输入设置的内容。

专门输入设置

专业输入类型是一组专业选项,它不允许我们在主题编辑器中包含各种类型的输入设置,但同时也为我们提供了轻松访问各种 Liquid 对象的能力。在专业输入下,我们可以使用以下选项:

  • richtext

  • html

  • linklist

  • liquid

  • color

  • url

  • video_url

  • image_picker

  • font_picker

  • article

  • blog

  • collection

  • page

  • product

与基本输入类型相似,大多数输入设置将只包含标准属性。然而,一些输入将需要额外的属性。通过了解所有不同类型的专业输入,我们将学习如何使用它们以及我们可以期待的结果类型,这将有助于创建我们在下一章中将要学习的复杂部分。

richtext输入

richtext输入类型类似于基本的textarea类型输入,因为它们都输出一个多行文本字段。主要的区别在于richtext还为我们提供了一些基本的格式化选项:

  • 粗体

  • 斜体

  • 下划线

  • ]

  • 段落

第二个区别是,虽然texttextarea输入值返回一个干净的字符串,但richtext总是会返回一个格式化为段落并封装在 HTML <p></p>标签内的字符串值。以下截图展示了richtext输入的一个示例:

图 6.11 – 专业输入类型的示例

图 6.11 – richtext专业输入类型的示例

此外,使用richtext提供的格式化选项将自动更新字符串值与相应的 HTML 标签。

重要提示:

使用格式化选项将自动将必要的 HTML 标签应用到richtext字符串值上。然而,我们无法在主题编辑器的richtext字段中手动包含任何 HTML 标签。

虽然default属性不是强制的,如果我们决定使用它,我们必须在default属性值中包含<p></p>标签。否则,我们将收到错误:

{
  "type": "richtext",
  "id": "header_announcement_richtext",
  "label": "Header Announcement",
  "default": "<p>Spend more than 100$ to qualify for a 10%     discount!</p>"
}

注意,与接受placeholder属性的textarea不同,richtext不接受任何placeholder属性。

html输入

html输入类型是一个多行文本字段,正如其名称所暗示的,它允许我们在输入字段中包含 HTML 标记。除了标准属性外,html输入类型还接受一个可选的placeholder属性:

{
  "type": "html",
  "id": "google_analytics",
  "label": "Google Analytics",
  "placeholder": "Paste the Google Analytics code here."
}

虽然html输入将接受大多数 HTML 标签,但 Shopify 会自动移除以下三个标签:

  • <html>

  • <head>

  • <body>

html类型输入值将始终返回一个字符串值,如果它是空的,则返回EmptyDrop值。我们可以在以下截图中的示例中看到html输入:

图 6.12 – 专业输入类型的示例

图 6.12 – html专业输入类型的示例

注意,虽然我们可以在html类型输入中包含 HTML 代码,但我们不能在字段中包含 Liquid 代码,因为它将被处理为一个简单的字符串。

链接列表输入

使用link_list输入类型,我们可以创建一个特殊的菜单选择字段类型,允许我们输出一个商店导航菜单。请注意,我们只能在之前在在线商店部分下位于导航部分的管理界面中看到的菜单:

{
  "type": "link_list",
  "id": "header-menu",
  "label": "Header Menu",
  "default": "main-menu"
}

注意,虽然default属性是可选的,但它只接受两个特定的值,即main-menufooter。让我们看看以下屏幕截图中linklist输入的外观:

图 6.13 – 专业输入类型的示例

图 6.13 – linklist专业输入类型的示例

使用设置和 ID 检索link_list值将返回一个linklist对象,我们可以使用它来构建导航菜单。如果我们回想一下,在第四章使用对象深入 Liquid 核心中,我们使用了以下代码来输出具有特定indoor-navigation句柄的导航菜单:

{% assign collection-menu = linklists.indoor-navigation %}
  {% for link in collection-menu.links %}
{% endfor %}

如果我们想要使用不同的导航,我们就必须手动更新菜单导航句柄。然而,考虑到我们现在可以直接从主题编辑器中恢复linklist对象,我们可以更新之前硬编码的linklist对象值,并用一个动态值替换它:

{% assign collection-menu = settings.header-menu %}
  {% for link in collection-menu.links %}
{% endfor %}

注意,如果link_list类型输入没有default属性或我们尚未选择菜单,我们将收到一个blank值作为返回值。

液体输入

liquid输入类型也是 Shopify 最近添加的功能,它允许我们包含 HTML 标记和有限的 Liquid 代码,这使得它成为一个相当强大的工具。以下是该输入的示例代码:

{
  "type": "liquid",
  "id": "liquid_block",
  "label": "Liquid block"
}

注意,当我们保存设置时,任何未关闭的 HTML 标签都会自动关闭。在下面的屏幕截图中,我们可以看到一个liquid输入类型的示例:

图 6.14 – 专业输入类型的示例

图 6.14 – liquid专业输入类型的示例

liquid输入类型为我们提供了访问所有全局对象、基于页面的对象以及标准标签和过滤器的权限。

注意,如果liquid类型输入值是空的,它将始终返回一个字符串值或EmptyDrop值。

颜色输入

如其名所示,color输入类型允许我们创建一个颜色选择器类型的输入,以便轻松更新商店的颜色调色板:

{
  "type": "color",
  "id": "store_background_color",
  "label": "Background Color",
  "default": "#ffffff"
}

我们可以通过组合settings关键字和输入的 ID 来访问color输入类型的值:

body {
  background-color: {{ settings.store_background_color }};
}

除了可以在color字段中手动输入十六进制颜色外,我们还可以触发一个实际的调色板,在那里我们可以选择所需的颜色色调:

图 6.15 – 专业输入类型的示例

图 6.15 – color专业输入类型的示例

注意,虽然 default 属性是可选的,但我们应始终包含默认值或在整个 CSS 行中包含整个语句,以检查输入值是否为空。如果我们忽略包含这两个中的任何一个,如果 color 输入值未定义,我们可能会得到一个损坏的样式表。

url 输入

url 类型输入为我们提供了一个特殊的 URL 输入字段,我们可以手动输入外部 URL 或使用一系列下拉菜单来选择以下资源的路径:

  • 文章

  • 博客

  • 集合

  • 页面

  • 产品

  • 政策

我们也可以通过粘贴 URL 并随后点击下拉菜单中的链接来确认选择,从而在我们的商店之外包含对网站的链接:

图 6.16 –  专用输入类型的示例

图 6.16 – url 专用输入类型的示例

注意,虽然 default 属性是可选的,但它只接受两个特定的值 – /collections/collections/all

{
  "type": "url",
  "id": "banner_link",
  "label": "Banner link",
  "default": "/collections"
}

我们可以通过组合 settings 关键词和输入的 ID 来访问 url 输入类型的值,然后将其作为 HTML <a> 标签的 href 参数包含:

<a href="{{ settings.banner_link }}"></a>

考虑到 HTML <a> 标签是硬编码的,我们无法使用 url 类型输入对其进行任何动态修改。然而,我们可以引入一个额外的输入类型,例如 checkbox,我们可以使用它来显示或隐藏 target="_blank" 属性。

注意,color 类型输入值将始终返回一个字符串值或 nil(如果未定义值)。

video_url 输入

video_url 类型输入为我们提供了一个特殊的 URL 输入字段,我们可以手动输入来自 YouTube 或 Vimeo 的视频的外部 URL 并提取它们的 ID 以供以后使用。除了标准属性集之外,video_url 输入类型还有两个附加属性。我们可以以下列方式列出附加属性:

  • accept 属性是一个必填的数组类型属性,我们可以定义我们将接受的不同类型的提供者的视频 URL。有效值是 youtubevimeo 或两者的组合。

  • placeholder 属性是一个可选的类型属性,它允许我们为 video_url 输入包含一个占位符值。

以下代码显示了具有先前属性的 video_url 类型输入:

{
  "type": "video_url",
  "id": "banner_video_url",
  "label": "Video url",
  "accept":  [
    "youtube",
    "vimeo"
  ],
  "placeholder": "Enter the YouTube or Vimeo video URL."
}

在将视频 URL 包含到两个视频平台之一后,我们将看到视频名称和第一帧,这使我们能够确认我们拥有正确的视频 URL:

图 6.17 –  专用输入类型的示例

图 6.17 – video_url 专用输入类型的示例

我们可以通过组合 settings 关键词和输入的 ID 来访问 video_url 输入类型的值,这将返回我们在主题编辑器中先前包含的 URL:

{{ settings.banner_video_url }}

与之前返回单个值的输入类型相比,video_url 允许我们访问视频 URL 的两个附加部分:

  • id 属性,它允许我们仅恢复视频 ID

  • type 属性,它允许我们轻松识别视频是否来自 YouTube 或 Vimeo 平台:

    {{ settings.banner_video_url.id }}
    {{ settings.banner_video_url.type }}
    

注意,无论我们尝试返回完整的 URL 还是仅返回其 type/id,返回的值始终是字符串类型,除非 video_url 值未定义,在这种情况下,返回的值将是 nil:

_9VUPq3SxOc
youtube

现在我们已经恢复了视频信息,剩下要做的就是在每个相应平台的 iframe 嵌入中包含返回的值。注意,我们需要为每个平台包含一个单独的 iframe 嵌入,这应该不成问题,因为我们可以使用 type 属性轻松识别视频属于哪个平台。

注意,由于 video_url 返回字符串类型的 URL,因此不可能使用我们在前几章中提到的媒体对象生成必要的 iframe 嵌入。

image_picker 输入

如其名称所示,image_picker 输入类型允许我们创建一个图像选择器选择字段。图像选择器选择器允许我们上传新图像,从 Shopify 上提供的免费照片系列中选择照片,或使用 Shopify 管理员中 文件 部分之前包含的任何照片。

文件 部分是我们通过管理员或主题编辑器上传的所有资产的家园,我们可以在 文件 部分中找到它,我们还可以使用右上角的 上传文件 按钮直接将资产上传到 文件 部分此外,我们可以轻松恢复任何资产的直接 URL 路径并使用它。

在以下代码块中,我们可以看到如何使用 image_picker 输入的示例:

{
  "type": "image_picker",
  "id": "banner_image",
  "label": "Banner image"
}

注意,image_picker 类型输入值将始终返回一个图像对象值或 nil,如果值未定义:

![图 6.18 – image_picker 专用输入类型的示例图 6.18 – image_picker 专用输入类型的示例

图 6.18 – image_picker 专用输入类型的示例

由于 image_picker 返回一个 image 对象,我们可以使用 img_tagimg_url 过滤器动态生成必要的图像标签:

<img src="img/{{ settings.banner_image | img_url: "600x600" }}"   alt="settings.banner_image.alt }}"/>
{{ settings.banner_image | img_tag: image_item.alt, "class1   class2", "600x600" }}

关于 img_tagimg_url 过滤器的详细说明,我们可以回顾 第五章HTML 和 URL 过滤器 部分,深入 Liquid 核心与过滤器

font_picker 输入

在学习 select 输入类型时,我们提到 Shopify 为我们提供了访问广泛字体库的权限。font_picker 类型输入允许我们创建一个字体选择器选择字段,我们可以用它来选择 Shopify 字体库中的任何字体:

{
  "type": "font_picker",
  "id": "body_font",
  "label": "Body font",
  "default": "helvetica_n4"
}

标准属性集的一个额外变化是,default属性现在是强制性的。我们可以在以下链接中找到default属性的可能的字体句柄值,shopify.dev/themes/architecture/settings/fonts#available-fonts,通过点击font_picker类型输入:

图 6.19 – font_picker 专业输入类型的示例

图 6.19 – font_picker 专业输入类型的示例

由于default值是强制性的,font_picker值将始终返回一个font对象,允许我们使用font过滤器和对象来操纵font_picker值以满足我们的需求。

假设我们尝试使用常规方法访问font_picker值,我们会收到FontDrop作为结果。为了解决这个问题,我们将包括font_face过滤器,这将生成@font-faceCSS:

<style>
  {{ settings.body_font | font_face }}
</style>

声明font_face过滤器将自动获取特定字体的所有必要信息,并将所有信息填充到@font-face内部:

<style>
  @font-face {
  font-family: Helvetica;
  font-weight: 400;
  font-style: normal;
  src: url("https://fonts.shopifycdn.com/helvetica/  helvetica_n4.fe093fe9ca22a15354813c912484945a36b79146   .woff2?&hmac=64c57d7fee8da8223a0d4856285068c02c248ef210ca   e57dcd9c3e633375e8a4") format("woff2"),
    url("https://fonts.shopifycdn.com/helvetica     /helvetica_n4.8bddb85c18a0094c427a9bf65dee963ad88de     4e8.woff?&hmac=f74109e3105603c8a8cfbd8dec4e8a7e535     72346fb96aacec203fc3881ddabf1") format("woff");
}
</style>

font_face过滤器到位后,我们现在可以访问所选字体。然而,在当前设置中,我们可能需要为每个font-family硬编码值。所以,让我们学习如何单独提取@font-face属性。

我们需要做的第一件事是创建一个变量,我们将把font_picker对象值保存在这个变量中,之后我们需要使用font_face过滤器调用这个变量:

<style>
  {% assign body_font = settings.body_font %}
  {{ body_font | font_face }}
</style>

font_face声明后,我们现在可以轻松访问变量内的@font-face

假设我们想要修改特定属性,例如font-weightfont-style,对于特定元素。为了实现这种功能,我们可以使用font_modify过滤器,它接受两个属性,即style属性,允许我们修改font-style,以及weight,我们可以用它来修改font-weight属性:

<style>
  {% assign body_font = settings.body_font %}
  {{ body_font | font_face }}
  {% assign body_font_bold = body_font | font_modify:     "weight", "bolder" %}
  {% assign body_font_italic = body_font | font_modify:     "style", "italic" %}
  {% assign body_font_bold_italic = body_font_bold |     font_modify: "style", "italic" %}
{{ body_font_bold | font_face }}
{{ body_font_italic | font_face }}
{{ body_font_bold_italic | font_face }}
</style>

注意,我们现在有三个不同的变量,包含三种不同类型的@font-face。现在我们只剩下提取所需的特定属性并将它们分配给样式化内容。

我们可以通过使用font对象的familystyleweight属性来返回特定的@font-face属性值:

<style>
  {% assign body_font = settings.body_font %}
  {{ body_font | font_face }}
  {% assign body_font_bold = body_font | font_modify:     "weight", "bolder" %}
  {% assign body_font_italic = body_font | font_modify:     "style", "italic" %}
  {% assign body_font_bold_italic = body_font_bold |     font_modify: "style", "italic" %}
  {{ body_font_bold | font_face }}
  {{ body_font_italic | font_face }}
  {{ body_font_bold_italic | font_face }}
  .body_bold {
    font-family: "{{ body_font_bold.family }}";
    font-style: "{{ body_font_bold.style }}";
    font-weight: "{{ body_font_bold.weight }}";
  }
  .body_italic {
    font-family: "{{ body_font_italic.family }}";
    font-style: "{{ body_font_italic.style }}";
    font-weight: "{{ body_font_italic.weight }}";
  }
</style>

我们现在已经成功学习了font_picker输入类型的工作原理,更重要的是,我们还学习了如何使用font对象和过滤器来输出font_picker值,并使字体选择过程完全动态。

为了使我们的代码更实用,我们可以在上一个示例中添加一个后备字体族,以防选定的字体族由于某种原因无法渲染。我们可以通过引入fallback_families对象来实现这一点,该对象将返回一个建议的后备字体族:

<style>
  {% assign body_font = settings.body_font %}
  {{ body_font | font_face }}
  {% assign body_font_bold = body_font | font_modify:     "weight", "bolder" %}
  {% assign body_font_italic = body_font | font_modify:     "style", "italic" %}
  {% assign body_font_bold_italic = body_font_bold |     font_modify: "style", "italic" %}
  {{ body_font_bold | font_face }}
  {{ body_font_italic | font_face }}
  {{ body_font_bold_italic | font_face }}
  .body_bold {
    font-family: "{{ body_font_bold.family }}", 
        "{{ body_font_bold.fallback_families }}";
    font-style: "{{ body_font_bold.style }}";
    font-weight: "{{ body_font_bold.weight }}";
  }
  .body_italic {
    font-family: "{{ body_font_italic.family }}", 
        "{{ body_font_italic.fallback_families }}";
    font-style: "{{ body_font_italic.style }}";
    font-weight: "{{ body_font_italic.weight }}";
  }
</style>

关于所有可用字体过滤器的更多信息,我们可以参考shopify.dev/api/liquid/filters/font-filters,以及关于所有可用字体对象的更多信息,我们可以参考shopify.dev/api/liquid/objects/font

文章输入

article类型输入为我们提供了一个特殊的文章选择器选择字段。通过文章选择器,我们可以访问商店中所有可用的文章:

{
  "type": "article",
  "id": "featured_article",
  "label": "Featured article"
}

直到最近,article输入类型总是会返回文章的字符串 handle,然后需要使用它来恢复article对象。然而,自 Shopify Unite 2021 活动以来,article和其他页面相关输入现在返回一个对象,这使得我们的工作变得容易得多。

注意,虽然与页面相关的输入类型会返回一个对象,我们可以从中提取所需的任何值,但我们有时仍会发现自己正在处理一个主题,该主题使用旧方法来检索与页面相关的输入类型对象。因此,我们将提及两种方法,因为我们虽然不会使用旧方法,但如果我们需要使用它,了解其工作原理是至关重要的。在下面的屏幕截图中,我们可以看到一个article输入类型的示例:

图 6.20 – 文章专用输入类型的示例

图 6.20 – 文章专用输入类型的示例

由于article输入值返回一个对象值,我们已经有权访问article对象,并且可以轻松检索我们可能需要的任何属性:

{% assign article = settings.featured_article %}

如我们所见,现在article输入返回一个对象,访问该对象本身和检索任何属性的值几乎不费吹灰之力。现在让我们来看看使用文章 handle 检索article对象的过时方法。

如前所述,在 Shopify Unite 2021 活动之前,article输入值返回一个 handle 字符串。要访问article对象,我们需要将我们要访问的对象名称复数化,然后加上方括号[]表示法,类似于我们在第二章 Liquid 的基本流程中处理产品 handle 的方式:

{% assign article = articles[settings.featured_article] %}

现在,我们有权访问article对象,并且可以轻松地将任何类型的文章内容输出到商店的任何部分。请注意,虽然两种方法产生相同的结果,但通过其 handle 恢复页面相关输入对象的第二种方法现在已过时。

博客输入

blog类型输入的工作方式类似,因为它通过提供一个特殊的博客选择器选择字段,使我们能够访问商店中所有可用的文章:

{
  "type": "blog",
  "id": "featured_blog",
  "label": "Featured blog"
}

访问特色博客将始终返回一个blog对象或 nil,如果值尚未定义:

图 6.21 – 博客专用输入类型的示例

图 6.21 – 博客专用输入类型示例

article对象类似,我们可以直接访问blog对象或使用方括号[]表示法检索它。唯一的区别是object关键字的变化:

{% assign blog = blogs[settings.featured_blog] %}
{% for article in blog %}
{% endfor %}

一旦我们恢复了blog对象,我们可以使用for标签遍历所选博客中的所有文章并正确渲染它们。

集合输入

collection类型输入为我们提供了一个特殊的集合选择器字段,通过该字段我们将获得对所选集合中所有可用产品的访问权限:

{
  "type": "collection",
  "id": "featured_collection",
  "label": "Featured Collection"
}

与之前的设置类型相似,如果我们尚未选择集合,则返回值将为 nil。否则,返回值将返回一个对象值,我们可以使用它来检索任何对象属性值:

![Figure 6.22 – 集合专用输入类型示例

![Figure 6.22_B17606.jpg]

图 6.22 – 集合专用输入类型示例

此外,我们可以直接访问collection对象,或者通过将我们要访问的对象名称复数化,然后跟一个方括号[]表示法来访问:

{% assign collection =
collections[settings.featured_collection] %}

collection对象就绪后,我们现在可以轻松访问所有集合属性。

页面输入

page类型输入为我们提供了一个特殊的集合选择器字段,通过该字段我们将获得对之前在商店的页面管理部分创建的所有页面的访问权限:

{
  "type": "page",
  "id": "featured_page",
  "label": "Featured page"
}

当访问特色页面值时,它将始终返回一个对象值,如果值尚未定义,则返回 nil:

![Figure 6.23 – 页面专用输入类型示例

![Figure 6.23_B17606.jpg]

图 6.23 – 页面专用输入类型示例

我们可以直接访问page对象或通过方括号[]表示法访问:

{% assign page = pages[settings.featured_page] %}

使用声明的页面变量,我们现在可以访问page对象,我们可以使用它来进一步访问特定页面的任何属性。

产品输入

正如其名所示,product为我们提供了一个产品输入选择器,我们可以使用它来访问product对象:

{
  "type": "product",
  "id": "featured_product",
  "label": "Featured product"
}

当访问时,如果值尚未定义,将返回 nil,或者我们会得到一个对象值:

![Figure 6.24 – 产品专用输入类型示例

![Figure 6.24_B17606.jpg]

图 6.24 – 产品专用输入类型示例

我们可以使用返回的对象值来访问属性值,或者我们可以使用product对象的方括号[]表示法来使用过时的方法检索对象:

{% assign product = products[settings.featured_product] %}

通过产品变量,我们现在可以访问所选product对象中的所有属性。

到目前为止,我们已经学习了所有不同类型的输入设置,无论是基本还是专业输入类型,我们如何访问它们,以及我们可以包含的附加属性,以及我们可以从它们那里期望接收到的值类型。然而,如果没有对这些设置进行排序,那么在单个类别中堆叠大量设置可能会让人感到不知所措。

组织主题编辑器

在上一节中,我们学习了不同类型的可配置设置,这使我们能够通过一系列基本或专业输入选择器轻松更新其值。现在,我们将学习另一组设置,侧边栏设置。侧边栏设置将允许我们将每个类别的设置划分为其他单独的块。

侧边栏设置不包含任何值,我们也不能通过主题编辑器来配置它们。它们唯一的用途是提供给我们额外的信息,并帮助我们将不同的输入设置集组织成单独的块,以便更直观地导航。

在侧边栏设置下,我们可以使用以下选项:

  • header

  • paragraph

与基本和专业输入设置不同,headerparagraph只能包含以下标准属性:

  • 如其名称所示,type属性允许我们设置设置的类型,其值可以是headerparagraph设置。对于headerparagraphtype属性是必需的。

  • 虽然侧边栏设置不能包含任何值,但我们可以使用content属性将某些信息输出到主题编辑器。对于headerparagraphcontent属性也是必需的。

  • 最后一个属性,info,允许我们提供有关header类型设置的附加信息。请注意,info不是必需的属性,我们只能与header类型设置一起使用。

现在我们已经了解了所有不同类型的侧边栏设置及其属性,让我们分别查看每种设置类型,并学习如何使用它们。

标题类型

header类型的设置,正如其名称所暗示的,允许我们创建一个标题元素,并因此将所有设置输入类型组合在一个单独的块中。请注意,引入header类型设置将自动将分类内的所有输入类型设置组合在一起,直到遇到另一个header元素,或者当前类别中没有更多的设置:

{
  "type": "header",
  "content": "Newsletter settings",
  "info": "Enabling the popup feature will automatically     trigger a newsletter popup on page load."
}

如我们所见,使用header类型设置相对简单,我们获得的好处相当显著,因为现在我们可以轻松地将相关的输入设置分组。此外,通过包含可选的info属性,我们能够包含一些与特定设置块相关的附加信息,这可以在下面的屏幕截图中看到:

图 6.25 – 标题侧边栏类型设置示例

图 6.25 – 头部侧边栏类型设置的示例

我们现在已经学会了如何将相关的设置分组在头部名称下,并包含有关特定设置块的一些附加信息。然而,如果我们需要包含一组额外的信息来进一步描述特定的设置集,该怎么办?为了实现这一点,我们可以使用以下类型的侧边栏设置,即paragraph

段落类型

如前所述,侧边栏设置中的段落类型允许我们包含额外的信息,类似于使用头部类型设置中的info属性:

{
  "type": "paragraph",
  "content": "It is recommended not to decrease the show     after value below 3 seconds."
}

注意,在段落类型设置中,除了typecontent之外,包含任何其他属性将导致错误。在下面的屏幕截图中,我们可以看到一个在主题编辑器中使用段落的示例:

图 6.26 – 段落侧边栏类型设置的示例

图 6.26 – 段落侧边栏类型设置的示例

虽然我们不能包含额外的属性,但我们可以使用content属性创建指向特定页面的直接链接,提供一些附加信息。我们可以通过以下格式包含必要的信息:

link text

包含指向特定页面的直接链接将使我们的主题编辑器更加简洁。同时,这也允许我们包含使用它的人可能需要的所有必要信息:

{
  "type": "paragraph",
  "content": "The paragraph type setting allows us to    include a direct link to any page. Learn more"
}

注意,我们可以在任何接受info属性的设置类型中创建指向任何页面的直接链接,无论是content还是info属性。

我们现在已经了解了所有类型的输入和侧边栏类型设置。然而,虽然了解所有当前设置至关重要,但同样重要的是要提及一些现在已弃用的设置,尽管我们可能在日常工作中还会遇到它们。

查看已弃用的设置

虽然以下设置不再受支持,但我们有很大可能会在所有者从未更新的旧主题中遇到它们。由于这些设置不再受支持,我们不会过多地详细介绍它们的工作原理,但我们将提供一些识别它们及其功能的通用指南。

字体输入

font输入类型设置允许我们生成 Shopify 上可访问的字体文件的简短列表:

{
  "type": "font",
  "id": "body_font",
  "label": "Body font"
}

我们之前提到的font_picker输入类型的引入使font输入类型设置变得过时。

碎片输入

正如其名所示,snippet输入类型允许我们选择我们之前在主题中创建的任何片段文件,并在特定位置执行其内容:

{
  "type": "snippet",
  "id": "featured_products",
  "label": "Featured products"
}

sections的引入,我们将在下一章中学习,已经使snippet输入类型设置变得过时。

摘要

在前面的章节中,我们避免了对每个具体选项进行详述,从而创建了一个我们需要学习的选项列表,以保持内容简洁并直截了当。然而,在本章中,我们详细介绍了每个选项,并仔细解释了何时使用它,以及我们可以期待什么,因为鉴于它们的重要性,我们将定期使用本章学到的知识。

我们已经学习了如何生成一些最基本输入类型,我们可以使用它们来输出各种类型的内容和输入类型,从而允许我们创建与特定内容相关的附加逻辑。此外,我们还学习了如何输出专业化的输入类型,允许我们通过简单且可配置的界面创建复杂功能。

最后,我们学习了如何将 JSON 设置组织成独立的块以提高可读性。通过本章获得的知识将在下一章中派上用场并接受检验。

在下一章中,我们将学习关于部分和块的内容,并使用它们来创建商家可以通过主题编辑器使用的特定部分/块设置。

问题

  1. 有哪两种输入设置类型?

  2. 以下代码片段将导致错误的哪个问题?

    {
      "type": "text",
      "id": "header_announcement",
      "label": "Text",
    }
    
  3. 我们如何在 Shopify 中包含自定义字体文件并在主题编辑器中使用它?

  4. 哪两个问题会阻止我们执行以下代码片段?

    {
      "type": "range",
      "id": "number_of_products",
      "min": 110,
      "max": 220,
      "step": 1,
      "unit": "pro",
      "label": "Number of products",
      "default": 235
    }.
    

第七章:第七章《处理静态和动态部分》

在上一章中,我们熟悉了不同类型的输入,无论是基本的还是专业的,以及我们如何使用它们来创建可以通过主题编辑器轻松配置的全局设置。

在本章中,我们不仅将使用之前提到的输入,还将学习如何创建易于配置和可重复使用的部分,我们可以使用这些部分来轻松更改页面或模板的布局。

在本章中,我们将学习以下主题:

  • 静态部分与动态部分

  • 与部分架构一起工作

  • 使用块构建

  • 使用 JSON 模板增强页面

  • 探索特定于部分的标签

在完成本章后,我们将了解部分是什么,何时使用它们,以及如何创建一个部分。我们还将了解静态部分与动态部分之间的区别,以及我们如何通过主题编辑器来配置它们。

通过了解部分架构和我们可以使用的不同属性,我们还将学习如何在部分内创建可重复使用的模块,我们可以使用这些模块来重复操作并获得不同的结果。在熟悉了部分和块之后,我们将通过学习 JSON 模板和内置元字段来提高这些概念。最后,我们将了解在部分文件内可以使用的不同类型的专用标签,这将帮助我们创建可重复使用和动态的模块。

技术要求

虽然我们将解释每个主题并配合相应的图形展示,但考虑到 Shopify 是一个托管服务,我们需要互联网连接来跟随本章中概述的步骤。

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter07

本章的“代码实战”视频可以在这里找到:bit.ly/3hQzhVg

静态部分与动态部分

第一章《Shopify 入门》中,当我们讨论主题结构时,简要提到了部分文件,但部分究竟是什么呢?

除了是主题文件中的一个目录名称之外,部分是一种文件类型,它允许我们创建可重复使用的模块,我们可以使用主题编辑器来定制这些模块,正如我们之前所学的。然而,与我们所学的全局设置相比,主要区别在于部分的 JSON 设置是在每个部分文件内部定义的,并且是针对特定部分的。

特定部分的设置允许我们在页面上多次使用相同的部分模块,并为每个出现选择不同的选项集,这使得它成为一个相当强大的功能。例如,我们可以创建一个特色收藏部分,并重复三次以显示来自三个不同收藏的三种产品。

在我们继续之前,让我们通过从代码编辑器中点击自定义按钮来导航到主题编辑器,并查看部分文件的实际效果。点击自定义按钮将自动打开主题编辑器,并将我们定位在主页上,页面预览在右侧,侧边栏在左侧。在侧边栏中,我们可以看到此特定页面上可用的几个部分:

![图 7.1 – 主题编辑器内页面部分的示例图片

图 7.1 – 主题编辑器内页面部分的示例

初看之下,我们可以看到页眉页脚部分通过一条细边框与其它部分分开。这告诉我们页眉页脚部分是静态部分,我们不能从主题编辑器中改变它们的位置。

类似于片段文件,section主题标签:

{% section "name-of-the-file" %}

通过包含section标签,我们将自动将部分的内容渲染到我们包含标签的位置,并能够通过其 JSON 设置进一步自定义它。请注意,由于这是一个我们手动包含在主题文件中的静态部分,通过主题编辑器应用到此特定部分的任何设置将在我们包含此部分的任何页面上可见。

例如,主题开发者已经在theme.liquid布局文件中包含了页眉和页脚部分文件,这是我们的主题主文件,这意味着页眉和页脚部分将在我们商店的任何页面上可见。另一方面,如果我们导航到主题编辑器内的不同页面,我们会看到一个页眉、一个页脚,以及在这两个之间的一组完全不同的部分,与我们之前在主页上看到的不同。

通过在theme.liquid文件中包含页眉和页脚部分,我们已经使它们在整个主题的任何页面上都可见。然而,我们不会在theme.liquid文件中包含所有部分,因为它们不是必需的。相反,我们将某些特定模板的部分包含在其各自的模板文件中,这样它们就只能在我们访问具有特定模板的页面时访问。

假设我们想在产品页面上包含相关产品部分。在这里,我们会导航到product.liquid模板,并在任何位置包含section标签,这将使相关产品部分在任何使用此特定模板的产品上可见。

如我们从第四章,“使用对象深入 Liquid 核心”中回忆的,在“The Please apply P-Italics here content_for_layout object”部分,我们提到content_for_layout对象允许我们通过加载动态生成的内容,包括来自其他模板的章节文件,将其连接到theme.liquid文件。

由于content_for_layout输出所有其他模板的内容,通过将其放置在页眉和页脚部分之间,我们已经确保了将所有来自其他模板文件的章节文件放置在页眉和页脚部分之间。让我们看看theme.liquid布局文件;我们会注意到content_for_layout被放置在headerfooter部分之间,如下面的代码块所示:

{% section 'header' %}
  <div class="page-container drawer-page-content" 
    id="PageContainer">
    <main class="main-content js-focus-hidden"         id="MainContent" role="main" tabindex="-1">
      {{ content_for_layout }}
    </main>
    {% section 'footer' %}

注意,在多个模板中包含相同的部分文件将在所有模板中显示相同的内容。通过从主题编辑器配置静态部分,我们可以将选择的数据保存到settings_data.json文件中,这将返回相同部分的任何出现的精确数据。

如果我们需要多次重复静态部分并包含不同的内容,我们需要使用不同的名称创建一个新的部分文件:

{% section "related-product-1" %}
{% section "related-product-2" %}

到目前为止,我们已经看到了静态部分是什么以及如何使用它们来创建基于模板的可配置布局。但我们也有访问动态部分的能力,我们不必每次手动重新定位部分时都包括它们。

正如名称所示,动态部分是一组我们可以添加、删除、重新定位或重复任意次数,内容各不相同,而不需要修改任何代码的部分,所有这些都可以从主题编辑器中完成。让我们回到主题编辑器中的主页,并查看一些现有的动态部分:

图 7.2 – 主题编辑器中页面部分的示例

图片

图 7.2 – 主题编辑器中页面部分的示例

我们可以通过简单地悬停在部分本身上轻松地区分静态部分和动态部分。我们会注意到动态部分名称右侧有两个图标——一个眼睛图标和一个六个点图标——通过悬停。眼睛图标将允许我们切换部分的可见性。通过单击并按住六个点图标,我们可以通过将其移动到其他部分的上方或下方来重新定位部分。

此外,在动态部分的底部,我们会注意到一个添加部分按钮,点击后会出现一个下拉菜单,我们可以通过简单地点击它们来包含我们商店中存在的任意数量的部分。通过添加部分按钮添加到主题中的任何部分都将允许我们为任何出现包含不同的内容,并且我们可以根据需要重复此操作任意多次。

直到最近,主页是我们唯一可以使用动态部分的地方。对于所有其他模板,我们不得不依赖于使用静态部分。然而,自从 Shopify Unite 2021 活动以来,Shopify 引入了 JSON 类型模板,我们在第一章“使用 Shopify 入门”中简要提到了这一点。JSON 模板,我们将在本章后面了解更多,可以为任何页面添加动态部分,并将商店的整个功能提升到一个全新的水平。

现在我们已经了解了什么是部分以及如何使用它们,现在是时候学习如何创建它们了。

与部分模式一起工作

在上一章中,我们学习了如何使用 JSON 创建全局设置,其格式与部分文件的 JSON 格式相似。然而,部分 JSON 有一些显著的不同之处。

第一个主要区别是我们需要在部分文件内定义 JSON,而不是在settings_schema.json文件中。为此,我们需要引入schema标签:

{% schema %}{% endschema %}

schema标签是一个 Liquid 标签,它本身没有输出。它仅仅允许我们在部分文件中编写 JSON 代码。请注意,每个部分文件只能包含一个schema标签,它必须独立存在。它不能嵌套在任何其他 Liquid 标签内。

一旦我们设置了schema标签,我们就可以熟悉在schema标签内可以使用哪些类型的属性了。

名称属性

如其名称所示,name属性允许我们设置部分名称,我们将在稍后通过主题编辑器识别该部分:

{% schema %}
{
  "name": "Announcement bar"
}
{% endschema %}

有了这些,我们已经学会了如何定义部分文件的名称,但如果我们想创建一个国际化的商店,我们可以在主题编辑器中轻松翻译不仅商店内容,还包括商店设置标签怎么办呢?

我们可以轻松地将schema标签内的大多数属性通过在name属性值中包含翻译键来翻译:

{% schema %}
{
  "name": {
    "cs": "Panel oznámení",
    "da": "Meddelelseslinje",
    "de": "Ankündigungsbereich",
    "en": "Announcement bar"
  }
}
{% endschema %}

第一个值代表Locales目录内文件的名称,而第二个值代表翻译值。通过引入翻译键,我们确保一旦我们更改商店语言,name值将自动调整并显示当前选定语言文件的值。

注意,我们可以包括不同类型属性的翻译键,包括我们在上一章中学到的某些输入设置。我们可以使用翻译键的属性包括nameinfolabelgroupplaceholderunitcontentcategory

注意,name是一个必填属性。然而,与其他部分相比,name属性不必是唯一的,因此我们在创建新部分时应注意,以避免混淆。

类属性

class是一个简单的属性,允许我们向div元素添加额外的类,该元素围绕部分内容:

{% schema %}
{
  "name": {
    "cs": "Panel oznámení",
    "da": "Meddelelseslinje",
    "de": "Ankündigungsbereich",
    "en": "Announcement bar"
  },
"class": "homepage-section desktop-only"
}
{% endschema %}

通过这个可选属性,我们可以轻松地将任意数量的类包含在围绕我们的部分的父元素中,如下面的代码块所示:

<div id="shopify-section-[id]" class="homepage-section   desktop-only">
</div>

注意,虽然我们可以包含任意数量的类,但由于class属性只接受字符串值,我们无法动态修改它们。

设置属性

使用settings属性,我们可以创建特定于部分的设置,这允许我们使用主题编辑器来配置部分:

{% schema %}
{
  "name": {
    "cs": "Panel oznámení",
    "da": "Meddelelseslinje",
    "de": "Ankündigungsbereich",
    "en": "Announcement bar"
  },
  "class": "homepage-section desktop-only",
  "settings": [
  ]
}
{% endschema %}

一旦我们定义了settings属性,我们就可以开始包含任何我们之前学过的基本或专业的输入设置,以创建我们需要的流程。由于我们正在创建一个公告栏,我们可以包含以下输入:

{% schema %}
{
  "name": {
    "cs": "Panel oznámení",
    "da": "Meddelelseslinje",
    "de": "Ankündigungsbereich",
    "en": "Announcement bar"
  },
  "class": "homepage-section desktop-only",
  "settings": [
    {
      "type": "text",
      "id": "announcement-text",
      "label": "Text"
    },
    {
      "type": "color",
      "id": "announcement-text-color",
      "label": "Text color",
      "default": "#000000"
    }
  ]
}
{% endschema %}

通过引入settings和输入设置,我们已经成功创建了第一个部分,其文本和文本颜色值可以在主题编辑器中调整。如果我们尝试将其作为静态部分包含,我们之前创建的两个输入设置将立即在相应的部分下可见:

{% section "section-file-name" %}

然而,如果我们导航到主页并通过添加部分按钮包含公告部分,我们将找不到它。我们仍然缺少一个属性,我们需要创建一个可以动态添加到主页的部分。

预设属性

presets属性允许我们定义一个部分的默认配置,这使得部分可以通过presets属性访问,它可以包含以下属性:

  • name属性是强制的,它将定义部分在添加部分下拉菜单中的显示方式。

  • category属性不是强制的。我们可以使用它将不同的部分分组到单个类别下,以便更直观地导航。

注意,虽然 Shopify 没有严格要求,但建议为presets部分使用一个独特的名称以避免混淆。否则,我们可能会得到多个具有相似名称的部分:

{% schema %}
{
  "name": {
    "cs": "Panel oznámení",
    "da": "Meddelelseslinje",
    "de": "Ankündigungsbereich",
    "en": "Announcement bar"
  },
  "class": "homepage-section desktop-only",
  "settings": [
    {
      "type": "text",
      "id": "announcement-text",
      "label": "Text"
    },
    {
      "type": "color",
      "id": "announcement-text-color",
      "label": "Text color",
      "default": "#000000"
    }
  ],
  "presets": [
    {
      "name": "Announcement bar",
      "category": "Text"
    }
  ]
}
{% endschema %}

注意,我们只应该为动态部分包含presets属性。如果我们计划将其用作静态部分,我们应该删除presets属性。

如果我们现在打开主题编辑器并点击主页上的添加部分按钮,我们会看到公告栏部分,其中包含我们之前定义的文本和颜色设置。

访问特定部分的 JSON 输入值与访问settings_schema.json文件内的设置的方式相对类似。唯一的区别是,访问部分的settings对象的唯一方式是通过section对象:

{{ section.settings.announcement-text }}
{{ section.settings.announcement-text-color }}

关于section对象的更多信息,请参阅shopify.dev/api/liquid/objects/section

到目前为止,我们已经学习了什么是章节,静态章节和动态章节之间的区别,以及如何使用它们。然而,回顾我们已工作的公告章节项目,很明显整个章节相当基础,因为它只允许我们创建一个公告。

我们可以包含一些额外的文本输入选项,我们可以使用这些选项来创建多个公告,但这将需要我们每次需要包含额外的公告时都手动编辑 JSON 代码。如果我们想创建一个可以添加任意数量公告的章节,而不需要在每次需要包含额外的公告时都修改 JSON 代码,那会怎么样? 对于这个,我们可以使用blocks属性。

使用模块构建

blocks属性是 Shopify 中最强大的工具之一。通过使用它们,我们可以创建可以重复使用任意次数的模块,并从主题编辑器内部重新排序章节内容。然而,这可能会让人感到困惑,因为它听起来与我们刚刚学到的关于动态章节的内容相似。然而,关键的区别在于blocks章节允许我们在章节内部重新排序内容,而不是章节本身,这使得我们可以创建更复杂的功能。

此外,我们可以将blocks属性与静态章节结合使用,以创建类似于我们目前在主页上拥有的动态章节功能,并将其包含在任何页面上。然而,我们将重新排列的不是章节,而是模块。

blocks属性允许我们使用对象格式创建不同类型的模块,其中每个对象类型将作为一个独特的模块。在这里,我们可以在每个模块下包含一组不同的输入设置选项。

下面是创建和使用blocks模块的步骤:

  1. 让我们通过引入blocks属性来修改之前创建的公告栏章节。此外,我们将删除某些功能,如翻译键和class属性,以使代码保持简洁和可读:

    {% schema %}
    {
      "name": "Announcement Bar",
      "settings": [
        {
          "type": "text",
          "id": "announcement-text",
          "label": "Text"
        },
        {
          "type": "color",
          "id": "announcement-text-color",
          "label": "Text color",
          "default": "#000000"
        }
      ],
      blocks attribute has its own set of attributes that we need to use to create different modules:*   The `name` attribute allows us to set the name of the `blocks` module and decide how the block will appear in the theme editor. The `name` attribute is mandatory.*   The `type` attribute is a mandatory attribute that accepts a string value where we can define the block type. Note that the `type` attribute does not have a predefined set of values. Instead, we can use any string value to define the block type.*   Using the `limit` attribute, we can limit how many times we can repeat a particular block type. The `limit` attribute is optional and only accepts a `number` type value.*   The `settings` attribute allows us to include `blocks` module-specific settings. The `settings` attribute is optional.Note that the `name` and `type` attributes of the `blocks` module need to remain unique within the section, whereas the `id` attribute needs to remain unique within the `blocks` modules. Otherwise, we will end up with invalid JSON code.
    
  2. 现在,让我们学习如何在章节模式中包含之前提到的blocks模块属性。请注意,我们只展示blocks属性内部的代码,以保持代码简洁并突出重点:

    "blocks": 
    blocks module with the name attribute set to Announcement, the type attribute set to announcement, and the limit attribute set to 3, which limits the blocks module to a maximum of 3 repetitions.
    
  3. 现在我们已经设置了所有必要的属性,我们只需要将必要的输入设置填充到settings属性中。因为我们已经创建了文本和文本颜色输入设置,所以我们可以简单地将文本输入设置迁移到blocks模块内部:

    "blocks": [
    blocks module, we can dynamically repeat the entire block up to three times, consequently creating three separate announcements.
    
  4. 让我们通过导航到主题编辑器并点击settings对象来查看这看起来是什么样子:![Figure 7.03 – Example of a section inside the theme editor ![Figure 7.03_B17606.jpg 图 7.3 – 主题编辑器内章节的示例 由于我们只迁移了文本输入,而将文本颜色输入留在了section设置对象中,我们可以一次性使用它来设置所有块的文本颜色。让我们点击blocks模块左侧的箭头。 我们会注意到,除了添加部分按钮外,我们还在我们创建的公告栏部分下方有一个添加公告按钮: ![图 7.4 – 区块块模块的示例 图片

    图 7.4 – 区块块模块的示例

  5. 点击settings对象,点击blocks模块并立即定位到块内部。在这里,我们可以定义公告文本输入。

  6. blocks模块的对象设置中定义所有输入后,我们可以点击左侧的箭头。然而,请注意,由于我们引入了一个值为3limit属性,我们只能重复使用公告块最多 3 次,如下所示:

![图 7.5 – 有限数量的区块块模块示例图片

图 7.5 – 有限数量的区块块模块示例

一旦我们包含了定义的最大模块数,blocks模块。同样,与动态部分一样,悬停在blocks模块上会显示两个图标。这些图标将允许我们隐藏当前选定的块或使用拖放功能重新排列块的顺序。

现在我们已经学会了如何创建和使用blocks模块,我们需要学习如何输出blocks模块输入设置的值:

  1. 我们可以像访问section对象一样访问blocks模块的对象设置:我们需要使用一个section对象与blocks属性结合。这个section对象和blocks属性的组合将返回一个包含区块块的数组,我们可以使用简单的for循环来访问这些数组:

    {% for block in section.blocks %}
    {% endfor %}
    
  2. 一旦我们创建了for循环,剩下的唯一事情就是输出每个块的值,就像我们对部分所做的那样。唯一的区别是这次,我们将使用在for循环中定义的变量,而不是使用sections关键字:

    {% for block in section.blocks %}
      {{ block.settings.announcement-text }}
    {% endfor %}
    

到目前为止,我们已经学会了如何创建整个部分架构,通过它我们可以创建静态和动态部分,并构建blocks模块。我们还学会了如何输出sectionsblocks模块输入设置的值。然而,在先前的例子中,我们只有一种类型的块;如果我们有多个块类型会怎样呢?

区块块真正的力量在于我们可以在单个section元素内创建多个blocks模块类型,这可以通过简单地创建具有不同类型值的多个section块对象来实现:

"blocks": [
  {
    "name": "Product",
    "type": "product",
"settings": [
      {
        "type": "product",
        "id": "featured-product",
        "label": "Product"
      }
    ]
  },
  {
    "name": "Collection",
"type": "collection",
    "settings": [
      {
        "type": "collection",
        "id": "featured-collection",
        "label": "collection"
      }
    ]
  }
]

注意,所有nametype属性值在整个节区内都需要是唯一的,而输入设置的id属性只需要在单个块内是唯一的。

正如我们所见,创建多个blocks模块元素相对简单。虽然在我们的示例中我们只创建了两个简单的blocks模块,但我们可以创建任意数量的blocks模块,并通过主题编辑器重新排列以创建复杂的布局:

图 7.6 – 某节区内多个块模块的示例

图 7.6 – 某节区内多个块模块的示例

如我们从示例中可以看到,我们有四种不同类型的块(文本集合图片视频),我们可以使用这些块来创建一个店主可以轻松配置的布局,而无需自己修改代码。

之前,我们学习了如何通过使用section.blocks返回的for标签来遍历块数组。然而,如果我们有多个blocks模块类型,我们还需要引入一个额外的步骤。

假设,正如前面的示例中那样,我们有一个包含四种类型块的节。我们如何识别块类型并知道应该为哪个块输出哪种布局?为了解决这个问题,我们可以使用与type属性配对的block对象,这将允许我们恢复块类型值。

关于block对象的更多信息,请参阅shopify.dev/api/liquid/objects/block

一旦我们识别出每个块类型,我们就可以使用if语句或case/when控制流标签来执行相应值的正确代码。假设我们需要提醒自己如何使用语句或case/when控制标签;我们可以回顾第三章使用标签深入 Liquid 代码,并查阅控制 Liquid 流程部分,其中概述了使用控制流标签的过程:

{% for block in section.blocks %}
  {% case block.type %}
    {% when "collection" %}
      {% render "block-collection ", collection: block %}
    {% when "image" %}
      {% render "block-image", image: block %}
    {% when "video" %}
      {% render "block-video", video: block %}
  {% endcase %}
{% endfor %}

在前面的示例中,我们结合使用case/when控制流标签和block.type来识别我们当前正在遍历的块类型。在识别块类型后,我们渲染了包含相应块正确布局的片段文件。

注意,我们已经将block对象传递给每个片段。如您所回忆的那样,片段文件是唯一可以访问父目录中定义的变量的文件类型。然而,即使是片段文件也无法自动访问这些变量。相反,我们需要将这些值作为参数传递。

假设我们需要提醒自己如何将值传递给片段文件;我们可以回顾第三章使用标签深入 Liquid 代码,并查阅位于主题标签部分下的渲染标签部分,其中概述了处理片段文件的过程。

除了学习如何创建blocks模块外,我们还了解了limit属性,我们可以使用它来限制重复特定类型blocks模块的次数。当我们只有一个类型块时,这效果很好。但是,如果我们想创建一个限制,以便在某个部分内可以拥有任意组合的最大数量块呢?

max_blocks属性

max_blocks属性与limit属性类似,允许我们限制在某个部分内可以创建的blocks模块的数量。然而,它们有一个显著的区别:limit属性只允许我们限制重复特定块类型的次数,而max_blocks属性允许我们限制特定部分的最大块类型数量。

注意,max_blocks属性是可选的,并且只接受数字数据作为其值:

{% schema %}
{
  "name": "Footer",
  "max_blocks": 5,
  "settings": [
  ],
  "blocks": [
  ]
}
{% endschema %}

max_blocks属性最常见的使用是在页脚部分。有了它,店主可以轻松地以任何顺序重新排列块,无论是重复单个块五次还是使用五个不同的块,同时确保整个部分的布局保持适当的流程。

在此之前,我们学习了如何创建blocks模块,以及如何识别不同类型的块类型并相应地访问每个块对象。现在我们已经学会了如何使用max_blocks来限制某个部分内的产品数量,我们就拥有了构建任何类型blocks模块所需的所有必要知识。

正如我们所见,块功能非常强大,允许我们创建从基本文本功能到复杂布局功能的一切,这些功能与不同类型的块交织在一起,店主可以使用这些块来讲述他们产品的故事。

通过以上内容,我们已经了解了静态部分和动态部分之间的区别,静态部分是我们可以在任何页面上手动嵌入的,而动态部分则允许我们动态地向主页添加任意数量的部分。然而,正如我们在本章开头提到的,Shopify 最近为我们提供了通过 JSON 模板在任意页面上动态包含部分的方法。通过使用 JSON 模板,我们可以将静态和动态部分结合成一个新的功能,我们可以在主题编辑器内对其进行控制。

使用 JSON 模板增强页面

第一章《Shopify 入门》中,当我们讨论主题结构时,我们简要提到了 JSON 模板,但它们究竟是什么呢?

.json类型模板通常与其对应的.liquid模板具有相同的目的,因为它们都允许我们通过单个模板创建和管理多个页面的外观。然而,两者之间的显著区别在于,虽然.liquid类型模板仅作为标记文件,.json文件则作为数据类型文件,这使我们能够轻松地在任何页面上添加、删除或重新排列部分,类似于我们在主页上所能做的。

.json类型模板也与Section目录文件有相似之处,其中我们需要在部分文件中包含一个有效的模式设置。.json类型模板必须是一个有效的.json文件,模板内部定义了 JSON 代码。虽然我们可以创建任意数量的.json类型文件,但模板文件的名称必须在.liquid.json文件中是唯一的。例如,如果我们创建一个product.json文件模板,我们不能同时创建一个product.liquid文件。

此外,JSON 文件有一个限制。每个模板最多可以渲染 20 个部分,每个部分最多 16 个块,这是一个相当高的数字,但如果我们达到这个限制,我们可能需要重新考虑我们的页面布局。

现在我们已经对 JSON 类型模板是什么以及它们是如何工作的有了一些基本了解,让我们学习如何创建我们的第一个 JSON 类型文件。

构建 JSON 模板结构

而不是简单地列出创建文件所需的必要属性,我们将学习如何通过将当前的product.liquid类型模板迁移到 JSON 模板来创建一个 JSON 模板。

让我们从打开Templates目录并点击alternate开始创建一个 JSON 类型模板:

![图 7.7 – 创建新的 JSON 类型模板的示例图 7.07 – 示例:创建新的 JSON 类型模板

图 7.7 – 创建新的 JSON 类型模板的示例

一旦我们创建了一个新的 JSON 文件,Shopify 将自动将我们重定向到新创建的文件。我们将看到一个几乎为空、只包含两个属性的文件。因此,让我们开始创建必要的属性并开始我们的迁移过程。

正如我们之前提到的,JSON 模板必须是一个有效的 JSON 文件,其根是一个对象,可以包含以下一些属性:

  • name属性是一个必需的字符串类型属性,正如其名称所暗示的,我们可以定义模板的名称:

    {
      "name": "JSON product template"
    }
    
  • layout 属性是一个可选属性,它接受两种类型的值,一个字符串或 false,这取决于我们想要实现什么。字符串值应该表示我们想要与这个特定模板一起使用的布局文件的名称,不包括 .liquid 扩展名。如果您需要了解更多关于布局文件的信息,请回顾 第三章深入 Liquid 核心与标签,在那里我们学习了 主题标签 部分的 layout 标签。请注意,如果我们不包括 layout 属性,Shopify 将默认使用 theme.liquid 布局。然而,为了学习目的,我们将手动添加并选择 theme 布局:

    {
      "name": "JSON product template",
      "layout": "theme"
    }
    
  • wrapper 属性是一个字符串类型属性,它允许我们选择在模板内部每个节周围想要包含的 HTML 包装器的类型。我们在这里可以使用以下 HTML 标签:

    • div

    • main

    • section

    注意,除了选择 HTML 包装器的类型之外,我们还可以包含我们可能需要的任何其他属性,例如 classiddata。请注意,wrapper 属性完全是可选的:

    {
      "name": "JSON product template",
      "layout": "theme",
      "wrapper": "div.product-wrapper[data-type=product]"
    }
    
  • sections 属性是一个强制性的对象类型属性,它使用节的名称作为键,section 数据作为值。在 sections 对象内部,我们可以定义我们想要在页面内部包含哪些节:

    {
      "name": "JSON product template",
      "layout": "theme",
      "wrapper": "div.product-wrapper[data-type=product]",
      sections object allows us to include a mandatory static section, similar to our main page content, that is more page-specific. First, we will need to set a unique ID representing each static section that we are looking to include:
    
    

    {

    "name": "JSON 产品模板",

    "layout": "theme",

    "wrapper": "div.product-wrapper[data-type=product]",

    "sections": {

    sections 属性使用与之前我们了解过的 section 属性相同的格式,我们还需要在 sections 对象内部包含一些额外的属性。例如,我们需要包含 type 属性,其值应该是我们想要包含的节的名字,如果需要的话,还可以包含 settings 或 blocks 属性:

    {
      "name": "JSON product template",
      "layout": "theme",
      "wrapper": "div.product-wrapper[data-type=product]",
      "sections": {
        "main-block": {
          settings or blocks attribute should not contain the input type settings. Instead, we should set the key value to the ID of the existing input inside each section, as well as the value of the settings input value that we wish to set:
    
    

    {

    "name": "JSON 产品模板",

    "layout": "theme",

    "wrapper": "div.product-wrapper[data-type=product]",

    "sections": {

    "main-block": {

    "type": "name-of-the-section",

    "settings": {

    main 和 recommendations) 不是预设值,我们可以将它们的名称更改为我们喜欢的任何值。在处理 sections 对象时,我们需要牢记三个关键点:* 我们需要在 sections 对象内部至少设置一个块。* 所有块名称在整个 sections 对象中必须是唯一的。* type 属性的值需要与我们要包含的节的名字相匹配。通过在我们的新 JSON 模板中包含 mainrecommendations 节,我们已经确保了这两个节在预览分配了特定 JSON 类型模板的页面时,始终会在主题编辑器中可见。然而,尽管这些节被认为是静态的,我们仍然可以隐藏它们并重新排列,就像动态节一样。

    
    
    
    
  • 最后一个属性是order属性,这是一个必填属性。order属性是一个数组类型的属性。在这里,我们可以包括我们在sections对象内部之前设置的sections块 IDs 并对它们进行排列:

    {
      "name": "JSON product template",
      "layout": "theme",
      "wrapper": "div.product-wrapper[data-type=product]",
      "sections": {
        "main": {
          "type": "product-template"
        },
        "recommendations": {
          "type": "product-recommendations"
        }
      },
      "order": [
        "main",
        "recommendations"
      ]
    }
    

并且在order属性设置到位后,我们的 JSON 模板就准备好了!让我们导航到主题编辑器并测试一下。

要测试新的商品模板,请按照以下步骤操作:

  1. 导航到管理员仪表板的商品

  2. 点击任何商品,然后从模板后缀下拉菜单中选择新的模板名称,该下拉菜单位于主题模板区域下。

    重要提示:

    模板后缀下拉菜单只能读取当前实时主题中的值。这意味着新创建的模板文件在我们发布或复制它们为实时,或者在我们当前实时团队中创建相同的模板文件之前,将不会在我们的管理员仪表板中可见。如果我们选择后者,请注意,我们需要使用相同的名称创建文件;我们不需要对文件内容进行任何更改。

然而,使用这种新的模板类型,我们也有了一种新的预览模板的方法。请注意,以下方法只能让我们预览模板。我们仍然需要通过导航到管理员仪表板的商品页面,并在下拉菜单中选择模板来分配模板:

  1. 让我们从导航到主题编辑器开始,点击屏幕中间的下拉菜单,并选择商品选项。这将显示我们目前正在工作的主题下的模板。让我们通过点击它来选择我们创建的新 JSON 类型模板:图 7.8 – 在主题编辑器中选择模板的示例

    图 7.8 – 在主题编辑器中选择模板的示例

  2. 点击备用模板将自动将我们重定向到一个随机商品,允许我们预览我们选择的模板。

    重要提示:

    与主页上的动态部分类似,只有具有预设属性存在于sections架构中的部分将在添加部分下拉菜单下可见。

    如果我们一切操作正确,我们应该在左侧边栏中看到两个部分和添加部分按钮:

    图 7.9 – 在主题编辑器中预览 JSON 类型模板的示例

    图 7.9 – 在主题编辑器中预览 JSON 类型模板的示例

  3. 现在,尝试点击任何两个部分,更新任何设置,或者通过点击其名称旁边的X,通过我们创建的.json类型模板添加一个新部分,然后再次打开它。

    我们将注意到Shopify已更新模板文件,并且它现在包含所有设置及其值。

    由于我们可以在模板文件中找到所有设置及其值,如果我们将 JSON 模板文件分配给多个产品,它们将使用具有相同设置的布局。如果我们想创建一个额外的 JSON 模板,以便我们可以创建不同的布局,我们可以手动创建一个新的 JSON 模板并复制代码,或者通过主题编辑器完成此操作。

  4. 让我们回到主题编辑器,点击屏幕中间的下拉菜单,并选择产品选项。但这次,我们将点击创建模板按钮,这将导致以下弹出窗口出现:

![图 7.10 – 通过主题编辑器创建新 JSON 类型模板的示例

![img/Figure_7.10_B17606.jpg]

图 7.10 – 通过主题编辑器创建新 JSON 类型模板的示例

如我们所见,在弹出窗口中,我们可以轻松设置新的模板名称并选择我们应该复制内容的 JSON 模板。

有了这些,我们已经学会了如何为任何数量的页面创建任何数量的模板,但如果我们想为多个页面使用相同的布局,而只为每个页面使用不同的内容呢? 这就是元字段发挥作用的地方!

使用元字段升级 JSON 模板

如您可能从第四章,“使用对象深入液态核心”中回忆起,在浏览“使用元字段改进工作流程”部分时,我们提到 Shopify 引入了一个功能,允许我们使用元字段而无需依赖第三方应用程序。

除了允许我们在仪表板内创建元字段外,Shopify 还提供了一套全新的不同类型的元字段,我们可以创建:

  1. 首先,导航到仪表板中的设置选项,位于屏幕的左下角,并点击元字段选项。

  2. 进入后,我们会看到我们没有设置任何元字段,并且我们只能使用产品和变体元字段;其他字段仍在等待中。让我们通过点击产品元字段链接来继续操作。

  3. 考虑到我们目前没有设置任何元字段,我们可以立即点击添加定义按钮。这将带我们到一个可以创建元字段定义的页面。虽然大多数字段应该与您在第四章“使用对象深入液态核心”中学习元字段时熟悉,但我们现在有一个新的字段。让我们点击选择内容类型字段,这将显示一个包含我们可以创建的所有可用元字段类型的下拉菜单:![图 7.11 – 可用元字段类型字段的示例

    ![img/Figure_7.11_B17606.jpg]

    图 7.11 – 可用元字段类型字段的示例

    注意,每种类型的元字段都会在我们的产品页面仪表板内创建一个选择器。我们可以选择文本输入,这将自动显示一组额外的设置。然而,我们现在不会更改这些设置。

  4. 在选择元字段的文本类型后,我们只需要设置其名称。为了我们的目的,我们将通过点击名称字段并选择产品副标题来选择预定义的元字段值,这将自动填写所有其他字段。请注意,我们需要按下保存按钮来保存新创建的元字段:图 7.12 – 产品仪表板内元字段示例    图 7.12 – 产品仪表板内元字段示例

    图 7.12 – 产品仪表板内元字段示例

  5. 现在我们已经创建了一个产品元字段,让我们点击位于我们管理仪表板左上角的产品链接,并点击我们选择的任何产品。如果我们滚动到页面底部,我们会注意到我们创建的元字段现在已成为我们产品页面的一部分。

  6. 如我们所见,通过简单地创建产品元字段定义,我们已经自动将相同的元字段添加到每个产品中,这使得我们比使用第三方应用程序更容易更新元字段的值。让我们通过添加任何字符串值并按下保存按钮来更新元字段的值。

  7. 现在,返回到主题编辑器,导航到 JSON 模板,并找到我们可用的部分中的任何文本输入类型。为了我们的目的,我们添加了一个名为Image with text的新部分。

  8. 在查看本节时,我们会注意到特定类型字段旁边出现的新图标:![img/Figure_7.13_B17606.jpg]

    图 7.13 – 主题编辑器内元字段图标的示例

    点击标题文本输入旁边的底部图标会立即显示我们可以从该特定产品中提取的所有元字段列表。

  9. 选择我们之前选择的产品副标题元字段,并点击插入按钮。这将自动添加所选元字段的值,并在我们选择的输入字段中输出:

图 7.14 – 主题编辑器内元字段图标的示例

通过这种方式,我们已经学会了如何通过使用元字段动态更新部分设置的值,而不必为每个产品创建一个新的 JSON 模板。我们现在需要做的就是通过在 JSON 模板内排列必要的部分来创建适当的布局。

注意,通过使用元字段,我们可以更新所有类型的输入,如图片、URL 或甚至是颜色样本。然而,我们应该创建一个适当的元字段,以确保元字段值和输入类型值匹配。

如我们之前所见,仅使用部分就能非常强大。将部分与 JSON 模板和元字段混合使用,使其更加引人注目。这允许商家轻松地为每个页面配置不同的设置,而无需为每个页面创建新的模板或手动插入部分。

除了我们迄今为止所学的,我们还知道 Shopify 还提供了一些额外的特定于部分的标签,我们可以使用这些标签创建更强大的元素。

探索特定于部分的标签

虽然我们可以轻松地在主题的相关资产文件中定义样式或包含 JavaScript 代码,但 Shopify 提供了三种类型的标签,我们可以使用这些标签直接在部分文件中包含 CSS 和 JavaScript。

stylesheet 标签

schema 标签类似,stylesheet 标签是一个 Liquid 标签,它本身没有输出。它仅仅允许我们在部分文件内编写 CSS 代码。请注意,每个部分文件只能包含一个 stylesheet 标签,该标签必须独立存在。它不能嵌套在任何其他类型的 Liquid 标签内:

{% stylesheet %}
{% endstylesheet %}

虽然一开始这可能看起来不是一个好主意,因为我们最终会在多个文件中分散 CSS 代码,但 Shopify 会自动收集来自不同部分文件的所有 CSS 文件,将它们合并成一个样式表文件,并通过 content_for_header 全局对象将其注入主题文件。

重要提示:

stylesheet 标签仅接受 CSS 值。我们无法在标签内包含 Liquid 代码。

注意,捆绑的资产不是针对部分或块的。对于针对部分或块的样式表,我们需要使用 style 标签。

style 标签

style 标签与 stylesheet 标签类似,因为它允许我们直接在部分文件中编写 CSS 代码。然而,style 标签与 stylesheet 标签有两个显著的区别:

  • 第一个区别在于 style 标签等同于使用 HTML <style> 标签,这意味着 Shopify 不会收集和捆绑通过 style 标签包含的 CSS 代码。相反,Shopify 将在包含它的同一位置渲染 HTML style 标签及其内容:

    {% style %}
    {% endstyle %}
    
  • 第二个也是更重要的一点是,style 标签允许我们包含 Liquid 代码,我们可以使用主题编辑器动态地通过它来修改 CSS 内容:

    {% style %}
    .featured-wrapper { 
      background-color: {{ section.settings.background-    color }};
    }
    {% for block in section.blocks %}
      {% case block.type %}
        {% when "product" %}
          .featured-product {
            font-size: {{ block.settings.product-font-size }}          px;
          }
        {% when "collection" %}
          .featured-collection {
            font-size: {{ block.settings.collection-font-                size }}px;
    }
      {% endcase %}
    {% endfor %}
    {% endstyle %}
    

注意,与定义在 settings_schema.json 文件内的输入设置不同,其值可以在任何文件中全局访问,部分和块的输入值只能在部分文件本身或片段文件内访问,在我们将值作为参数传递之后。

在前面的示例中,我们看到了如何输出部分输入值并使用它们动态地修改 CSS 代码。然而,在我们的示例中,我们使用了静态类,这意味着我们应用到的background-color CSS 将影响每个部分和块。但如果我们想为每个部分或块应用不同的颜色呢?

要为特定元素应用独特的样式,我们需要使用sectionblock对象,结合id属性,创建一个我们可以稍后调用的唯一标识符:

<div class="featured-wrapper featured-wrapper-{{ section.id 
}}"></div>
{% for block in section.blocks %}
  <div class="featured-collection featured-collection-{{     block.id }}"></div>
{% endfor %}

注意,section.id将为动态部分返回一个动态生成的 ID 以及部分文件名(对于静态部分不包含 Liquid 扩展名)。block.id将始终返回一个动态生成的 ID。

现在我们已经生成了唯一的选择器,我们只需要在style标签内包含相同的选择器:

{% style %}
.featured-wrapper-{{ section.id }} {
  background-color: {{ section.settings.background-color}};
}
{% for block in section.blocks %}
  {% case block.type %}
    {% when "collection" %}
      .featured-collection-{{ block.id }} {
        font-size: {{ block.settings.collection-font-size                 }}px;
      }
  {% endcase %}
{% endfor %}
{% endstyle %}

现在我们已经知道了如何生成和调用唯一选择器,我们可以轻松地为部分和块元素创建不同的样式。请注意,我们还可以在 HTML 中使用style属性包含具有动态值的 CSS。

javascript标签

javascript标签的工作方式与stylesheet标签相同。该标签本身没有输出;它只是允许我们直接在部分文件内编写 JavaScript 代码。我们可以在一个部分中包含一个单独的javascript标签,该标签必须独立存在,并且不能嵌套在其他 Liquid 标签内:

{% javascript %}
{% endjavascript %}

类似地,与stylesheet标签一样,javascript标签只接受 JavaScript 代码。我们无法在标签内包含任何 Liquid 代码。此外,Shopify 将自动将javascript标签内的任何代码打包,并通过content_for_header全局对象将其注入主题文件。

由于我们无法在javascript标签内使用 Liquid 代码,一种应用特定部分 JavaScript 的方法是使用数据属性,这将输出特定的输入值,并在稍后使用 JavaScript 恢复该值:

<div class="rotating-announcement" data-speed="{{   section.settings.speed }}"></div>
{% javascript %}
  var rotationSpeed = parseInt(document.querySelector     ('.rotating-announcement').dataset.speed);
{% endjavascript %}

考虑到捆绑的资产不是针对部分或块的,如果我们需要创建一些针对部分或块的特定 JavaScript 代码,我们就需要使用 HTML <script>标签。请注意,目前 Shopify 没有接受 Liquid 代码的 Liquid 标签。

使用 HTML <script>标签,我们现在可以编写针对部分和块的特定 JavaScript,并直接包含输入设置值。然而,请注意,当直接在 JavaScript 中包含输入设置值时,应使用空白控制来确保不包含额外的空格:

{{- section.settings.speed -}}

如果你需要提醒自己如何使用空白控制,请重新阅读第二章Liquid 的基本流程,并查阅控制空白部分,其中我们概述了控制空白的过程。

摘要

在本章中,我们学习了如何将我们之前获得的基本和专用输入知识与部分架构相结合。这使得我们能够创建从简单到复杂的布局,我们可以在主题编辑器中轻松配置。

通过学习静态和动态部分之间的区别,我们学会了如何扩展部分的功能,使其在任何类型的页面上都易于访问。此外,除了学习如何创建不同类型的部分外,我们还获得了创建不同类型的blocks模块的知识,这将使我们能够重新排列单个部分的结构,使其易于配置。

最后,我们了解了我们可以用来进一步开发部分内容以及为特定部分或甚至blocks模块创建独特体验的不同类型的部分特定标签。

在下一章中,我们将了解 Shopify Ajax 是什么以及我们如何利用它来实现高级功能并使商店更加动态。

问题

  1. 静态部分和动态部分之间主要区别是什么?

  2. 我们可以使用什么对象来访问块输入值?编写一些代码,使我们能够访问特定的blocks模块输入值。

  3. limitmax_blocks属性之间的区别是什么?

  4. 我们如何应用特定部分的 CSS 样式?

熟能生巧

与前几章的项目一样,这个项目将包含关于我们需要做什么以及适当的说明,以帮助你实现结果。

我们建议独立于前几章的项目进行工作,因为这将确保你真正理解了你迄今为止所学的内容。

没有一个项目有正确或错误解决方案。然而,如果你有任何困难,你总是可以查阅项目解决方案,该解决方案可在本书末尾找到。

项目 4

对于我们的第四个项目,我们将致力于创建一个具有多种块类型的部分架构。虽然我们可以包括任何数量的块类型,但我们应确保包括以下类型:

  • 文本类型,将渲染单个富文本输入

  • 产品类型,将渲染一个包含产品名称、价格、图片和链接的单个产品元素

  • 列表类型,将在导航管理部分渲染一个可配置的一级导航列表

  • 视频类型,将渲染来自 YouTube 或 Vimeo 视频平台的视频

下面是关于资产的说明:

  1. 创建一个名为featured-content.liquid的新部分文件。

  2. 为每个部分创建一个单独的片段文件,同时将适当的对象传递给每个片段。

以下是为这项作业的说明:

  1. 包含我们需要的所有动态部分属性。

  2. 根据提供的规范创建四个模块。

  3. 将视频区块类型限制为最多重复两次,而其他所有区块应限制为仅出现一次。

  4. 任何时刻存在的区块总数不应超过四个区块。

  5. 应通过使用预定义的下拉值单独配置每个区块的宽度,并通过类应用所选宽度到每个区块,而不是直接应用值。这些值是 100%、50%、33.33%和 25%。

  6. 只在区块文件中包含区块或区块特定的 CSS。我们应该在主题的相关 CSS 文件中包含所有其他 CSS 样式。

  7. 在每个代码片段文件的区块设置部分定义以下输入设置:font-sizefont-family、文本颜色背景颜色。我们应该能够从 Shopify 的font库中选择字体家族。

  8. 在区块内创建一个文本输入元素,我们将使用它来定义整个区块的 H1 标题。如果输入字段为空,我们应该隐藏 h1 HTML 元素。

  9. 将整个项目迁移到.json页面类型模板。

第八章:第八章:探索 Shopify Ajax API

在前面的章节中,我们学习了 Shopify 和 Liquid 的基础知识,这为我们未来的开发提供了坚实的基础。在为我们的未来学习打下适当的基础后,我们学习了 Liquid 核心的工作原理。通过了解对象、标签和过滤器,我们学会了如何使用相对简单且不显眼的特性集创建复杂的功能。最后,我们学习了如何使用各种输入类型设置以及sectionsblocks属性,在商店中创建易于配置的元素。

在这些章节中,我们学习了如何使用静态内容创建元素。然而,如果我们想动态更新页面内容呢?这正是 Shopify Ajax API 发挥作用的地方。在本章的最后,我们将探讨 Shopify Ajax API,解释其要求和限制,以及可能的用例。

在本章中,我们将涵盖以下主题:

  • Shopify Ajax API 简介

  • 使用 POST 请求更新购物车会话

  • 使用 GET 请求获取数据

完成本章后,我们将了解 Shopify Ajax API 是什么,以及我们可以发起的类型请求,例如获取产品信息、将产品添加到购物车,甚至读取购物车当前的内容。此外,我们将通过改进一些我们的先前项目来学习 Shopify Ajax API 的典型用例。

最后,我们将学习如何根据 Shopify 的算法自动生成推荐产品列表,并将其转换为通常由店主要求的预测搜索功能。

技术要求

尽管我们将解释每个主题并配合相应的图形展示,但由于Shopify是一个托管服务,我们需要网络连接来跟随本章中概述的步骤。

本章的代码可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Chapter08

尽管本章将包含每个主题的多个真实示例和用例,但我们仍需要具备基本的 Ajax 理解和知识,以便彻底理解本章内容。

注意,我们只会展示与 Shopify API 相关的示例和项目,而不是通用的 Ajax。有关 Ajax 的详细信息,我们可以参考www.w3schools.com/js/js_ajax_intro.asp,它提供了 Ajax 的出色介绍。

本章的“代码实战”视频可在此找到:bit.ly/2VUZ7Qp

Shopify Ajax API 简介

Ajax,或异步 JavaScript 和 XML,是一种我们可以用来与服务器交换少量数据并更新任何页面的部分内容的方法,而无需重新加载整个页面。那么,Shopify Ajax API 究竟是什么呢?

Shopify Ajax API 是一个 REST API 端点,通过该端点我们可以发送请求来读取或更新某些信息。例如,我们可以使用GET请求来读取产品或甚至当前的购物车数据,或者我们可以使用POST请求来更新当前的内容会话。

Shopify Ajax 是一个未认证的 API,这意味着它不需要任何令牌或 API 密钥即可访问商店信息。Shopify 还为我们提供了一个名为Shopify Admin API的认证 API,应用程序和服务使用该 API 与 Shopify 服务器通信。

通过 Shopify API,我们可以访问我们大部分的商店数据,其响应将返回 JSON 格式的数据,尽管我们无法读取客户和订单数据或更新任何商店数据——我们只能使用 Shopify Admin API 来完成这些操作。

注意,Shopify 对 Ajax API 有一定的速率限制,以防止滥用(向 Shopify 服务器发送无限数量的请求)。其中一项限制是最大输入数组大小限制,目前限制为 250。假设我们正在查找有关超过 1,000 个产品的集合中所有产品的信息。由于我们每个查询最多只能限制为 250 个产品,因此我们将不得不使用多个查询来实现这一点。

小贴士:

为了保持简洁并切中要点,我们在此不会提及所有速率限制。有关 Ajax API 速率限制的更多信息,请参阅shopify.dev/api/usage/rate-limits

现在我们已经熟悉了需要了解的 Shopify Ajax API 基础知识,我们可以从实际的角度了解更多关于 Ajax API 的内容。

使用 POST 请求更新购物车会话

此前,我们提到可以使用 POST 请求来更新当前的购物车会话。根据我们想要执行的操作类型,我们可以将 POST 请求与以下购物车端点配对:

  • /cart/add.js

  • /cart/update.js

  • /cart/change.js

  • /cart/clear.js

虽然这听起来可能微不足道,但它是当今电子商务商店的一个基本方面,我们期望在不刷新整个页面的情况下执行操作。

/cart/add.js端点

如其名称所示,/cart/add.js端点允许我们添加一个或多个产品变体到购物车,而无需刷新购物车。要执行此操作,我们需要创建一个名为items的数组,其中包含一个对象,该对象包含以下两个键:

  • id键的值应包含我们要添加到购物车的变体 ID 的数字类型值。

  • quantity键的值应包含我们要添加到购物车的数量的数字类型值。

如果我们需要包含多个变体,我们可以在items数组内部简单地追加多个对象:

items: [
  {
    id: 40065085407386
  },
  {
    id: 40065085603994,
    quantity: 5
  }
]

在前面的例子中,我们可以看到一个包含两个对象的数组,这些对象包含一组不同的变体idquantity键。然而,请注意,第一个对象不包含quantity键。这是因为quantity键完全是可选的,如果我们没有包含它,它假定quantity的值等于1

让我们看看我们如何在现实生活中的例子中使用它:

  1. 如您所回忆的,在第四章使用对象深入液态核心,以及后来在第五章使用过滤器深入液态核心中,我们通过向集合模板添加额外的集合来开展了一个自定义集合项目。然而,当前的功能是,如果我们点击通过第四章使用对象深入液态核心,和第五章使用过滤器深入液态核心开发的自定义集合表单,我们可以在Snippet目录下的collection-form.liquid中找到自定义集合表单:

    {% if product.compare_at_price != blank %}
    <div class="custom-collection--item">
      <a href="{{ product.url }}">
        <img src="img/{{ product | img_url: "300x300" }}"/>
        <p class="h4 custom-collection--title">{{ 
            product.title }}</p>
        <p class="custom-collection--price">
          {{ product.price | money }}
          {% assign discount-price =            product.compare_at_price |                   minus: product.price %}
          <span>Save {{ discount-price | money }}</span>
        </p>
        <span class="custom-collection--sale-badge">{{       product.compare_at_price | minus: product.price |         times: 100 | divided_by: product.compare_at_price     }}%</span>
      </a>
      {% form "product", product %}
    <input type="hidden" name="id" value="{{ 
            product.first_available_variant.id }}" />
        <input type="submit" value="Add to Cart"/>
      {% endform %}
    </div>
    {% endif %}
    
  2. 如我们所见,我们已创建的集合表单已经包含了我们需要的两个必要元素:提交按钮和存储在隐藏输入中的变体id。为了更直接的导航,让我们首先将一个名为collection-submit的新类分配给提交按钮:

    {% form "product", product %}
      <input type="hidden" name="id" value="{{     product.first_available_variant.id }}" />
      <input type="submit" class="collection-submit"     value="Add to Cart"/>
    {% endform %}
    
  3. 在适当的选择器到位后,我们现在可以在提交按钮上使用addEventListener来捕获点击事件并将对象传递给我们将要创建的函数:

    const addSelector = document.querySelectorAll   (".collection-submit");
    if (addSelector.length) {
      for (let i = 0; i < addSelector.length; i++) {
        addSelector[i].addEventListener('click', function(e) { 
          e.preventDefault();
          addCart(this);
        });
      }
    }
    
  4. 在前面的例子中,我们创建了一个addSelector常量来捕获点击事件。使用preventDefault(),我们取消了任何当前事件的流程,并将点击元素的object传递给addCart函数。现在,让我们看看如何创建addCart函数:

    const addCart = (el) => {
      let formData = {
        'items': [
          {
            id: el.previousElementSibling.value
          }
        ]
      };
    }
    

    我们首先创建了一个带有el参数的箭头函数,我们将传递之前点击元素的object。在addCart函数内部,我们创建了一个局部变量,在其中我们分配了一个数组。这个数组包含一个对象,该对象包含我们想要添加到购物车的变量的id属性。

  5. 考虑到我们之前将点击的对象传递给了箭头函数,我们使用了previousElementSibling来选择正确的输入元素并相应地返回其值。现在我们已经准备好了所有必要的资产,我们只需要使用fetch请求将数据POST到 Shopify 服务器并更新当前的购物车会话:

    const addCart = (el) => {
      let formData = {
        'items': [
          {
            id: el.previousElementSibling.value
          }
        ]
      };
      fetch('/cart/add.js', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(formData)
      })
      .then(success => {
        console.log("Success:", success);
      })
      .catch((error) => {
        console.error('Error:', error);
      });
    }
    

有了这个,我们已经成功创建了一个完全功能的 Ajax API POST 请求,允许我们在不重新加载页面的情况下向当前的购物车会话添加任意数量的项目。此外,我们还包含了 then()catch() 方法,以便在控制台日志中返回 successerror 消息。

我们还学会了如何通过 /cart/add.js 端点将特定数量的产品添加到当前的购物车会话中。然而,如果我们想要将特定产品的某些行项目属性携带到购物车中,那会怎样呢?

我们可以通过简单地包含一个额外的参数 properties 来轻松解决这个问题,它接受一个键值类型的对象:

let formData = {
  'items': [
    {
      id: el.previousElementSibling.value,
      properties: {
        'Engraving message': 'Learning Liquid is fantastic!'
      }
    }
  ]
};

我们应该设置键,使其等于行项目输入的名称或行项目属性的第一个部分,其中值应该等于从输入或行项目属性的第二个部分检索到的值。假设我们需要回忆行项目属性是如何工作的。在这种情况下,我们可以回顾 第四章使用对象深入液态核心,其中在 产品定制 子主题中,位于 使用全局对象 部分中,我们解释了行项目属性是如何工作的。

如果我们需要传递一个仅在管理员订单部分可见的隐藏行项目,我们需要在键名后附加一个 下划线

let formData = {
  'items': [
    {
      id: el.previousElementSibling.value,
      properties: {
        '_Engraving message': 'Learning Liquid is fantastic!'
      }
    }
  ]
};

如果我们决定使用 jQuery,我们可以使代码更加紧凑:

jQuery.post('/cart/add.js', {
  items: [
    {
      quantity: 1,
      id: 40065085407386,
      properties: {
        '_Engraving message': 'Learning Liquid is fantastic!'
      }
    }
  ]
});

然而,我们应该检查我们正在工作的主题是否已经包含一个 jQuery 库。否则,我们应该避免向主题引入新的库。

通过涵盖 JavaScriptjQuery 解决方案,我们现在可以确信我们将能够使用我们的技能来生成必要的 Ajax API 代码。但是,如果我们不小心添加了比所需数量多得多的数量,我们需要更新产品的数量怎么办?

/cart/update.js 端点

正如其名称所暗示的,/cart/update.js 端点允许我们更新当前购物车会话中的行项目值。

虽然 /cart/update.js/cart/update.js 的工作方式类似,但有一些明显的区别。例如,在 /cart/add.js 中,当我们处理多个变体时,我们必须创建一个单独的对象,而与 /cart/update.js 不同,我们只需要创建一个单一的对象:

updates: {
    40065085407386: 5,
    40065085603994: 3
}

注意,在这里我们使用的是一组键值,而不是两组,其中键由变体 ID 表示,quantity 值代表键值。此外,我们现在使用的是更新,而不是项目。让我们创建一个函数来帮助我们测试我们的新知识:

const updateCart = (el) => {
  let formData = {
    updates: {
      [el.previousElementSibling.value]: 5
    }
  };
  fetch('/cart/update.js', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(formData)
  })
  .then(success => {
    console.log("Success:", success);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
}

正如我们所见,更新购物车会话的通用代码与添加产品购物车相似,这并不令人惊讶。除了更新购物车的内容外,/cart/update.js 还允许我们将产品添加到当前的购物车会话中。

通过使用 /cart/update.js,我们可以通过使用变体 ID 来识别我们想要更新的变体来轻松更新购物车中每个项目的数量。但如果我们想要更新的变体不在购物车中呢? 这就是 /cart/update.js 交替函数触发的地方,通过将产品变体添加到购物车中并选择数量来更新当前的购物车会话。

例如,在之前的 updateCart 函数中,我们将 quantity 值设置为静态值 5。无论我们调用前面的函数多少次,购物车中任何变体的总数量永远不会超过 5。因此,我们建议始终使用 /cart/update.js 来更新现有的购物车项目,并使用 /cart/add.js 来添加额外的项目到购物车。

这样,我们就已经学会了如何在当前的购物车会话中更新行项目。然而,正如你可能从 第四章 中回忆起来的,“使用对象深入液态核心”,在 产品定制 子主题中,位于 使用全局对象 部分中,我们了解到可以使用行项目实现不同类型的定制。因此,如果它们的定制不同,这将把相同的产品变体排序到不同的行中。

虽然这些产品可能位于不同的行上,但它们都将具有相同的变体 ID。那么,如果我们运行 /cart/update.js 来更新三个不同行上的特定变体会发生什么?

/cart/update.js 端点将成功执行其操作。然而,由于它不知道我们想要更新哪个行项目,它只会更新匹配变体 ID 的行项目的第一个出现,然后停止。它不会更新具有相同变体 ID 的任何其他出现。但如果我们想要更新特定的行项目而不是第一个出现呢?

/cart/change.js 端点

/cart/change.js 端点与 /cart/update.js 端点类似,因为它允许我们在当前的购物车会话中更新行项目。然而,有两个关键的区别:我们一次只能修改一个行项目,而且(更重要的是)我们可以指定我们想要更改的确切行项目。

/cart/add.js 端点类似,/cart/change.js 端点也使用一个包含两个键值对的对象——一个用于识别行项目,另一个用于分配所需的数量:

{
  'id': 40065085407386,
  'quantity': 7
}

在使用 id 和变体 ID 来识别行项目时,这不会引起任何错误,但这并不能解决我们的问题,因为我们购物车中可能会有多个具有相同变体 ID 的行项目。为了解决这个问题,我们可以使用 line 属性来识别我们想要更改的具体行项目:

{
  'line': 3,
  'quantity': 7
}

line值基于当前购物车会话中行项的索引位置,其中基本值从1开始。例如,如果我们购物车中有四个项目,并且我们正在尝试更新第三个位置的行项,我们可以将line值设置为3,正如我们之前的例子。请注意,/cart/change.js端点最常见的用途是轻松更新购物页面中每个行项的数量。

执行以下步骤以成功实现/cart/update.js端点:

  1. 正如我们之前提到的,要成功使用/cart/update.js端点,我们需要两样东西:quantity值,我们可以快速从我们修改的输入值中返回它,以及行项的当前位置。为了确定行项的位置,我们可以使用 JavaScript 的indexOf()方法。或者,如果我们想在 Liquid 的for循环中设置quantity输入的data属性并设置其值为forloop.index,我们可以采用第二种方法。在这里,我们将使用第二种方法来添加forloop.index作为data属性:

    <input type="number" name="quantity" value="0" data-  quantityItem="{{ forloop.index }}"/>
    
  2. 在确保我们已放置所有必要的属性后,我们只需要使用addEventListener来检测输入上的change事件,然后将对象传递给changeCart()箭头函数:

    const changeCart = (el) => {
      let formData = {
        line: el.dataset.quantityItem,
        quantity: el.value
      };
      fetch('/cart/change.js', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(formData)
      })
      .then(success => {
        console.log("Success:", success);
      })
      .catch((error) => {
        console.error('Error:', error);
      });
    }
    

changeCart()箭头函数与我们之前创建的函数类似。唯一的区别是,现在我们正在使用/cart/change.js端点,而不是使用静态值作为键值对。相反,我们从之前传递的对象中获取这两个值。

虽然我们可以使用/cart/update.js/cart/change.js通过将quantity值设置为0来简单地从购物车中移除项目,但我们必须手动调整每个line项的数量到0但如果我们想要一个简单的方法,通过单次点击就能清空整个购物车呢?

/cart/clear.js端点

与之前的端点相比,/cart/clear.js端点非常简单易用,因为它不接受任何参数。我们只需提交一个带有/cart/clear.js的 POST 请求,购物车就会自动清空所有现有项目:

const clearCart = () => {
  fetch('/cart/clear.js', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    }
  })
  .then(success => {
    console.log("Success:", success);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
}

注意,如果我们要在购物车中运行前面的代码,我们会成功清除购物车中的所有项目。然而,我们仍然需要刷新购物车页面才能看到变化,因为尽管我们已经从当前的购物车会话中清除了所有项目,但我们并没有从实际的 DOM 中移除这些项目。我们可以在success函数中实现一个短的while语句,并移除所有行项目元素以解决这个问题:

const cartItems = document.querySelector("[data-cart-line-  items]");
while (cartItems.firstChild) {
  cartItems.removeChild(cartItems.firstChild);
}

使用前面的代码示例,我们已经成功从当前的购物车会话和 DOM 中移除了所有项目。请注意,还有许多其他需要处理的微调方面,例如清除价格、移除购物车表格,并显示一条消息,说明购物车为空。然而,为了使这本书简洁明了,我们不会深入探讨这一点,但你可以自由(并且建议)根据需要升级前面的代码,因为这将只会对你有益。

到目前为止,我们已经学习了如何使用 /cart/add.js 端点向当前购物车会话添加产品,使用 /cart/update.js/cart/change.js 更新现有行项目,以及如何使用 /cart/clear.js 端点清除当前购物车会话。然而,正如我们所看到的,虽然我们可以轻松添加、更新或甚至清除当前购物车会话中的项目,但我们仍然需要重新加载页面才能看到特定的结果,例如更新位于页眉中 购物车 图标附近的项目 counter 或更新项目数量时的行项目价格。

当我们这样做时,我们可以快速简单地通过添加到购物车中的产品数量来增加项目 counter。实现这一点的更直接的方法是使用与 Shopify Ajax API 结合的 GET 请求,这将允许我们从 Shopify 服务器检索所有类型的数据,包括当前购物车会话中的产品数量。

使用 GET 请求检索数据

正如我们之前提到的,使用 GET 请求,我们可以从 Shopify 服务器获取所有类型的数据,除了客户和订单信息,这些信息只能通过认证的 Shopify Admin API 访问。根据我们想要执行的操作类型,我们可以将 GET 请求与以下端点配对:

  • /cart.js

  • /products/{product-handle}.js

  • /recommendations/products.json

  • /search/suggest.json

GET 请求是一种相当强大的方法,我们将经常与 POST 请求结合使用,在更改当前购物车会话后检索数据。然而,我们也可以使用 GET 请求来检索和创建复杂的功能,正如我们即将学习的。

/cart.js 端点

如其名称所示,/cart.js 端点允许我们访问当前的购物车会话并检索有关购物车以及购物车中产品的所有信息。我们可以使用它来动态更新购物车页面,甚至为商店创建购物车抽屉,从而显著改善购买流程。让我们看看:

  1. 我们可以使用以下 fetch 方法检索有关购物车的信息:

    fetch('/cart.js')
      .then(response => response.json())
      .then(data => {
        console.log(data);
    });
    

    注意,成功的 GET 请求的响应是一个 JSON 对象。以下示例显示了使用前面的代码获取购物车数据时我们将收到的响应:

    items array object, which we have minified to keep everything concise.
    
  2. 现在我们已经学会了如何检索当前购物车会话信息,我们可以将其与之前我们工作的 /cart/add.jsPOST 请求结合起来,并确保每次我们向购物车添加新产品时购物车计数器都能正确更新:

    fetch('/cart/add.js', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(formData)
    })
    .then(success => {
      console.log("Success:", success);
      fetch('/cart.js')
      .then(response => response.json())
      .then(data => {
        document.querySelector("[data-cart-        count]").innerHTML = data.item_count;
      });
    })
    .catch((error) => {
      console.error('Error:', error);
    });
    

    我们现在拥有从当前购物车会话中检索不同类型信息所需的所有知识。然而,请注意,在购物车响应中价格值是纯字符串,没有货币格式。

  3. 例如,假设我们想在更新产品数量时更新购物页面的总价格。首先,我们将使用 fetch 方法检索总价格值:

    fetch('/cart.js')
      .then(response => response.json())
      .then(data => {
        console.log(data.total_price);
    });
    

    虽然我们成功检索到了价格,但我们收到的只是一个未格式化的字符串值,这对我们来说并不那么有用:

    78594
    
  4. 解决这个问题最简单的方法是查看主题开发者如何在主题中定义货币格式化辅助函数。我们通常可以在主题的 master js 文件中找到它。在我们的例子中,这将是一个 theme.js 文件。

    在确定我们需要的关键词后,我们只需将格式应用到我们想要格式化的值上:

    fetch('/cart.js')
      .then(response => response.json())
      .then(data => {
        console.log(theme.Currency.formatMoney(        cart.total_price, theme.moneyFormat));
    });
    

注意,我们在前面的示例中使用过的格式在大多数情况下无需修改即可正常工作。然而,我们可能需要根据特定的主题进行调整——这完全取决于主题开发者如何定义该函数。

之前,我们学习了如何检索当前购物车会话的信息以及购物车中任何产品的数据。然而,如果我们想更直接地检索产品信息呢?

/products/{product-handle}.js 端点

/products/{product-handle}.js 端点是一个简单的端点,我们可以将其与 GET 请求结合使用,轻松检索商店中任何产品的信息。同样,与 /cart.js 端点一样,/products/{product-handle}.js 相对容易使用,因为它只需要我们包含我们想要检索数据的产品的处理程序:

const getProduct = (handle) => {
  fetch(`/products/${handle}.js`)
  .then(response => response.json())
  .then(product => {
    console.log(product.id);
  });
}

从前面的示例中我们将收到的返回值将包括产品 ID,我们将在下面的示例中使用它。

这个端点最常用的用途是在创建点击功能时,例如快速查看功能,我们需要动态加载大量产品信息以避免 DOM 杂乱无章并减慢商店速度。

/recommendations/products.json 端点

/recommendations/products.json 端点允许我们根据 Shopify 算法检索关于所选产品的推荐产品列表的 JSON 对象,我们可以使用这些对象构建动态推荐部分。

通过这个端点,我们可以使用三个参数,其中一个是必需的,而其他两个是可选的:

  • product_id参数是一个必填参数,其值应设置为我们要检索推荐列表的产品 ID。请注意,产品 ID 与变体 ID 不同,它们是两个不同的属性,我们可以通过product对象来获取。

  • limit参数是一个可选参数,允许我们选择每次请求应接收的最大推荐产品数量。由于 Shopify 的限制,我们每次请求不能检索超过 10 个推荐产品。如果我们没有设置limit参数,这将默认值。

  • 最后但同样重要的是section_id参数,虽然它是可选的,但这个参数相当有趣,因为它允许我们更改我们将接收的响应类型。通过将部分 ID 作为section_id参数值包含,我们可以选择我们想要渲染推荐产品的父元素。更重要的是,我们还可以将 JSON 响应更改为 HTML 字符串,然后我们可以将其与recommendations对象结合使用,以动态输出推荐产品。

现在我们已经熟悉了所有可以与/recommendations/products.json端点一起使用的属性,是时候看看它们在实际中的应用了。

在下面的示例中,我们使用了一个fetch请求,配合/recommendations/products.json端点,生成一个 JSON 对象列表并在控制台日志中输出:

const productRecommendations = (productId, limit) => {
fetch(`/recommendations/products.json?product_id=  ${productId}&limit=${limit}`)
  .then(response => response.json())
  .then(products => {
    console.log(products);
  });
}

如我们所见,检索推荐产品的 JSON 对象相当简单,因为我们现在需要做的只是将产品 ID 传递给productId参数。如您所回忆,limit参数是可选的,如果不包含,将默认为最大值10。现在,让我们看看如何包含section_id并学习如何检索 HTML 字符串。

在我们能够修改fetch请求以实现这一点之前,我们需要做一些具体的准备。我们首先需要做的是在Sections目录中创建一个新的部分。在我们的例子中,我们将命名为recommended-products

由于我们在产品页面模板上已经有了推荐产品部分,现在只是创建一个新的部分来了解它是如何工作的,所以让我们将这个新部分包含在theme.liquid布局文件的底部,正好在</body>标签之上。现在我们已经创建了部分文件并成功包含它,我们必须熟悉recommendations对象。

如其名所示,recommendations对象允许我们从产品推荐列表中检索产品。然而,这个特定的对象仅与/recommendations/products端点结合使用。

如我们所见,recommendations对象相对简单易用,因为它只包含三个属性:

  • performed属性返回一个布尔值,取决于我们是否将recommendations对象放置在通过组合recommendations端点和必要的参数而渲染内容的部分中。

  • products_count属性为我们提供了推荐列表中产品数量的数值。

  • 最后但同样重要的是,products属性允许我们检索推荐产品对象的数组。我们可以将products属性与for标签结合使用,以提供与我们在第五章,“使用过滤器深入液态核心”的Custom collection项目中相同的方式提供输出。

让我们回到我们创建的recommended-products部分文件,并使用recommendations对象输出推荐产品数组:

<div class="product-recommendations">
  {% if recommendations.performed %}
    {% if recommendations.products_count > 0 %}
      {% for product in recommendations.products %}
        <div class="product">
          <a href="{{ product.url }}">
            <img class="product__img" src="img/{{             product.featured_image | img_url: '300x300' }}" 
            alt="{{ product.featured_image.alt }}" />
            <p class="product__title">{{ product.title }}</p>
            <p class="product__price">{{ product.price |                 money}}</p>
          </a>
        </div>
      {% endfor %}
    {% endif %}
  {% endif %}
</div>

这样,我们就为未来的推荐列表创建了一个合适的布局。然而,如果我们预览主题上的结果,我们会注意到该部分在product-recommendationsdiv 内没有渲染任何内容。正如我们之前提到的,recommendations对象仅与recommendations端点结合使用,所以让我们看看如何使用端点生成必要的 HTML 字符串以输出推荐产品列表。

为了实现这一点,我们需要对我们的之前的fetch请求做一些调整:

  1. 我们需要做的第一件事是为productRecommendations函数包含额外的参数,我们将传递部分 ID 值。此外,我们还需要将section_id参数及其值包含到 fetch URL 中。

  2. 第二个且更为重要的步骤是移除 fetch URL 中的.json。否则,我们将无法检索到 JSON HTML 字符串。

  3. 最后但同样重要的是,我们需要在第一个then方法中将.json()替换为.text()

到目前为止,我们已经拥有了检索 JSON HTML 字符串所需的所有必要元素。让我们通过在控制台日志中调用产品来测试它:

const productRecommendations = (productId, limit,   sectionId) => {
fetch(`/recommendations/products?product_id=${productId}  &limit=${limit}&section_id=${sectionId}`)
  .then(response => response.text())
  .then(products => {
    if (products.length > 0) {
      console.log(products);
    }
  });
}

然而,在我们能够测试之前,我们需要将三个值传递给我们的函数:

  • 对于productId,我们可以使用我们在学习/products/{product-handle}.js端点时检索到的产品 ID 值。或者,我们可以在任何产品模板中使用product.id并复制我们收到的值。

  • 对于limit,我们可以使用任何不超过10的数字值,这是我们可以作为响应接收的最大产品数量。

  • 对于sectionId,我们应该包含一个等于我们想要在内部显示推荐产品的部分名称的字符串值。在我们的例子中,值是recommended-products

以下是将所有三个值传递给我们的函数的示例:

productRecommendations(6796616663194, 3, "recommended-  products");

如果我们要预览我们的重复主题并检查之前fetch函数中的控制台日志,我们会看到我们已经成功检索到了推荐产品的 JSON HTML 字符串值。

现在我们已经确认一切正常,我们唯一要做的就是使用检索到的值并输出推荐产品列表:

const productRecommendations = (productId, limit,   sectionId) => {
fetch(`/recommendations/products?product_id=${productId}  &limit=${limit}&section_id=${sectionId}`)
  .then(response => response.text())
  .then(products => {
    if (products.length > 0) {
      document.querySelector(".product-          recommendations").innerHTML = products;
    }
  });
}

有了这些,我们已经成功学习了如何根据我们在recommended-products部分之前定义的布局渲染推荐产品列表。此外,产品列表将根据 Shopify 的算法自动更新。

虽然有一个推荐的产品列表是一个很好的功能,用于查找类似的产品,但我们仍然需要导航到特定的产品,即使如此,我们也不能确定我们会收到我们需要的确切结果。为了帮助我们,我们可以使用预测搜索功能。

/search/suggest.json端点

如其名所示,/search/suggest.json端点允许我们创建一个预测搜索,该搜索将自动为我们提供一个与我们的查询部分或完全匹配的产品列表。

除了允许我们在产品上使用预测搜索外,我们还可以根据我们包含的参数类型搜索集合、页面甚至文章。/search/suggest.json允许我们使用七种不同的参数类型。然而,为了保持简洁并切中要点,我们只会介绍使预测搜索功能正常工作所需的最重要的一些参数:

  • 我们列表中的第一个参数是q参数,这是一个必填的字符串类型参数,其值应等于搜索查询。

  • type参数允许我们指定我们想要接收的结果类型。我们可以包含以下以逗号分隔的值:productpagearticlecollectiontype参数也是必填的。

  • limit参数是一个可选的整数参数,允许我们设置每次请求应接收的结果数量。请注意,如果我们不包括limit属性,它默认为10,这是我们每次请求可以接收的最大结果数量。

  • resources属性是一个必填的哈希类型参数,它根据typelimit字段请求查询的资源结果。

在以下示例中,我们使用了一个fetch请求,配合/search/suggest.json端点,生成一个与我们的搜索查询匹配的 JSON 对象列表,并在控制台日志中输出:

const predictiveSearch = (query, limit, type) => {
fetch(`/search/suggest.json?q=${query}&resources[type]=  ${type}&resources[limit]=${limit}`)
  .then(response => response.json())
  .then(suggestions => {
    const productSuggestions =         suggestions.resources.results.products;
    if (productSuggestions.length > 0) {
      console.log(productSuggestions);
    }
  });
}

如我们所见,根据搜索查询检索预测搜索结果相对简单,因为我们现在唯一需要做的是将所需的值传递给我们的函数:

const searchSelector = document.querySelectorAll     (".search-bar__input");
if (searchSelector.length) {
  for (let i = 0; i < searchSelector.length; i++) {
    searchSelector[i].addEventListener('input',       function(e){
      e.preventDefault();
      predictiveSearch(this.value, 4,           "product,page,article,collection");
    });
  }
}

在搜索框内输入搜索查询后,我们会注意到我们已经在控制台日志中成功检索到了最多四个产品、页面、文章或 JSON 对象的集合,这些集合部分或完全符合我们的搜索查询。现在我们唯一要做的就是使用响应值在 DOM 内生成结果。

使用 /products/{product-handle}.js 端点,我们有一个参数允许我们检索一个 JSON HTML 字符串,以便轻松将其输出到 DOM 中。然而,对于 /search/suggest.json 端点来说并非如此;为了渲染这些结果,我们需要使用 JavaScript 来创建所需的布局和功能。为了保持简洁并直击要点,我们不会在本书中涵盖这一点。然而,我们建议完成这个项目,因为它将是一个极好的实践,将帮助你巩固迄今为止所学的一切。

如需有关预测搜索参数及其一般要求和限制的更多信息,请参阅shopify.dev/api/ajax/reference/predictive-search

摘要

在本章的最后,我们熟悉了 Shopify Ajax API,并了解了不同类型的用例。首先,我们学习了如何使用 /cart/add.js 端点升级当前的购买流程,通过这个端点,我们可以将任何数量的产品、数量和行项目定制直接添加到当前的购物车会话中。

通过学习如何处理 /cart/change.js 端点,我们获得了创建包含特定产品和数量的功能所需的知识,例如自动赠品或升级销售功能。结合使用 /cart/update.js/cart.js 端点,我们学会了如何动态更新购物车的内容并检索它。然后我们可以利用这一点来创建购物车抽屉功能。

此外,我们还学会了如何使用 /products/{product-handle}.js 端点检索推荐产品的自动列表,并将它们的内容渲染到我们选择的区域。

最后,我们了解了 /search/suggest.json 端点,它允许我们创建预测搜索功能,这是店主们最常请求的功能之一。

从这本书的最初开始,我们就一起努力拓展我们的知识边界,并建立了一个坚实的理解流程,这将帮助我们成为 Shopify 专家的道路上。虽然我们没有覆盖每一行 Liquid 代码,但我们参与了一些令人兴奋的项目,从中我们学到了很多更有益的知识。我们的目标不仅仅是创建一个列表,列出所有不同的方法和属性,这些我们总是可以通过查阅 Shopify 文档找到,而是要学习 Shopify 和 Liquid 如何工作。

虽然可以说,通过在这里获得的知识,我们应该准备好独立开始 Shopify 主题的开发工作,但请注意,我们的冒险之旅并未结束——它才刚刚开始。

Shopify 是一个不断发展的平台,我们需要跟上所有最新的公告和策略。幸运的是,Shopify 提供了各种社区,以进一步丰富我们的知识或从其他 Shopify 专家那里获得各种主题的帮助。最后但同样重要的是,我们有一个可用的 Discord 频道,我们可以与其他开发者交谈,在我们需要时获得帮助,或者与开发者分享我们的知识:discord.gg/shopifydevs.

进一步阅读

第九章:评估

第一章,Shopify 入门

问题 1

合作伙伴计划是什么?

答案

合作伙伴计划是由 Shopify 创建的一个平台,汇集了来自世界各地的人们。通过这个平台,我们可以为店主构建新的电子商务商店,设计主题,开发应用程序,向 Shopify 推荐新客户,最重要的是,为我们自己创建一个用于练习的开发商店。我们可以在“如何开始?”主题中提醒自己这一点。

问题 2

我们如何禁用开发类型商店的密码保护?

答案

我们可以通过点击位于“主题”部分横幅中的查看存储密码按钮,或者在“在线商店”部分下选择在线商店并随后点击展开下拉菜单中的偏好设置来禁用我们的密码保护存储。一旦进入,我们可以轻松地移除常规商店的密码保护。然而,由于我们的商店处于开发模式,此选项目前已被禁用。我们可以在“理解主题结构”主题下的侧边栏子主题中提醒自己这一点。

问题 3

布局和模板目录文件有什么区别?

答案

布局目录是我们主题的主要目录。它包含基本文件,并且是所有其他文件(包括模板文件)将渲染的地方。模板文件是一组文件,允许我们一次性轻松创建和管理多个页面的外观。我们可以在“理解主题结构”主题下的侧边栏子主题中提醒自己这一点。

问题 4

在什么情况下,新的模板文件将在你页面的管理员部分可见?

答案

考虑到 Shopify 的管理端只能从当前发布的主题中读取值,我们必须满足两个条件。除了创建一个新的模板文件外,我们还需要确保相同的模板文件存在于当前正在运行的主题中,或者发布我们的副本主题。我们可以在“理解主题结构”主题下的模板子主题中提醒自己这一点。

问题 5

什么类型的文件以及在什么条件下允许我们访问父文件作用域内的变量?

答案

片段文件允许我们通过引用它们的名称在模板/部分中重用重复的代码片段。除了允许我们重用代码的部分外,片段还将使我们能够访问作为参数传递给片段的变量所在的父元素内的变量。

第二章,Liquid 的基本流程

问题 1

如果我们期望输出结果,我们应该使用哪种分隔符?

答案

如果我们期望从 Liquid 代码中获取输出,我们应该使用双括号分隔符,因为我们只有在执行特定逻辑时才应该使用带百分比的括号。我们可以在“理解 Liquid 及其分隔符”部分提醒自己。

问题 2

以下条件语句的结果是什么,为什么?

{% if collection.all_products_count > "20" %}
  The number of products in a collection is greater 
   than 20!
{% endif %}

答案

考虑到collection.all_products_count默认返回一个数字作为其值,而我们比较的值是一个字符串,因为它被括号封装。由于我们不能比较不同类型的数据,条件语句将返回false,我们的消息将不会显示。我们可以在“学习比较运算符”部分提醒自己。

问题 3

访问数组内部元素有两种方法是什么?

答案

我们可以使用两种方法来访问数组内部的元素。第一种方法允许我们使用一个项目的索引位置来恢复我们正在寻找的确切项目,而第二种方法允许我们遍历数组中的所有项目。我们可以在“Array子节”中提醒自己,在“理解数据类型”部分。

问题 4

使用句柄访问对象的方法是什么?

答案

我们可以通过将我们要访问的对象的名字复数化,然后跟一个方括号([])或点(.)表示法来通过其句柄访问对象。这两种访问对象的方法都是正确的。然而,它们各自有其用途。我们可以在“EmptyDrop子节”中提醒自己,在“理解数据类型”部分。

问题 5

以下代码块中存在两个问题是什么?

{% if customer != nil %}
  Welcome {{- customer.name -}} !
{% endif %}

答案

由于 nil 是一个特殊的数据类型,它返回一个空值,因此它没有可视化的表示,这是第一个问题。我们可以在“Nil子节”中提醒自己,在“理解数据类型”部分。第二个问题是我们在customer.name输出的两侧都添加了一个连字符。虽然输出右侧的连字符会清除感叹号前的多余空格,但我们也在左侧添加了一个连字符,移除了“Welcome”这个词和我们的客户名字之间的空格。我们可以在“控制空格”部分提醒自己。

第三章,深入 Liquid 核心与标签

问题 1

如果我们想在循环中显示最多七次迭代,同时跳过前三次迭代,我们应该在for循环中使用哪些参数?

答案

如果我们想要创建一个循环,跳过前三次迭代并输出最多七次迭代,我们应该使用 offsetlimit 参数的组合。offset 标签将允许我们跳过任何数量的迭代,具体取决于我们分配给它的值。limit 参数将允许我们限制标签应执行的迭代次数。我们可以通过重新查看 Iterations tags 部分的 for parameters 子部分来提醒自己这一点。

问题 2

我们可以使用 capture 标签创建的变量分配哪些类型的数据?

答案

我们可以将任何类型的数据分配给使用 capture 标签创建的变量。然而,使用 capture 标签创建的变量将始终返回字符串数据作为结果。我们可以通过重新查看 Variable tags 部分的 capture 子部分来提醒自己这一点。

问题 3

在以下代码块中存在两个问题是什么?

liquid for product in collections["outdoor"].products
  if product.price > 10000
    continue
  else
    product.title
  endif
endfor

答案

虽然使用 liquid 标签允许我们在代码块中消除花括号分隔符,但我们不应该从 liquid 标签中移除它们。我们应该在 liquid 标签的左侧放置一个带有百分号的开括号分隔符。我们代码的第二个问题是我们在 product.title 前缺少一个 echo 标签,它替换了双大括号分隔符。我们可以通过重新查看 Theme tags 部分的 The liquid and echo tags 子部分来提醒自己这一点。

问题 4

我们应该采取什么方法来修改由 HTML 生成的产品表单,通过替换现有的类属性为一个字符串和变量的组合?

答案

考虑到 form 标签不接受字符串和变量的组合作为其参数,我们应该首先使用 capture 标签将这些值分配给一个变量,然后将其传递给 form 标签。我们可以通过重新查看 Theme tags 部分的 form 子部分来提醒自己这一点。

问题 5

如果我们想从父元素传递一个对象,我们应该使用哪个参数?

答案

唯一允许我们从父元素传递对象的标签是 render 标签,即使如此,我们也只能通过使用 withas 参数来实现。我们应该将 with 参数的值设置为我们要传递的对象,而 as 参数的值应该是我们在片段文件中将使用的变量名。我们可以通过重新查看 Theme tags 部分的 render 子部分来提醒自己这一点。

第四章,使用对象深入液态核心

问题 1

在以下代码块中,我们缺少什么来使 form 功能化?

{% form "product", product %}
  <input type="hidden" value="{{ 
   product.first_available_variant.id }}" />
    <input type="submit" value="Add to Cart"/>
{% endform %}

答案

虽然我们已经介绍了必要的 id 变体来创建一个工作的产品表单,但我们没有使用 name 属性,其值为 id。我们可以通过回到 自定义集合 子主题,在 与全局对象一起工作 部分来提醒自己这是如何工作的。

第二个问题

我们如何通过在管理导航中定义的链接来获取访问 product 对象的权限?

答案

要通过导航菜单访问 product 对象,我们需要使用一个 for 标签来遍历导航菜单。一旦我们找到了哪个菜单项是 product_type,我们就可以使用那个菜单项,然后是 object 属性,来访问那个特定的 product 对象。我们可以通过回到 自定义导航 子主题,在 与全局对象一起工作 部分来提醒自己这是如何工作的。

第三个问题

访问单个和多个 metafield 对象的两种方法是什么?

答案

我们可以通过访问我们想要恢复 metafield 的页面对象,然后是 metafields 对象,然后是 namespace,最后是 key 来访问单个 metafield 对象。如果我们想要恢复多个 metafields 对象,我们需要使用一个 for 标签来遍历所有具有其命名空间的元字段。我们可以通过回到 使用元字段改进工作流程 部分来提醒自己这是如何工作的。

第四个问题

如果我们想要捕获 line_item 值并在结账页面上隐藏它,我们需要对输入元素进行什么调整?

<input type="text" name="properties[Your Name]" placeholder="Your Name"/>

答案

如果我们想要捕获 line_item 值,我们需要在方括号内引入一个下划线作为第一个字符。通过在 line_item 输入中引入这个下划线,我们将自动隐藏特定的 line_item,使其不在结账页面上显示。然而,这不会在购物车页面上隐藏它。购物车页面需要一些手动调整。我们可以通过回到 自定义导航 子主题,在 与全局对象一起工作 部分来提醒自己这是如何工作的。

第五章,使用过滤器深入液态核心

第一个问题

假设我们有一个名为 product_handles 的数组,包含 30 个产品的句柄。以下代码中哪个问题会阻止我们成功输出所有 30 个产品的图片?

{% for handle in product_handles %}
  {% assign product_object = all_products[handle] %}
  {% for image_item in product_object.images %}
    <img src="img/{{ image_item | img_url }}"/>
  {% endfor %}
{% endfor %}

答案

由于我们想要输出超过 20 个产品,在这种情况下,30 个产品,我们不能使用 all_products 对象,因为 all_products 对象有一个限制,我们只能在单页上调用它 20 次。如果我们想要从超过 20 个产品中恢复数据,我们需要将它们分配到一个集合中,然后对这些产品进行循环。我们可以通过访问 使用 HTML 和 URL 过滤器一起工作 部分来提醒自己 all_products 对象。

第二个问题

为什么在创建产品媒体库时不推荐仅使用 model_viewer_tag 标签?

{% for media in product_object.media %}
  {% case media.media_type %}
    {{ media | model_viewer_tag }}
  {% endcase %}
{% endfor %}

答案

虽然model_viewer_tag将正确输出每种媒体类型的必要 HTML 媒体标签,但我们应仅在所有其他媒体标签无法渲染正确标签时才使用model_viewer_tag作为后备。使用model_viewer_tag将阻止我们包括每个media标签的任何特定参数。我们可以通过回顾本章中我们完成的一个先前项目,增强产品媒体库,来提醒自己media对象。

问题 3

如果我们想要在数组中访问特定位置的项,我们可以使用哪个过滤器?

答案

如果我们想要访问特定位置的项,我们需要使用一个名为index的数组类型过滤器。使用index过滤器,我们可以访问数组中的指定索引位置并返回其值。我们可以通过回顾本章中我们完成的一个先前项目,产品折叠面板,来提醒自己索引参数。

问题 4

我们可以使用哪个过滤器来快速更新主题文件中字符串值的任何出现?

答案

为了轻松更新字符串值的任何出现,我们需要使用 t(翻译)过滤器。通过定义翻译键,我们可以快速更新或甚至翻译任何字符串值,而无需手动更新多个文件中的硬编码字符串值。

第六章,配置主题设置

问题 1

输入设置有两种类型吗?

答案

第一组设置被称为基本输入类型,它由六种设置组成,使我们能够输出基本的 HTML 输入元素,通过这些元素我们可以动态地输出某些内容。第二组设置,也称为专用设置,使我们能够生成专用选择器类型字段,通过存储访问各种对象,并使用它们的属性输出其内容。我们可以通过访问第六章中的基本输入类型专用输入设置部分来提醒自己设置的基本和专用输入类型。

问题 2

以下代码片段将导致错误的问题是什么?

{
  "type": "text",
  "id": "header_announcement",
  "label": "Text",
}

答案

虽然代码结构是正确的,但我们不小心在文本设置类型中的最后一个属性后面多加了一个逗号,这将导致 JSON 错误并阻止我们保存更改。我们可以通过访问第六章中的基本 JSON 设置部分来提醒自己严格的 JSON 格式。

问题 3

我们如何在 Shopify 中包含一个自定义字体文件并在主题编辑器中使用它?

答案

虽然font_picker允许我们在 Shopify 中访问大量字体,但我们无法将自定义字体包含在这个库中。要包含一组自定义字体,我们必须使用设置中的select输入类型,在那里我们可以手动创建一个我们希望包含的字体列表。我们可以通过访问选择输入子部分来提醒自己如何包含自定义字体,该子部分位于基本输入类型部分中的第六章配置主题设置

问题 4

哪两个问题会阻止我们执行以下代码片段?

{
  "type": "range",
  "id": "number_of_products",
  "min": 110,
  "max": 220,
  "step": 1,
  "unit": "pro",
  "label": "Number of products",
  "default": 235
}

答案

考虑到每个range滑块最多可以有 100 个步骤,第一个问题是minmax属性值相差太远,我们可以通过减少这两个值中的一个来解决,这样它们就不会超过 100 个步骤。另一种解决方案是将step增加到更高的值,从而减少minmax值之间的步骤数。

第二个问题是default属性值目前超过了max属性值。我们可以通过减少default值或增加max属性值来解决这个问题。在确保default属性值不超过max值之后,我们还需要确保default值也高于min属性值。我们可以通过访问范围输入子部分来提醒自己range类型设置格式,该子部分位于基本输入类型部分中的第六章配置主题设置

第七章,处理静态和动态部分

问题 1

静态部分和动态部分的主要区别是什么?

答案

静态部分和动态部分的主要区别在于,我们只能通过主题编辑器内的添加部分按钮将动态部分添加到 JSON 模板和主页。此外,我们可以重复此操作任意次数,使用不同的内容。

另一方面,静态部分需要手动使用section标签包含在主题模板中。我们可以在多个模板中包含相同的静态部分。然而,每个部分将显示相同的内容,因为我们只能有一个静态部分的实例。我们可以通过访问静态与动态部分部分来提醒自己静态和动态部分。

问题 2

我们可以使用什么对象来访问块输入值?编写一些代码,使我们能够访问特定的blocks模块输入值。

答案

我们可以通过section对象访问块的输入值,并将其与blocks属性结合,这将返回一个块对象的数组:

{% for block in section.blocks %}
  {% case block.type %}
    {% when "block-type" %}
    {{ block.settings.input-id }}
  {% endcase %}
{% endfor %}

我们可以通过访问使用块构建部分来提醒自己如何访问块输入类型。

问题 3

limitmax_blocks 属性之间的区别是什么?

答案

limitmax_blocks 属性之间的主要区别在于,limit 属性只允许我们限制重复特定块类型的次数。另一方面,max_blocks 属性允许我们限制在特定部分内可以包含的块的数量,而不考虑块类型。我们可以通过访问 使用块构建max_blocks 属性 部分来提醒自己如何使用 limitmax_blocks 属性以及两者之间的区别。

问题 4

我们如何应用特定部分的 CSS 样式?

答案

如果我们需要包含特定部分的 CSS,我们可以使用 {% style %}{% endstyle %} 标签,这将允许我们使用 Liquid 代码。然而,除了 style 标签之外,我们还需要定义一个唯一的标识符,稍后我们将用它作为选择器。我们可以使用 section 对象和 id 属性来完成这个操作,这将返回动态部分的动态 ID 或静态部分的文件名。我们可以通过访问 样式标签 部分来提醒自己如何创建特定部分的 CSS。

请注意,所有四个项目的解决方案都可在 GitHub 上找到:github.com/PacktPublishing/Shopify-Theme-Customization-with-Liquid/tree/main/Projects

附录

常见问题解答

经过多年的帮助开发者适应 Liquid,出现了许多特定的情况和问题,对于这些问题的答案并不容易找到。现在我们将列出最常见的情况,并提供一些建议,这些建议将有助于提高你对 Shopify 和 Liquid 的了解,并使你的工作变得更加容易:

  1. 是否可以在代码编辑器中找到特定代码的位置,而无需手动搜索每个模板、部分或代码片段?

    开发者在开始使用Shopify时遇到的最大问题之一是,他们很难在不同的模板、部分和代码片段中找到特定的代码片段。虽然经过一段时间,我们确实会习惯于更有效地查找东西,但一开始可能会相当麻烦。

    为了帮助我们解决这个问题,我们可以使用一个叫做Shopify Theme Search by BoldChrome插件。这个小插件在代码编辑器内提供了一个搜索输入框,我们可以输入任何想要查找的字符串。几秒钟后,它会高亮显示包含搜索字符串的每一个目录和文件。你可以在chrome.google.com/webstore/detail/shopify-theme-search-by-b/epbnmkionkpliaiogpemfkclmcnbdfle了解更多信息。

  2. 无论我们创建多少新的副本主题,出于某种原因,副本主题都没有保存任何之前所做的自定义设置。 在这种情况下我们应该怎么办呢?

    第二个最棘手的问题发生在主题复制过程中。在创建主题副本后,我们可能会注意到副本主题已经完全重置。它不包含我们在主题编辑器中之前设置的任何自定义设置。

    问题出在settings_data.json文件中,如果我们打开它,我们会看到它是空的,在某些情况下,文件可能完全缺失。这种发生的原因很明显——Shopify 没有正确地复制文件内容——但问题是为什么?如果我们导航到最初创建副本的主题,打开settings_data.json文件,并尝试将其内容复制到新的副本主题中,由于 JSON 文件中的错误,我们无法保存文件。如果我们尝试将复制的内容保存到新文件中,Shopify 将不允许这样做,并给我们提供一个错误代码,指示有问题的值。

    为了解决这个问题,我们需要手动搜索错误中收到的每个值,并相应地更新它。可能出现的问题包括,我们在删除特定部分块之前没有从主题编辑器内部删除该块,就完全删除了该部分。Shopify 会继续尝试加载该块。然而,由于我们已经删除了它,它将无法加载,并会破坏 JSON 文件。第二个最常见的问题是使用range输入类型时,输入的当前值超过了最小值和最大值之间的范围。只有在解决所有这些问题之后,我们才能正确保存文件。

  3. 是否可以恢复已删除的主题?

    每个商店允许我们保留最多 20 个重复的主题作为备份,在Shopify Plus计划中最多可以保留 50 个重复的主题。如果我们因任何原因删除主题文件,并且没有在 GitHub 或本地保存备份文件,该文件将永久丢失,我们将无法以任何方式恢复它。

  4. 我们如何找到特定商店正在使用的应用程序?

    虽然我们无法看到商店使用的每种应用程序,但我们可以通过使用Fera.ai 的 Shopify App DetectorKoala Inspector - 检查 Shopify 商店Chrome 插件来检索大多数应用程序的名称。您可以通过以下链接获取有关 Fera.ai Shopify App Detector 的更多信息:chrome.google.com/webstore/detail/shopify-app-detector-by-f/lhfdhjladfcmghahdbcmlceajdlbkale.

    您可以通过以下链接获取有关 Koala Inspector - 检查 Shopify 商店的更多信息:chrome.google.com/webstore/detail/koala-inspector-inspect-s/hjbfbllnfhppnhjdhhbmjabikmkfekgf.

  5. 我们如何轻松识别导致商店变慢的 Liquid 代码?

    为了帮助我们,Shopify 推出了一款名为Shopify Theme Inspector for Chrome的插件,我们可以使用它来获取详细的分析渲染配置文件,并帮助我们缩小导致最大延迟的文件。您可以通过以下链接获取有关 Shopify Theme Inspector for Chrome 的更多信息:chrome.google.com/webstore/detail/shopify-theme-inspector-f/fndnankcflemoafdeboboehphmiijkgp.

  6. 当我们尝试使用 Shopify 合作伙伴平台向客户发送协作账户请求时,由于某种原因,我们始终收到消息说商店 URL 未与商店关联。 我们该如何解决这个问题?

    原因在于,在大多数情况下,店主已经设置了一个自定义域名,其 URL 与我们要求发送协作账户请求的 myshopify.com URL 相差甚远,所以将 .com 替换为 .myshopify.com 并不能解决我们的问题。我们可以通过简单地导航到他们提供给我们的 URL,打开检查器并在控制台中输入 Shopify 来轻松解决这个问题,这将提示一系列关于商店的各种信息。其中,我们将找到一个正确的 myshopify.com URL。请注意,Shopify 关键字是区分大小写的。

  7. Shopify 代码编辑器有暗黑模式吗?

    答案是肯定的!但它相当隐蔽。为了揭示它,导航到代码编辑器并点击按钮以展开编辑器屏幕。我们可以在编辑器屏幕的右上角找到它。一旦我们展开了编辑器屏幕,我们会注意到现在我们的右侧有两个滚动条。如果我们滚动到页面底部,我们会找到允许我们更改编辑器颜色模式的 白色黑色 按钮。然而,请注意,当我们关闭展开的编辑器视图时,编辑器将恢复到默认颜色。

  8. 有没有一种方法可以自动格式化 Liquid 文件中的代码呢?

    答案是肯定的! 我们可以使用 CTRL + A 在代码编辑器中突出显示整个 Liquid 文件,然后按 Shift + Tab,自动根据整个文件进行格式化。然而,虽然自动格式化对大多数文件都有帮助,但请注意,自动格式化在 section 架构标签及其内容上不适用。

图片

Packt.com

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及领先的行业工具,帮助您规划个人发展并提升职业生涯。更多信息,请访问我们的网站。

第十章:为什么订阅?

  • 使用来自超过 4,000 位行业专业人士的实用电子书和视频,节省学习时间,增加编码时间

  • 通过为您量身定制的 Skill Plans 提高学习效果

  • 每月免费获得一本电子书或视频

  • 完全可搜索,便于轻松访问关键信息

  • 复制粘贴、打印和收藏内容

您知道 Packt 为每本书都提供电子书版本,包括 PDF 和 ePub 文件吗?您可以在packt.com升级到电子书版本,并且作为印刷书客户,您有权获得电子书副本的折扣。有关更多信息,请联系我们customercare@packtpub.com

www.packt.com,您还可以阅读一系列免费技术文章,注册各种免费通讯,并享受 Packt 书籍和电子书的独家折扣和优惠。

您可能喜欢的其他书籍

如果您喜欢这本书,您可能对 Packt 的其他书籍也感兴趣:

Mastering Adobe Photoshop Elements

封面链接

HTML5 和 CSS 响应式网页设计 - 第三版

Ben Frain

ISBN: 978-1-83921-156-0

  • 将 CSS 媒体查询集成到您的设计中;为不同的设备应用不同的样式

  • 根据屏幕大小或分辨率加载不同的图像集

  • 利用可访问 HTML 模式的速度、语义和干净的标记

  • 将 SVG 集成到设计中,以提供分辨率无关的图像

  • 应用 CSS 的最新特性,如自定义属性、可变字体和 CSS 网格

  • 将验证和界面元素,如日期和颜色选择器,添加到 HTML 表单中

  • 了解使用过滤器、阴影、动画等多种方式增强界面元素的方法

Mastering Adobe Photoshop Elements

封面链接

使用 Pimcore 现代化企业 CMS

Daniele Fontani, Marco Guiducci, Francesco Minà

ISBN: 978-1-80107-540-4

  • 创建、编辑和管理 Pimcore 文档以用于您的网页

  • 使用数字资产管理(DAM)功能在 Pimcore 中管理网络资产

  • 发现如何为您的网页创建布局、模板和自定义小部件

  • 使用管理 UI 管理您的 Pimcore 网站的第三方插件

  • 发现使用 Pimcore 作为产品信息管理(PIM)系统的实践方法

  • 探索 Pimcore 的主数据管理(MDM)以用于企业 CMS 开发

  • 使用有效的技巧和窍门构建可重用的网站组件并节省时间

Mastering Adobe Photoshop Elements

封面WordPress 5 Complete - 第七版

WordPress 5 完全指南 - 第七版

Karol Król

ISBN: 978-1-78953-201-2

  • 学习如何使用 Gutenberg 编辑器调整您的插件

  • 创建适合在网络上发布的优化内容

  • 利用块编辑器制作外观出色的页面和帖子

  • 以可访问和清晰的方式构建您的网页

  • 安装和使用插件和主题

  • 定制您网站的设计

  • 轻松有效地上传多媒体内容,如图片、音频和视频

  • 开发您自己的 WordPress 插件和主题

  • 使用 WordPress 构建除博客以外的网站

Packt 正在寻找像您这样的作者

如果您有兴趣成为 Packt 的作者,请访问 authors.packtpub.com 并今天申请。我们已与成千上万的开发者和技术专业人士合作,就像您一样,帮助他们将见解与全球技术社区分享。您可以提交一般申请,申请我们正在招募作者的特定热门话题,或者提交您自己的想法。

分享您的想法

现在您已经完成了 Shopify 主题定制与 Liquid,我们非常乐意听听您的想法!如果您在亚马逊购买了这本书,请点击此处直接进入该书的亚马逊评论页面 并分享您的反馈或在该购买网站上留下评论。

您的评论对我们和整个技术社区都至关重要,并将帮助我们确保我们提供高质量的内容。

您可能还会喜欢的其他书籍

您可能还会喜欢的其他书籍

您可能还会喜欢的其他书籍

posted @ 2025-09-08 13:00  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报