├── .circleci └── config.yml ├── .eslintrc.js ├── .gitignore ├── LICENCE ├── README.md ├── docs └── images │ └── template-select.png ├── package.json ├── src ├── clients │ ├── collection.ts │ ├── filter.ts │ ├── index.ts │ ├── search.ts │ ├── searchable-collection.ts │ └── utils │ │ └── index.ts ├── filter │ └── index.ts ├── index.ts ├── request │ ├── cache.ts │ ├── index.ts │ └── request.ts ├── types.ts └── utils │ └── index.ts ├── templates ├── collection.__DO-NOT-SELECT__.products.liquid └── search.__DO-NOT-SELECT__.liquid ├── test ├── .eslintrc.js ├── __snapshots__ │ └── browser.test.js.snap ├── browser.test.js └── browser │ ├── README.md │ ├── promiseGet.js │ ├── promiseGet.jsx │ ├── simpleGet.js │ └── version.js ├── tsconfig.json ├── tsdx.config.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/node:10.15-browsers 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: dependency-cache-{{ checksum "package.json" }} 11 | - run: 12 | name: Install Dependencies 13 | command: yarn install 14 | - save_cache: 15 | key: dependency-cache-{{ checksum "package.json" }} 16 | paths: 17 | - node_modules 18 | - run: 19 | name: Lint Workspace 20 | command: yarn lint 21 | - run: 22 | name: Test Workspace 23 | command: yarn test 24 | - run: 25 | name: Build Crisp 26 | command: yarn build 27 | - run: 28 | name: Browser Test 29 | command: yarn browser -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', 5 | 'prettier/@typescript-eslint', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: 'module', 11 | }, 12 | rules: { 13 | "prettier/prettier": ["error", { "singleQuote": true }], 14 | "@typescript-eslint/explicit-function-return-type": false 15 | }, 16 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built artifacts 2 | dist 3 | 4 | # Cache 5 | .rts2* 6 | 7 | # NPM Lockfile 8 | package-lock.json 9 | 10 | # Logs 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # Packages 19 | node_modules 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # TypeScript cache 25 | *.tsbuildinfo 26 | 27 | # Optional npm cache directory 28 | .npm 29 | 30 | # Yarn Integrity file 31 | .yarn-integrity -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Satel Creative Ecommerce Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crisp 2 | 3 | ## Features 4 | 5 | Crisp is a javascript library that enables advanced search and filtering capabilities for Shopify themes. It moves the filtering client-side which enables some cool stuff like 6 | 7 | - Filtering collections by tags, metafields, or anything else! 8 | - Filtering search results by tags, metafields, etc 9 | - Creating a searchable collection 10 | 11 | ## Demo 12 | 13 | - View Crisp in action: 14 | - [Colourpop](https://colourpop.com/collections/best-sellers) 15 | - [Flow Parts](https://flowparts.com/collections/all) 16 | - View on the [Demo Store](https://todo.com) 17 | 18 | ## When **NOT** to use Crisp 19 | 20 | Crisp adds a lot of complexity to a Shopify collection page. In many cases it won't be necessary and could cause headaches. That said, if you are just excited to try Crisp skip down to [getting started](#getting-started) 21 | 22 | ### You have a small catalog of products 23 | 24 | It is already easy for your customers to find what they are looking for, there is no need to intimidate them with a bunch of advanced filtering options. Just Shopify's build in Search and Tag filtering should be more than enough. 25 | 26 | ### You just want infinite scroll or search previews 27 | 28 | You don't need Crisp to accomplish this. It is overkill. If this is your goal then consider writing your own [templates](#templates) and making the ajax requests yourself. It also makes it easier to account for some of the [seo concerns](#seo-concerns) 29 | 30 | ### You want to filter a giant catalog of products 31 | 32 | Crisp does its best to optimize for performance but it can only take it so far. If you are wanting to filter thousands of products without narrowing down the selection first (think `/collections/all`) Crisp may take too long to return results. In this case consider restructuring your "funnel" if you want to use Crisp, or use an app instead. 33 | 34 | ## How it Works 35 | 36 | Crisp is made up of two components, the client and the template. The client is is a Javascript library that allows (relatively) easy access to the filtered data. The second component is the template installed in the Shopify theme which gives the client access to raw unfiltered data. 37 | 38 | ### Template 39 | 40 | Crisp relies on the fairly well-known technique of using Liquid templates to create pages that can be used as JSON endpoints. This allows the client to load and filter data. 41 | 42 | The template is simply a secondary template file in your theme (`collection.crisp.liquid` for example) that will never be seen when viewing the website normally. It starts with the line `{% layout none %}` which tells Shopify not to include the normal framing content of your site (header, tracking scripts, etc.). It then uses Liquid to build a JSON blob that the client will be able to recognize. 43 | 44 | The client will be able to make `fetch` or `ajax` calls to access this JSON data. This can be done by changing the `view` query parameter to match the name of the template (`example.com?view=crisp` for example). 45 | 46 | See the [/templates](/templates) folder for some pre-populated examples of templates. 47 | 48 | ### Client 49 | 50 | The client is where the "magic" happens. This is where all of the data loading, filtering, and configuration lives. 51 | 52 | At its most basic, you ask the client to get you some, for example, products from the `shoes` collection. The client will load data from the template url (`/collections/shoes?view=crisp`), process it, and return to you to display to the user. 53 | 54 | This gets more complicated when you ask tougher questions. If this time you want Size `9` or `10` running shoes in pink or purple things get a little more complicated under the hood but the interface you communicate with remains the same. 55 | 56 | To get a little bit more into it, Crisp tries to find a balance between performance and resource usage while loading and filtering. This involves making some educated guesses in terms of how many shoes to load immediately and cancelling any extraneous requests made from the guesses as quickly as possible. Of course there are still cases where there is only one item that matches the filter constraints and it is the very last one, but in most cases Crisp works quite quickly. 57 | 58 | ## SEO Concerns 59 | 60 | TODO 61 | 62 | ## Getting Started 63 | 64 | ### Adding Templates 65 | 66 | First up, we need to decide which template(s) you will need to install. This will depend on which features of Crisp you are planning to use. For example, `SearchableCollection` will require a `Collection` template and a `Search` template. If you are unsure which templates are required for a given feature see the corresponding entry in the [API Documentation](#api-documentation) and it will list the required templates. 67 | 68 | Now, knowing which templates we will be installing, head over to the [/templates](/templates) directory locate the example files that will need to be installed. Simply copy these files to your theme's `/templates` directory and we are ready to move on 69 | 70 | > You may have noticed a strange suffix on all the templates (ex. `__DO-NOT-SELECT__.products`). While this is by no means required, keep in mind that just like any alternate template this will show up in the Shopify Admin as an option for how to display a resource on the storefront. We never want to display this template by default so the underscore prefix ensures it gets pushed to the bottom of the list and, well, the "`DO-NOT-SELECT`" speaks for itself. 71 | 72 | > ![Shopify Admin Template Selector Box](./docs/images/template-select.png) 73 | 74 | ### Installing the Client 75 | 76 | There are a number of ways to install the client depending on your bundler or toolchain. For this guide however, we will be simply adding it as a script tag. 77 | 78 | Head over to the [latest release](https://github.com/SatelCreative/crisp/releases) and download `crisp.js`. Next, upload this to your themes `/assets` folder. Now we are ready to import it from the theme. 79 | 80 | > As with any dependency, it is good practice to only import resources on pages where they are required. For this example however, we will just be adding a global import in `theme.liquid`. 81 | 82 | To import Crisp, add `