├── .circleci ├── config.yml └── images │ ├── Dockerfile │ └── readme.md ├── .github ├── contributing.md ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── LICENSE ├── bower.json ├── default.nix ├── docs ├── concepts │ └── understanding-free-queries.md ├── examples.md ├── how-to │ └── embed-parent-queries.md ├── index.md └── tutorials │ ├── dropdown.md │ ├── getting-started.md │ └── typeahead.md ├── examples.dhall ├── examples ├── Components │ ├── Dropdown.purs │ └── Typeahead.purs ├── Internal │ ├── CSS.purs │ ├── Proxy.purs │ └── RemoteData.purs └── Main.purs ├── mkdocs.yml ├── package.json ├── packages.dhall ├── readme.md ├── spago.dhall ├── src └── Select.purs └── template └── ComponentTemplateCommented.purs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: ~/select 5 | docker: 6 | - image: thomashoneyman/purescript:0.0.3 7 | 8 | jobs: 9 | test: 10 | <<: *defaults 11 | steps: 12 | - checkout 13 | 14 | - restore_cache: 15 | keys: 16 | - v1.5.0-bower-cache-{{ .Branch }}-{{ checksum "bower.json" }} 17 | - v1.5.0-bower-cache-{{ .Branch }} 18 | - v1.5.0-bower-cache 19 | 20 | - restore_cache: 21 | keys: 22 | - v1.5.0-yarn-cache-{{ .Branch }}-{{ checksum "package.json" }} 23 | - v1.5.0-yarn-cache-{{ .Branch }} 24 | - v1.5.0-yarn-cache 25 | 26 | - run: 27 | name: Install dependencies from Yarn and Bower 28 | command: yarn 29 | 30 | - save_cache: 31 | key: v1.5.0-bower-cache-{{ .Branch }}-{{ checksum "bower.json" }} 32 | paths: 33 | - ~/select/bower_components 34 | - ~/select/output 35 | 36 | - save_cache: 37 | key: v1.5.0-yarn-cache-{{ .Branch }}-{{ checksum "package.json" }} 38 | paths: 39 | - ~/select/node_modules 40 | 41 | - run: 42 | name: Build example components 43 | command: | 44 | yarn build-docs 45 | 46 | - persist_to_workspace: 47 | root: docs 48 | paths: 49 | - js 50 | 51 | site: 52 | working_directory: ~/select 53 | docker: 54 | - image: jfloff/alpine-python:latest-slim 55 | steps: 56 | - checkout 57 | 58 | - setup_remote_docker 59 | 60 | - run: 61 | name: Install dependencies 62 | command: pip install mkdocs mkdocs-material 63 | 64 | - attach_workspace: 65 | at: docs 66 | 67 | - run: 68 | name: Run site generation 69 | command: mkdocs build 70 | 71 | - persist_to_workspace: 72 | root: site 73 | paths: 74 | - . 75 | 76 | docs: 77 | <<: *defaults 78 | steps: 79 | - checkout 80 | 81 | - run: 82 | name: Prepare branches by checking both out locally 83 | command: | 84 | git checkout gh-pages 85 | git checkout master 86 | 87 | - run: 88 | name: Set up gh-pages for build artifacts 89 | command: | 90 | mkdir ../gh-pages 91 | git worktree add ../gh-pages gh-pages 92 | 93 | - attach_workspace: 94 | at: site 95 | 96 | # Enable building docs 97 | - add_ssh_keys: 98 | fingerprints: 99 | - "bf:0e:a0:9d:37:6e:40:34:3c:72:90:f5:c8:93:cd:a5" 100 | 101 | - run: 102 | name: Copy over files to gh-pages branch 103 | command: | 104 | cp -r site/. ../gh-pages 105 | cp -r .circleci/. ../gh-pages/.circleci 106 | 107 | - run: 108 | name: Push update to GitHub 109 | command: | 110 | cd ../gh-pages 111 | git config --global user.email "admin@thomashoneyman.com" 112 | git config --global user.name "CircleCI Build" 113 | git add . 114 | git commit --allow-empty -m "Build triggered by CircleCI" 115 | git push -u origin gh-pages 116 | 117 | workflows: 118 | version: 2 119 | build: 120 | jobs: 121 | - test: 122 | filters: 123 | branches: 124 | ignore: gh-pages 125 | 126 | # On master branch, rebuild docs 127 | - site: 128 | filters: 129 | branches: 130 | only: 131 | - master 132 | - docs 133 | 134 | requires: 135 | - test 136 | 137 | - docs: 138 | filters: 139 | branches: 140 | only: 141 | - master 142 | - docs 143 | 144 | requires: 145 | - site 146 | 147 | -------------------------------------------------------------------------------- /.circleci/images/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:stretch 2 | 3 | MAINTAINER Thomas Honeyman 4 | 5 | # Global install yarn package manager 6 | # Global install purescript, pulp, and bower 7 | RUN apt-get update && apt-get install -y curl apt-transport-https && \ 8 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ 9 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ 10 | apt-get update && apt-get install -y yarn && \ 11 | yarn global add purescript pulp bower 12 | 13 | USER node 14 | -------------------------------------------------------------------------------- /.circleci/images/readme.md: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | 3 | This folder contains the base dockerfile for running tests on Circle CI for the project. It serves in place of an official PureScript repository. 4 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | Thank you for contributing to Select. Your contribution helps grow the PureScript ecosystem and make more essential components available to Halogen users. 4 | 5 | Following these guidelines helps ensure we're able to carefully consider your contribution and help you finalize your pull request. Your time (like ours) is valuable, and we try to at least provide constructive feedback on every contribution. 6 | 7 | ### Contributions we love 8 | Select is an open source project and we love to receive contributions from the community. There are many ways to contribute, including: 9 | 10 | * Help fix open issues, even if that simply means adding a helpful comment 11 | * Improve the existing example components to better demonstrate Select's features 12 | * Help expand our test coverage 13 | * Update documentation and guides to be easier to understand, more comprehensive, and above all -- up to date! 14 | 15 | ### Contributions we should discuss 16 | Some contributions will take some discussion before we accept an update to the project. If your contribution includes one of the below examples (or seems to be reasonably similar), please consider reaching out to us before putting in a lot of work to build the feature. We're active on [#fp-chat](https://functionalprogramming.slack.com/) in the PureScript channel (new? [use this link to join](https://fpchat-invite.herokuapp.com/)!) and are reachable via email. For feature requests, feel free to open an issue with a tag. 17 | 18 | * New example components (while we accept new examples, we expect a new example to be meaningfully different from existing ones) 19 | * New tutorials (while we love seeing new tutorials, we won't always be able to feature them in the readme) 20 | 21 | 22 | # Ground Rules 23 | 24 | We have a small set of quality-of-life guidelines for contributing to Select. These include: 25 | 26 | * All pull requests must pass continuous integration. 27 | * If you are adding new functionality, you're expected to provide tests and documentation for your code. If you're fixing an existing bug, please provide a failing test case your patch solves. 28 | * All new example components should be meaningfully different from existing ones and should be added to the ./circleci/run-examples.sh file so they are covered by continuous integration. 29 | * If at all possible, please avoid requiring new dependencies. 30 | 31 | ### Filing issues 32 | If you have a general question about the project, it is better to ask us on [#fp-chat](https://functionalprogramming.slack.com/) than to open a new issue. If you have run into a bug in the project, then please do open an issue! When you do, we ask that you follow a few steps which are outlined in our issues template. The gist of it is here: 33 | 34 | * Verify the problem is indeed with Select (not with Halogen, Pulp, Bower, or PureScript); 35 | * Record what versions you are using for Select, Halogen, and PureScript 36 | * Describe the issue with steps to reproduce (as much as you are able). A minimal reproducible example is the absolute best case scenario. 37 | 38 | We promise to address the issue as soon as we can. 39 | 40 | 41 | ### Suggesting features or enhancements 42 | We love to hear about ways we could make Select better. If you're wishing for a feature that doesn't exist in Select, you're probably not alone; there are bound to be others with similar needs. Please feel free to open an issue on GitHub that describes: 43 | 44 | * the feature you would like to see 45 | * why you need it 46 | * how it should work 47 | 48 | We promise to review your issue, but we aren't always able to accommodate all requests. It helps if you're able to contribute to the implementation, too! 49 | 50 | ### Code reviews 51 | The core team looks at pull requests at least weekly, at which point we will review your code, ensure it meets our ground rules and fits with the philosophy of the project, and -- if necessary -- provide constructive feedback. As soon as two members of the team have each signed off on your pull request, we will merge your contribution. We expect responses within two weeks. After two weeks, we may close the pull request if it isn't showing any activity. 52 | 53 | # Testing Structure 54 | 55 | Select is designed as a group of primitives that can be combined into various types of selection UIs. We test at two levels: 56 | 57 | * The primitives themselves, with some standard behaviors 58 | * The primitives used to create several example components 59 | 60 | Our continuous integration first attempts to build and test the primitives, and then builds and tests the `/examples/` folder. Tests are built into each sub-project, and Circle CI simply runs them one after another. 61 | 62 | To verify that your implementation is working well, please both build and run tests on the main source folder and run the `run-examples.sh` script in the `.circle-ci` folder. 63 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Precheck 2 | 3 | * For help and support, please don't use the issue tracker. Instead, try the [PureScript user forum](https://purescript-users.ml) or the [functional programming chat (#fpchat)](https://functionalprogramming.slack.com). 4 | * For bug reports, do a quick search to ensure the bug has not yet been reported. 5 | * For new feature requests, please remember to describe the feature you would like to see, why you need it, and how it might be implemented. 6 | * Finally -- be nice and have fun! 7 | 8 | --- 9 | 10 | ## Environment 11 | 12 | * PureScript **[version]** 13 | * Halogen **[version]** 14 | * Select **[version]** 15 | 16 | ## Current behavior 17 | 18 | Please include code samples, errors, steps to reproduce, and compiler output if appropriate. 19 | 20 | If possible, provide a sample minimal application or test case that reproduces the error. Consider using the [starter Halogen template](https://github.com/slamdata/purescript-halogen-template) from SlamData to reproduce the error. 21 | 22 | For visual bugs that are difficult to demonstrate in text, consider including a .gif using [Recordit](http://www.recordit.co/) 23 | 24 | ## Expected behavior 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## What does this pull request do? 2 | 3 | Please describe the purpose of the pull request, including any background context and links to related issues. If this introduces a large change to the project, please describe what you have implemented. 4 | 5 | ## Where should the reviewer start? 6 | 7 | Help save the reviewer time by highlighting the most important sections of code. 8 | 9 | ## How should this be manually tested? 10 | 11 | If this is a new feature, you should provide new test coverage. If it fixes an existing bug, you should provide a failing test case this change fixes. Other than automated testing, what manual tests can the reviewer perform to verify your work? 12 | 13 | ## Other Notes: 14 | 15 | Does the project documentation need to be updated? Should we introduce a new example, or update any existing examples? Does this PR require any other follow-up tasks? 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | .psci_modules 3 | bower_components 4 | node_modules 5 | .spago 6 | 7 | # Generated files 8 | .psci 9 | .psc-ide-port 10 | .purs-repl 11 | output 12 | package-lock.json 13 | generated-docs 14 | 15 | # Dist 16 | docs/css 17 | docs/js 18 | site 19 | 20 | # IDE 21 | *.swo 22 | *.swp 23 | *.log 24 | *.lock 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-halogen-select", 3 | "homepage": "https://github.com/citizennet/purescript-halogen-select", 4 | "authors": [ 5 | "Thomas Honeyman " 6 | ], 7 | "description": "Building blocks for common selection user interfaces in PureScript & Halogen", 8 | "keywords": [ 9 | "purescript", 10 | "halogen", 11 | "autocomplete", 12 | "typeahead", 13 | "date picker", 14 | "image picker", 15 | "select", 16 | "selection" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/citizennet/purescript-halogen-select.git" 21 | }, 22 | "license": "Apache-2.0", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "output", 28 | "dist", 29 | "docs", 30 | "site", 31 | "generated-docs" 32 | ], 33 | "dependencies": { 34 | "purescript-halogen": "^5.0.0-rc.9", 35 | "purescript-halogen-hooks": "https://github.com/thomashoneyman/purescript-halogen-hooks.git#v0.4.0", 36 | "purescript-halogen-hooks-extra": "https://github.com/JordanMartinez/purescript-halogen-hooks-extra.git#v0.6.0" 37 | }, 38 | "devDependencies": { 39 | "purescript-affjax": "^10.0.0", 40 | "purescript-argonaut": "^6.0.0", 41 | "purescript-psci-support": "v4.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs ? import {} }: 2 | with nixpkgs; 3 | 4 | let 5 | livereload = python27Packages.buildPythonPackage { 6 | name = "livereload-2.5.1"; 7 | src = fetchurl { 8 | url = https://pypi.python.org/packages/e9/2e/c4972828cf526a2e5f5571d647fb2740df68f17e8084a9a1092f4d209f4c/livereload-2.5.1.tar.gz; 9 | sha256 = "0b2yyfnpddmrwjfqsndidzchkf3l9jlgzfkwl8dplim9gq6y2ba2"; 10 | }; 11 | 12 | propagatedBuildInputs = with python27Packages; [ six tornado ]; 13 | 14 | meta = { 15 | homepage = https://github.com/lepture/python-livereload; 16 | description = "Python LiveReload is an awesome tool for web developers"; 17 | license = stdenv.lib.licenses.bsd3; 18 | }; 19 | }; 20 | 21 | mkdocs = python27Packages.buildPythonApplication rec { 22 | name="mkdocs-0.17.2"; 23 | src = fetchurl { 24 | url = https://pypi.python.org/packages/27/0a/bb42cda3b298ffb4b30375b7538a4d57803ff8be418ee3e00460188c4332/mkdocs-0.17.2.tar.gz; 25 | sha256 = "18d3m9iws5shlbg0yj5xwiy68bliiz70v32y5pa8wi274c36nssa"; 26 | }; 27 | 28 | propagatedBuildInputs = with python27Packages; 29 | [ tornado livereload click pyyaml markdown jinja2 ]; 30 | 31 | meta = { 32 | homepage = http://www.mkdocs.org/; 33 | description = "MkDocs is a fast, simple and downright gorgeous static site generator that’s geared towards building project documentation. Documentation source files are written in Markdown, and configured with a single YAML configuration file."; 34 | license = stdenv.lib.licenses.bsd3; 35 | }; 36 | }; 37 | 38 | pymdown-extensions = python27Packages.buildPythonPackage { 39 | name = "pymdown-extensions-4.8"; 40 | src = fetchurl { 41 | url = https://pypi.python.org/packages/f5/9f/74d8a85458e831f3b161956b30bc60d31c6a507ed72ac4f4cb2ca08d8042/pymdown-extensions-4.8.tar.gz; 42 | sha256 = "1zvi8d44v758vbhi9fl5x5gqs098ajamilfz53jzid0v0fad88nj"; 43 | }; 44 | 45 | propagatedBuildInputs = with python27Packages; [ markdown ]; 46 | doCheck = false; 47 | 48 | meta = { 49 | homepage = https://github.com/facelessuser/pymdown-extensions; 50 | description = "Extension pack for Python Markdown."; 51 | license = stdenv.lib.licenses.mit; 52 | }; 53 | }; 54 | 55 | mkdocs-material = python27Packages.buildPythonPackage { 56 | name = "mkdocs-material-2.6.0"; 57 | src = fetchurl { 58 | url = https://pypi.python.org/packages/e3/85/f42493d453d9b6f51912b818134a4a555c597807ba96b40eae12017ede35/mkdocs-material-2.6.0.tar.gz; 59 | sha256 = "1xq5nkj0g6gg4lm8nhcwc30g9drq1i4p4pky8s5c0rfa1s9s7sla"; 60 | }; 61 | 62 | propagatedBuildInputs = with python27Packages; [ pymdown-extensions pygments mkdocs ]; 63 | 64 | meta = { 65 | homepage = https://squidfunk.github.io/mkdocs-material/; 66 | description = "A Material Design theme for MkDocs"; 67 | license = stdenv.lib.licenses.mit; 68 | }; 69 | }; 70 | 71 | markdown-fenced-code-tabs = python27Packages.buildPythonPackage { 72 | name = "markdown-fenced-code-tabs-0.2.0"; 73 | src = fetchurl { 74 | url = https://pypi.python.org/packages/21/7a/0cee39060c5173cbd80930b720fb18f5cb788477c03214ccdef44ec91d85/markdown-fenced-code-tabs-0.2.0.tar.gz; 75 | sha256 = "05k5v9wlxgghw2k18savznxc1xgg60gqz60mka4gnp8nsxpz99zs"; 76 | }; 77 | 78 | propagatedBuildInputs = with python27Packages; [ markdown ]; 79 | 80 | meta = { 81 | homepage = https://github.com/yacir/markdown-fenced-code-tabs; 82 | description = "Generates Bootstrap HTML Tabs for Consecutive Fenced Code Blocks"; 83 | license = stdenv.lib.licenses.mit; 84 | }; 85 | }; 86 | 87 | in 88 | nixpkgs.stdenv.mkDerivation { 89 | name = "env"; 90 | buildInputs = [ 91 | mkdocs 92 | mkdocs-material 93 | nixpkgs.nodejs 94 | nixpkgs.yarn 95 | nixpkgs.stack 96 | ]; 97 | } 98 | -------------------------------------------------------------------------------- /docs/concepts/understanding-free-queries.md: -------------------------------------------------------------------------------- 1 | title: Free Queries in PureScript & Halogen 2 | 3 | # Understanding Free Monad Queries 4 | 5 | Most of the time Halogen queries look like this: 6 | 7 | ```hs 8 | data QueryF (… other type arguments omitted …) a 9 | = ... 10 | | SetVisibility Visibility a 11 | | GetVisibility (Visibility -> a) 12 | ``` 13 | 14 | (where `#!hs QueryF` is used directly as the Halogen query functor) 15 | 16 | This library takes a slightly different approach: the query functor is actually `#!hs Control.Monad.Free.Free QueryF`, the [free monad](https://pursuit.purescript.org/packages/purescript-free/4.2.0/docs/Control.Monad.Free) generated by the query functor. 17 | 18 | This allows queries the full power of monadic (and applicative) composition: sequencing effects, determining control flow based on previous results, and my favorite: doing nothing (`#!hs pure unit`). 19 | 20 | We now define smart query constructors for this Free pattern like so: 21 | 22 | ```hs 23 | -- | Set the container visibility (`On` or `Off`). 24 | setVisibility :: ∀ o item eff. Visibility -> Query o item eff Unit 25 | setVisibility v = liftF (SetVisibility v unit) 26 | 27 | -- | Get the container visibility (`On` or `Off`). Most useful when sequenced 28 | -- | with other actions. 29 | getVisibility :: ∀ o item eff. Query o item eff Visibility 30 | getVisibility = liftF (GetVisibility id) 31 | ``` 32 | 33 | ## Different patterns 34 | In the simple cases, the helpers Halogen use to work with raw query constructors are folded into the smart Free query constructors already: `#!hs H.action (SetVisibility On)` becomes simply `#!hs setVisiblity On`, and similarly `#!hs H.request GetVisibility` is just `#!hs getVisibility`. This is because these patterns are typically present already smart constructors: `#!hs setVisibility` returns `#!hs Free QueryF Unit`, since it is an action, and `#!hs getVisibility` returns `#!hs Free QueryF Visibility`, since it requests the visibility. This allows for easy composition in `#!hs do` notation: 35 | 36 | ```hs 37 | toggleVisibility = do 38 | vis <- getVisibility 39 | setVisibility (not vis) 40 | ``` 41 | 42 | C’est très facile! 43 | 44 | Event handlers look a little different. This is one example: 45 | 46 | ```hs 47 | HE.onMouseDown \ev -> Just do 48 | Select.preventClick ev 49 | Select.select index 50 | when doBlur Select.triggerBlur 51 | ``` 52 | 53 | (Of course you may return `#!hs Nothing` if you so wish, but its effect is just like `#!hs pure unit` now.) 54 | 55 | If you do not need access to the argument `#!hs ev`, `#!hs Select.always` provides a simple shortcut for `#!hs const <<< Just`: 56 | 57 | ```hs 58 | HE.onMouseOver $ Select.always $ Select.highlight (Index index) 59 | ``` 60 | 61 | ## Returning non-unit values 62 | Use `#!hs map` or `#!hs <$` or `#!hs pure` to return other types of values from a query. So, instead of something like this: 63 | 64 | ```hs 65 | H.subscribe $ eventSource' someEventSource 66 | \value -> Just (SetVisibility value H.Listening) 67 | ``` 68 | 69 | Use 70 | 71 | ```hs 72 | H.subscribe $ eventSource' someEventSource 73 | \value -> Just $ setVisibility value $> H.Listening 74 | ``` 75 | 76 | or 77 | 78 | ```hs 79 | H.subscribe $ eventSource' someEventSource 80 | \value -> Just do 81 | setVisibility value 82 | pure H.Listening 83 | ``` 84 | 85 | !!! info "" 86 | Many thanks to [Nicholas Scheel](https://github.com/MonoidMusician) for providing the implementation of `QueryF` and the documentation above. 87 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | title: PureScript Halogen Select Examples 2 | 3 | # Examples 4 | 5 | You can play around with a few example components here. However, for a much richer set of components with much more functionality, check out the [Ocelot design system by CitizenNet](https://citizennet.github.io/purescript-ocelot/#typeaheads). 6 | 7 | !!! warning 8 | The components on this page function properly, but look horrible while we migrate CSS. 9 | 10 | ### Dropdown 11 | 12 | Dropdowns are a common button-driven input type, especially for navigation. But most custom dropdowns sacrifice usability: unlike browser default dropdowns, you can't type on most custom dropdowns, nor are many built with accessibility in mind. With `Select` you can easily create rich, usable dropdowns with little code. 13 | 14 |
15 | 16 | Curious how to build a dropdown with `Select`? Check out [the dropdown tutorial](https://citizennet.github.io/purescript-halogen-select/tutorials/dropdown). 17 | 18 | ### Typeahead / Autocomplete 19 | 20 | This library was originally designed so that we could build typeaheads with all sorts of custom rendering and functionality. It was frustrating to find solutions that almost worked, but broke down as soon as you needed a moderate level of customization. 21 | 22 | Building typeaheads with `Select` is only a little more complex than building dropdowns. Instead of a button as input, you'll use a text input, and you'll be responsible for deciding how to handle user searches. `Select` handles debouncing user input, keyboard navigation, and more on your behalf. 23 | 24 | The typeahead below is quite simple; to see examples of more sophisticated typeaheads -- including ones that fetch and display data asynchronously -- check out the [Ocelot component library](https://citizennet.github.io/purescript-ocelot/#typeaheads). 25 | 26 |
27 | 28 | Curious how to build a typeahead with `Select`? Check out [the typeahead tutorial](https://citizennet.github.io/purescript-halogen-select/tutorials/typeahead). 29 | -------------------------------------------------------------------------------- /docs/how-to/embed-parent-queries.md: -------------------------------------------------------------------------------- 1 | # How to Embed Parent Queries 2 | 3 | One of the most powerful ways to extend `Select` is to write functionality in your component, triggered by a query, and then embed that query into the `Select` component. 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | title: Documentation for PureScript Halogen Select 2 | 3 | # Welcome 4 | 5 | `Select` helps you build selection user interfaces in PureScript with Halogen. You can use it to build dropdowns, typeaheads and autocompletes, date pickers, image pickers, and more, with features like keyboard navigation, accessibility, and state management handled for you. This library takes a unique approach to component design to ensure you can leverage its features without compromising your design in any way. 6 | 7 | ## Installation 8 | 9 | You can use `Select` in your PureScript project with a compatible package manager. The PureScript community typically leverages [psc-package](https://github.com/purescript/psc-package) or Bower: 10 | 11 | ```sh 12 | # Using psc-package 13 | $ psc-package install halogen-select 14 | 15 | # Using Bower 16 | $ bower install --save purescript-halogen-select 17 | ``` 18 | 19 | ## Quick Start 20 | 21 | If this is your first time using `Select`, start with the [tutorials](https://citizennet.github.io/purescript-halogen-select/tutorials/getting-started). I'd recommend starting with the simplest example where you'll learn to make a keyboard-navigable dropdown component: 22 | 23 | !!! tip 24 | Building a form? Check out [Formless](https://thomashoneyman.github.io/purescript-halogen-formless), a library for painless forms in Halogen. Don't want to build your own UI components? Check out the [Ocelot component library](https://citizennet.github.io/purescript-ocelot) for examples and resources. 25 | 26 | If this isn't your first time, you'll find value in these resources: 27 | 28 | * The [how-to](https://citizennet.github.io/purescript-halogen-select/how-to/embed-parent-queries) section contains plenty of short guides for common tasks you'll perform using `Select`. Consider it a grab-bag of useful strategies and examples you can refer to when developing new components. 29 | * The [concepts](https://citizennet.github.io/purescript-halogen-select/concepts/understanding-free-queries) section contains more detailed explanations on the design of the library. It will help you understand how to make good design choices and make the most of the tools available to you. 30 | * The [reference documentation on Pursuit](https://pursuit.purescript.org/packages/purescript-halogen-select) contains the module documentation and source code. It's a useful reference to verify what functions are available to you. 31 | * The [examples folder on Github](https://github.com/citizennet/purescript-halogen-select) contains the working source code for all the components in the [tutorials](https://citizennet.github.io/purescript-halogen-select/tutorials/getting-started). If you're building a similar component, this code can help you get started. 32 | 33 | ## Why Select? 34 | 35 | `Select` provides essential behaviors for selection UI as a flexible, extensible Halogen component. But you won't find a single render function in the code. Instead, with a few helper functions, you can write your own `#!hs State -> HTML` function however you'd like. You can: 36 | 37 | * Extend the component's functionality by embedding new queries in the HTML 38 | * Extend the component's data by including as much additional state from the parent as you want (which you can then use in your render function) 39 | * Leverage the provided features for user interaction, state management, accessibility, and logic 40 | * Retain complete freedom over the design and aesthetic of your selection component 41 | 42 | !!! aside "For visual learners" 43 | I gave a talk at the Los Angeles PureScript meetup in April 2018 about the approach this library takes. It provides an overview of our design approach, including advantages and drawbacks, as well as a simple walkthrough of building a dropdown. No, the man in the preview isn't me -- that's [Phil Freeman](http://functorial.com/), the designer of the PureScript programming language. 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/tutorials/dropdown.md: -------------------------------------------------------------------------------- 1 | title: Build a PureScript Dropdown in Halogen 2 | 3 | # Let's Build a Dropdown in PureScript! 4 | 5 | Dropdowns are among the simplest selection components you will build, but they can be tricky to get right. For example, you'll likely want to ensure that your users can type to highlight close text matches (like when you type "Ca" to highlight "California" in a state dropdown). You'll want to be accessible to folks using screen readers or keyboard-only navigation, too. And, of course, you'll want to achieve all this without compromising on your design. 6 | 7 | This tutorial is intended as a beginner-friendly, thorough introduction to `Select`. We'll build a functional dropdown complete with keyboard navigation. Along the way, we'll learn more about how to work with Halogen components, diagnose type errors, and other common PureScript tasks. 8 | 9 | !!! info 10 | This tutorial assumes you've followed the steps in the [Project Setup](https://citizennet.github.io/purescript-halogen-select/tutorials/getting-started/) section. While not necessary, this code is tested with those steps in mind. 11 | 12 | It also assumes familiarity with the Halogen framework. If you need a refresher, try the official [Halogen guide](https://github.com/slamdata/purescript-halogen/tree/master/docs) or the [whirlwind tour](https://citizennet.github.io/purescript-halogen-select/tutorials/getting-started/#a-whirlwind-tour-of-our-starter-component) of our starter component. 13 | 14 | !!! warning "" 15 | **If you are already an intermediate or advanced PureScript developer, then this tutorial will read slowly for you. Feel free to skim, get the gist of how the library works, and then move on to the [faster-paced and more advanced typeahead tutorial](https://citizennet.github.io/purescript-halogen-select/tutorials/typeahead).** 16 | 17 | Your code should work at the end of every step. If you run into issues or your code doesn't compile, please come visit us on the [PureScript user forum](https://discourse.purescript.org) or the [#fpchat Slack channel](https://functionalprogramming.slack.com). 18 | 19 | We're going to build a dropdown that is functionally equivalent this one: 20 | 21 |
22 | 23 | ## Basic Setup 24 | Let's get something on the screen! 25 | 26 | The simplest sort of dropdown has a button that can toggle a menu open or closed, a list of items that can be selected from that menu, and zero, one, or more selected items. For our dropdown we'll assume that you can select at most one item, and that selecting an item will replace the text on the button with that item. 27 | 28 | ### Rendering a button and items 29 | 30 | We'll start by rendering the button and the items. At this point our render function contains only an empty div, so let's fill in the rest of the HTML we need: 31 | 32 | ```hs 33 | render :: State -> H.ParentHTML Query ChildQuery ChildSlot m 34 | render st = 35 | HH.div_ 36 | [ HH.h1_ 37 | [ HH.text "Dropdown" ] 38 | , HH.button_ 39 | [ HH.text "Click me to view some items" ] 40 | , HH.ul_ 41 | [ HH.li_ 42 | [ HH.text "Item 1" ] 43 | , HH.li_ 44 | [ HH.text "Item 2" ] 45 | ] 46 | ] 47 | ``` 48 | 49 | !!! danger "" 50 | Make sure to compile this code and view the new output! You should see a header, a button, and two items in the list. After each step, make sure your code still compiles. 51 | 52 | ### A better `#!hs State` type 53 | 54 | It's already clear we're going to need more than `#!hs Unit` for our `#!hs State` type. We at least need to know three things: 55 | 56 | - If the menu is toggled on or off 57 | - The currently-selected item (if there is one) 58 | - The list of items available for selection 59 | 60 | We can represent each of these with simple types in our state: 61 | 62 | ```hs 63 | type State = 64 | { isOpen :: Boolean 65 | , selectedItem :: Maybe String 66 | , availableItems :: Array String 67 | } 68 | ``` 69 | 70 | Now that our state contains these three fields, we need to update our `#!hs initialState` function to produce the right type of values: 71 | 72 | ```hs 73 | initialState :: Input -> State 74 | initialState = const 75 | { isOpen: false 76 | , selectedItem: Nothing 77 | , availableItems: 78 | [ "Item One" 79 | , "Item Two" 80 | , "Item Three" 81 | ] 82 | } 83 | ``` 84 | 85 | Finally, lets update our render function to leverage the information now contained in `#!hs State`. If there's a selected item, that will be the button's text; if not, we'll fall back to a default message. If the menu is open, we'll list out the available items for selection. 86 | 87 | For code clarity, we'll also break out the dropdown into its own helper function. 88 | 89 | ```hs 90 | import Data.Maybe (fromMaybe) 91 | 92 | render :: State -> H.ParentHTML Query ChildQuery ChildSlot m 93 | render st = 94 | HH.div_ 95 | [ HH.h1_ 96 | [ HH.text "Dropdown" ] 97 | , dropdown st 98 | ] 99 | 100 | dropdown :: State -> H.ParentHTML Query ChildQuery ChildSlot m 101 | dropdown st = 102 | HH.div_ 103 | [ HH.button_ 104 | [ HH.text $ fromMaybe "Click me to view some items" st.selectedItem ] 105 | , if st.isOpen 106 | then HH.ul_ $ (\item -> HH.li_ [ HH.text item ]) <$> st.availableItems 107 | else HH.text "" 108 | ] 109 | ``` 110 | 111 | !!! tip 112 | Since the dropdown has no behavior yet, try changing the `#!hs initialState` to set `#!hs isOpen` to `#!hs true` to verify your items are in fact being rendered out to the page. 113 | 114 | It ain't pretty, but at this point we've got all the rendering we need for a basic dropdown! The next step is to actually wire in some behavior. 115 | 116 | ## Integrating the component 117 | 118 | Let's integrate the `Select` component! In just a few steps, we'll turn our simple render function into a fully-functioning dropdown with keyboard navigation, toggling, debounced type-to-search, and several other features. 119 | 120 | ### On building components with Select 121 | The key idea behind the `Select` library is to **provide behaviors, not rendering**. The core component the library exposes doesn't have a render function at all! Of course, all Halogen components require a render function to work, and `Select` is no different. *You* are expected to provide that render function. 122 | 123 | Why? 124 | 125 | When you write the render function, not the library, you get to decide exactly what your component will look and feel like. You can also control what queries to trigger from HTML and when, effectively allowing you to control the behavior of the component *without configuration*. You can even extend it with new behavior and new state by using information from your parent component. The end result is a much smaller library component with a lot more flexibility and power for you. 126 | 127 | We just wrote the rendering we need for an (admittedly ugly) dropdown. The render function we just wrote can actually serve almost as-is as the render function for `Select`! All we have to do is mount the `Select` component, make a few tweaks to our render code, and then pass in a little configuration information. Let's do that next. 128 | 129 | ### Importing the Select component 130 | 131 | The first thing we'll do is bring in the `Select` library in the first place. 132 | 133 | ```hs 134 | import Select as Select 135 | import Select.Setters as Setters 136 | ``` 137 | 138 | !!! tip 139 | You can always [view the module documentation for Select on Pursuit](https://pursuit.purescript.org/packages/purescript-halogen-select) or the [source code on GitHub](https://github.com/citizennet/purescript-halogen-select). This is useful when integrating with third-party components so that you can check out the `#!hs Input`, `#!hs State`, `#!hs Query`, and `#!hs Message` types. 140 | 141 | Next, we need to update our `#!hs ChildSlot` and `#!hs ChildQuery` types. We're only going to have one dropdown so we can leave the child slot as `#!hs Unit`; we do need to add the `Select` component's query type to our `#!hs ChildQuery` synonym, however. 142 | 143 | This code, unfortunately, won't work: 144 | 145 | ```hs 146 | type ChildQuery = Select.Query 147 | 148 | Error found: 149 | Type synonym Select.Query is partially applied. 150 | Type synonyms must be applied to all of their type arguments. 151 | 152 | in type synonym ChildQuery 153 | ``` 154 | 155 | The compiler has noticed that `#!hs ChildQuery`, a type synonym, is partially applied. That's because `#!hs Select.Query`, itself a type synonym, takes several arguments as described in the [module documentation on Pursuit](https://pursuit.purescript.org/packages/purescript-halogen-select/3.0.0/docs/Select#t:Query). Let's walk through each one: 156 | 157 | ```hs 158 | type ChildQuery o item = Select.Query o item 159 | ``` 160 | 161 | `o` is *your* query type. Remember how you can embed your own queries into `Select`, and in that way extend the component's functionality? This is how. So we can fill in the first argument: 162 | 163 | ```hs 164 | type ChildQuery item = Select.Query Query item 165 | ``` 166 | 167 | `item` is the type of whatever items you want to be selectable. Commonly these are strings, but can also be custom data types. Later on, in the [typeahead tutorial](https://citizennet.github.io/purescript-halogen-select/tutorials/typeahead), we'll see how powerful custom data types can be for rendering purposes. For our simple dropdown we'll simply specialize this to `#!hs String`: 168 | 169 | ```hs 170 | type ChildQuery = Select.Query Query String 171 | ``` 172 | 173 | Now that `Select` has been imported and we've updated our `ChildQuery` and `ChildSlot` types to support it, we can worry about what to do when we receive a message from the component. 174 | 175 | 176 | ### Mounting the component 177 | 178 | We're finally ready to mount the `Select` component. Mounting any component in Halogen requires supplying a slot value, the component itself, the component's input, and the component's handler. We can put together all of these except for the input, which we haven't prepared yet. 179 | 180 | Let's stub out our render function in preparation: 181 | 182 | ```hs 183 | import Halogen.HTML.Events as HE 184 | 185 | render :: State -> H.ParentHTML Query ChildQuery ChildSlot m 186 | render st = 187 | HH.div_ 188 | [ HH.h1_ 189 | [ HH.text "Dropdown" ] 190 | , HH.slot unit Select.component ?input (HE.input <<< const Nothing) 191 | ] 192 | ``` 193 | 194 | With that out of the way, we can turn to filling in our component's input type. We can either look at the module documentation for `Select.Input` or look at the type error that resulted from our typed hole, `?input`. Both will tell us that we need to provide a value of this type: 195 | 196 | ```hs 197 | type Input o item = 198 | { inputType :: InputType 199 | , items :: Array item 200 | , initialSearch :: Maybe String 201 | , debounceTime :: Maybe Milliseconds 202 | , render :: State item -> ComponentHTML o item 203 | } 204 | ``` 205 | 206 | Let's build this up, step by step. First, we see we have to provide an `#!hs InputType`. This is described in the module documentation: 207 | 208 | ```hs 209 | -- | Text-driven inputs will operate like a normal search-driven selection component. 210 | -- | Toggle-driven inputs will capture key streams and debounce in reverse (only notify 211 | -- | about searches when time has expired). 212 | data InputType 213 | = TextInput 214 | | Toggle 215 | ``` 216 | 217 | We don't have any text input for our dropdown -- its a button -- so we'll go with the `#!hs Toggle` constructor. 218 | 219 | ```hs 220 | selectInput :: Select.Input Query String 221 | selectInput = 222 | { inputType: Select.Toggle 223 | , ... 224 | } 225 | ``` 226 | 227 | Next, we're expected to provide an array of items. Fortunately we already have those in our `#!hs State`. We can just send those items directly into the input. 228 | 229 | ```hs 230 | selectInput = 231 | { ... 232 | , items: st.availableItems 233 | } 234 | ``` 235 | 236 | Next, we're expected to provide an initial search. This would be more useful if we had a text input, but for our dropdown, we'll start off with no initial search. 237 | 238 | ```hs 239 | selectInput = 240 | { ... 241 | , initialSearch: Nothing 242 | } 243 | ``` 244 | 245 | What about a debounce time? For toggle-driven components, this is how long to aggregate key presses before the user's typing should affect the list of items. For search-driven components, this is how long to delay before raising a message with the new search. For our dropdown, we don't care: 246 | 247 | ```hs 248 | selectInput = 249 | { ... 250 | , debounceTime: Nothing 251 | } 252 | ``` 253 | 254 | Finally, we're expected to provide a render function to the component. Ah ha! We've actually already written a render function for a dropdown -- it's just that the type is wrong. 255 | 256 | ### Adapting the render function for `Select` 257 | 258 | Let's look at the types side-by-side: 259 | 260 | ```hs 261 | Select.render :: Select.State item -> Select.ComponentHTML o item 262 | 263 | dropdown :: State -> H.ParentHTML Query ChildQuery ChildSlot m 264 | dropdown st = 265 | HH.div_ 266 | [ HH.button_ 267 | [ HH.text $ fromMaybe "Click me to view some items" st.selectedItem ] 268 | , if st.isOpen 269 | then HH.ul_ $ (\item -> HH.li_ [ HH.text item ]) <$> st.availableItems 270 | else HH.text "" 271 | ] 272 | ``` 273 | 274 | From this, we can see that we need to use the state type from `Select` to drive our render function, not the state from our parent component. Will our function still work? Let's look at [`Select`'s state type in the module documentation](https://pursuit.purescript.org/packages/purescript-halogen-select/3.0.0/docs/Select#t:State) to see what we have available: 275 | 276 | ```hs 277 | type State item = 278 | { inputType :: InputType 279 | , search :: String 280 | , debounceTime :: Milliseconds 281 | , debouncer :: Maybe Debouncer 282 | , inputElement :: Maybe HTMLElement 283 | , items :: Array item 284 | , visibility :: Visibility 285 | , highlightedIndex :: Maybe Int 286 | , lastIndex :: Int 287 | } 288 | ``` 289 | 290 | That's a lot of stuff! We have some of the data we need in `Select`'s state -- we have our list of items and whether the menu is open or closed. We even got new information, like which item is highlighted. But we're missing something crucial: which item is selected. 291 | 292 | As a general rule, `Select` does not manage selections on your behalf. You are expected to decide what you want to happen when an item is selected and to store the selections yourself. 293 | 294 | What can we do? We don't have all the information we need to write this function. Or do we? 295 | 296 | In fact, *so long as we write the `Select` render function within the `where` clause of the parent component's render function*, we have access to the parent's state! Let's give it a shot. 297 | 298 | ```hs 299 | render parentState = 300 | HH.div_ 301 | [ HH.h1_ 302 | [ HH.text "Dropdown" ] 303 | , HH.slot unit Select.component ?input (HE.input <<< const Nothing) 304 | ] 305 | where 306 | dropdown 307 | :: Select.State String 308 | -> Select.ComponentHTML Query String 309 | dropdown childState = 310 | HH.div_ 311 | [ HH.button_ 312 | [ HH.text $ fromMaybe "Click me to view some items" parentState.selectedItem ] 313 | , if childState.visibility == Select.On 314 | then HH.ul_ $ (\item -> HH.li_ [ HH.text item ]) <$> childState.items 315 | else HH.text "" 316 | ] 317 | ``` 318 | 319 | It works! Even better, we no longer have to manage things like `#!hs openState` in the parent anymore. Finally, now that we have the render function we need, we can finally finish our component's input type: 320 | 321 | ```hs 322 | render :: State -> H.ParentHTML Query ChildQuery ChildSlot m 323 | render parentState = 324 | HH.div_ 325 | [ HH.h1_ 326 | [ HH.text "Dropdown" ] 327 | , HH.slot unit Select.component selectInput (HE.input <<< const Nothing) 328 | ] 329 | where 330 | selectInput :: Select.Input Query String 331 | selectInput = 332 | { inputType: Select.Toggle 333 | , items: parentState.availableItems 334 | , initialSearch: Nothing 335 | , debounceTime: Nothing 336 | , render: dropdown 337 | } 338 | 339 | dropdown = ... 340 | ``` 341 | 342 | ## Integrating Behavior 343 | 344 | Everything up to this point has been standard Halogen except for writing the child component's render function. At this point, the `Select` component is running -- good work! However, it's not yet *doing* anything. 345 | 346 | It's now time to turn your static HTML into a fully-functioning dropdown. 347 | 348 | ### Attaching behavior to Select 349 | 350 | `Select` works by using a few helper functions that attach at critical points in your render function. The library assumes very little about what your rendering looks like, except that there _at least_ exists: 351 | 352 | - One or more items that can be selected 353 | - An element that contains those items 354 | - A focusable element that can be used to toggle visibility and capture keystrokes 355 | 356 | Accordingly, you'll need to use three helper functions, each exported by the `Select.Setters` module: 357 | 358 | - `setItemProps` 359 | - `setContainerProps` 360 | - `setToggleProps` (for toggle-driven input) 361 | - `setInputProps` (for text-driven input) 362 | 363 | Each of these functions should be used on the property array for the relevant element in your HTML. Let's walk through each one using our built render function. 364 | 365 | First, let's augment our individual items. `#!hs setItemProps` takes an index and some properties and outputs some new properties, which include all sorts of event handlers necessary for keyboard events and click events to work. In order to provide it with the index it needs, we'll use the `#!hs mapWithIndex` function from `#!hs Data.Array`. 366 | 367 | ```hs 368 | import Data.Array (mapWithIndex) 369 | 370 | dropdown childState = 371 | HH.div_ 372 | [ HH.button_ 373 | [ HH.text $ fromMaybe "Click me to view some items" parentState.selectedItem ] 374 | , case childState.visibility of 375 | Select.Off -> HH.text "" 376 | 377 | Select.On -> HH.ul [] $ 378 | mapWithIndex 379 | (\ix item -> HH.li (Setters.setItemProps ix []) [ HH.text item ]) 380 | childState.items 381 | ] 382 | ``` 383 | 384 | Next, we'll move to the element that contains the items. The `#!hs setContainerProps` function takes and returns some properties, attaching all the behavior the library needs. We'll use this on the parent element, `ul`: 385 | 386 | ```hs 387 | dropdown childState = 388 | HH.div_ 389 | [ HH.button_ 390 | [ HH.text $ fromMaybe "Click me to view some items" parentState.selectedItem ] 391 | , case childState.visibility of 392 | Select.Off -> HH.text "" 393 | 394 | Select.On -> HH.ul (Setters.setContainerProps []) $ 395 | mapWithIndex 396 | (\ix item -> HH.li (Setters.setItemProps ix []) [ HH.text item ]) 397 | childState.items 398 | ] 399 | ``` 400 | 401 | Finally, we can make sure that our button toggles the menu on and off, captures keyboard events, can be tabbed to, and all sorts of other stuff with the `#!hs setToggleProps` function. 402 | 403 | ```hs 404 | dropdown childState = 405 | HH.div_ 406 | [ HH.button 407 | (Setters.setToggleProps []) 408 | [ HH.text $ fromMaybe "Click me to view some items" parentState.selectedItem ] 409 | , case childState.visibility of 410 | Select.Off -> HH.text "" 411 | 412 | Select.On -> HH.ul [] $ 413 | mapWithIndex 414 | (\ix item -> HH.li (Setters.setItemProps ix []) [ HH.text item ]) 415 | childState.items 416 | ] 417 | ``` 418 | 419 | Whew! Your rendering code now contains everything it needs to provide a keyboard-accessible dropdown. If you open this up in the browser and click around, you'll notice it's properly toggling and can be tabbed to. 420 | 421 | Let's make one last improvement. When you use your arrow keys on the dropdown, the highlighted index is changing, but since we didn't provide any CSS we can't see it. Let's add some bare-bones styling so we can watch the highlights: 422 | 423 | ```hs 424 | import Halogen.HTML.Properties as HP 425 | 426 | Select.On -> HH.ul (Setters.setContainerProps []) $ 427 | mapWithIndex 428 | (\ix item -> 429 | HH.li 430 | ( Setters.setItemProps ix 431 | $ case Just ix == childState.highlightedIndex of 432 | true -> [ HP.attr (HH.AttrName "style") "color: red;" ] 433 | _ -> [] ) 434 | [ HH.text item ] 435 | ) 436 | childState.items 437 | ``` 438 | 439 | There we go! Try toggling the menu on and off, using your arrow, enter, and escape keys, and so on. It works! 440 | 441 | ...almost. Alas, we aren't doing anything when the user makes a selection. `Select` is attempting to notify us that a selection occurred, but we never provided a handler. Let's fix that now. 442 | 443 | ### Handling messages from Select 444 | 445 | When you add a new child component you invariably need to add a handler for its `#!hs Message` type. What should the parent do when something important has occurred in the child? To handle messages, add a new constructor to your query algebra that takes the child's `#!hs Message` type as an argument: 446 | 447 | ```hs 448 | data Query a 449 | = NoOp a 450 | | HandleSelect Select.Message a 451 | ``` 452 | 453 | Ah -- this won't compile! 454 | 455 | ```hs 456 | Error found in module Component: 457 | 458 | Could not match kind 459 | (Type -> Type) -> Type -> Type 460 | 461 | with kind 462 | Type 463 | 464 | in type constructor Query 465 | ``` 466 | 467 | This looks similar to the type error we got when we tried to just use `Select.Query` in a type synonym. We need to provide a `#!hs Type` to `#!hs HandleSelect`, but `#!hs Select.Message` is still awaiting 2 arguments, the first of which is *itself* awaiting an argument! Let's go look at the [module documentation for `Select.Message`](https://pursuit.purescript.org/packages/purescript-halogen-select/3.0.0/docs/Select#t:Message). 468 | 469 | ```hs 470 | data Message o item 471 | ``` 472 | 473 | We've seen both of these arguments before in the component's query type, so we should fill them in with the same values. `o` is our parent component query type, and `item` is a `#!hs String`: 474 | 475 | ```hs 476 | data Query a 477 | = NoOp a 478 | | HandleSelect (Select.Message Query String) a 479 | ``` 480 | 481 | As soon as you save and rebuild you'll see another compiler error! 482 | 483 | ```hs 484 | Error found in module Component 485 | 486 | A case expression could not be determined to cover all inputs. 487 | The following additional cases are required to cover all inputs: 488 | 489 | (HandleSelect _ _) 490 | 491 | in value declaration component 492 | ``` 493 | 494 | This time it's because we've added a new query, but we never updated our `#!hs eval` function to describe what should happen when the query is triggered. What should we actually _do_ when a message comes up from the child component? 495 | 496 | !!! tip 497 | You'll often see type errors that end in "... value declaration component." when the error occurred in any of the functions in the `where` clause for the component. It can be annoying to track down where the error actually is in your code. One way to help track these down is to move your code out of the `where` block and into the module level temporarily so the compiler can identify which particular function is causing the issue. 498 | 499 | There are four possible sub-cases that we need to handle, each described in the module documentation: 500 | 501 | ```hs 502 | data Message o item 503 | = Searched String 504 | | Selected item 505 | | VisibilityChanged Visibility 506 | | Emit (o Unit) 507 | ``` 508 | 509 | Let's stub out each of these cases and then decide what to do with them: 510 | 511 | ```hs 512 | eval :: Query ~> H.ParentDSL State Query (ChildQuery eff) ChildSlot Message m 513 | eval = case _ of 514 | NoOp next -> pure next 515 | 516 | HandleSelect message next -> case message of 517 | Select.Searched string -> 518 | pure next 519 | 520 | Select.Selected item -> 521 | pure next 522 | 523 | Select.VisibilityChanged vis -> 524 | pure next 525 | 526 | Select.Emit query -> 527 | pure next 528 | ``` 529 | 530 | Let's take these case-by-case. 531 | 532 | What should we do when the user has searched something on our dropdown? This is just a simple list of items, so we'll simply ignore their search. We can leave this as `#!hs pure next`. 533 | 534 | What should we do when the user has selected an item? Ah! This is more interesting. We want to set their item as the currently-selected one, and then we want to remove it from the available items list. Once we've removed it from the available items, we'll update `Select` with its new items to display and we'll toggle it off. 535 | 536 | We can use `#!hs difference` from `#!hs Data.Array` to filter out the selected item from the overall list of items. This is a common pattern in `Select`: the parent holds the immutable list of all possible items, and `Select` receives some subset of those items at each render. You might use the user's search to filter out items in a typeahead, for example, or only load 50 results at a time into a dropdown. 537 | 538 | ```hs 539 | import Data.Array (difference) 540 | 541 | Select.Selected item -> do 542 | st <- H.get 543 | _ <- H.query unit $ Select.setVisibility Select.Off 544 | _ <- H.query unit $ Select.replaceItems $ difference st.availableItems [ item ] 545 | H.modify _ { selectedItem = Just item } 546 | ``` 547 | 548 | What should we do when the dropdown's visibility has changed? This can often be useful to run validation, but for our dropdown, we don't care what its visibility is. We can leave this as `#!hs pure next`. 549 | 550 | Finally, what should we do when the child component raises its `Emit` message? What does this even mean? `Emit` exists so you can embed your own queries into `Select` and extend its behavior. Since the message contains one of your own queries, all you have to do is evaluate it: you can call `eval` recursively to run your query. 551 | 552 | You can think of `Emit` as notifying you that the query you embedded is ready to run. 553 | 554 | ```hs 555 | Select.Emit query -> do 556 | eval query 557 | pure next 558 | ``` 559 | 560 | Nice and simple! While you may write all kinds of logic for the other messages raised by `Select`, you'll always write this same code for the `Emit` message. 561 | 562 | ## Conclusion 563 | 564 | Congratulations! You have successfully built a keyboard-navigable dropdown using `Select`. You integrated the library, wrote your own render function, and then augmented it with helper functions from the library. Then, you handled the output messages and sent queries to update the component's state. You've done quite a lot of work! 565 | 566 | !!! tip 567 | Did you notice anything you would improve about this tutorial or the `Select` library? I'd love to hear about it! Feel free to reach out on the [functional programming Slack](https://functionalprogramming.slack.com/) or on the [PureScript user forum](https://discourse.purescript.org). If you found a bug or would like to make an improvement, please open an issue or pull request on the library. 568 | 569 | ### Next Steps 570 | 571 | This tutorial was a slow, thorough introduction to the `Select` library. But we've only scratched the surface of what you can do with it. I'd recommend continuing on to the faster-paced and more advanced [typeahead tutorial](https://citizennet.github.io/purescript-halogen-select/tutorials/typeahead). 572 | 573 | ### Source Code 574 | 575 | If you'd like to use this component as a starting point from which to build your own, feel free to copy/paste the source code below. 576 | 577 | ??? article "Full source code for the tutorial" 578 | ```hs 579 | module Component where 580 | 581 | import Prelude 582 | 583 | import Effect.Aff.Class (class MonadAff) 584 | import Data.Array (difference, mapWithIndex) 585 | import Data.Maybe (Maybe(..), fromMaybe) 586 | import Halogen as H 587 | import Halogen.HTML as HH 588 | import Halogen.HTML.Events as HE 589 | import Halogen.HTML.Properties as HP 590 | import Select as Select 591 | import Select.Setters as Setters 592 | 593 | data Query a 594 | = HandleSelect (Select.Message Query String) a 595 | 596 | type State = 597 | { isOpen :: Boolean 598 | , selectedItem :: Maybe String 599 | , availableItems :: Array String 600 | } 601 | 602 | type Input = Unit 603 | 604 | type Message = Void 605 | 606 | type ChildSlot = Unit 607 | type ChildQuery = Select.Query Query String 608 | 609 | component :: ∀ m. MonadAff m => H.Component HH.HTML Query Input Message m 610 | component = 611 | H.parentComponent 612 | { initialState 613 | , render 614 | , eval 615 | , receiver: const Nothing 616 | } 617 | where 618 | 619 | initialState :: Input -> State 620 | initialState = const 621 | { isOpen: false 622 | , selectedItem: Nothing 623 | , availableItems: 624 | [ "Item One" 625 | , "Item Two" 626 | , "Item Three" 627 | ] 628 | } 629 | 630 | render :: State -> H.ParentHTML Query ChildQuery ChildSlot m 631 | render parentState = 632 | HH.div_ 633 | [ HH.h1_ 634 | [ HH.text "Dropdown" ] 635 | , HH.slot unit Select.component selectInput (HE.input HandleSelect) 636 | ] 637 | where 638 | selectInput :: Select.Input Query String 639 | selectInput = 640 | { inputType: Select.Toggle 641 | , items: parentState.availableItems 642 | , initialSearch: Nothing 643 | , debounceTime: Nothing 644 | , render: dropdown 645 | } 646 | 647 | dropdown 648 | :: Select.State String 649 | -> Select.ComponentHTML Query String 650 | dropdown childState = 651 | HH.div_ 652 | [ HH.button 653 | (Setters.setToggleProps []) 654 | [ HH.text $ fromMaybe "Click me to view some items" parentState.selectedItem ] 655 | , case childState.visibility of 656 | Select.Off -> HH.text "" 657 | 658 | Select.On -> HH.ul (Setters.setContainerProps []) $ 659 | mapWithIndex 660 | (\ix item -> 661 | HH.li 662 | ( Setters.setItemProps ix 663 | $ case Just ix == childState.highlightedIndex of 664 | true -> [ HP.attr (HH.AttrName "style") "color: red;" ] 665 | _ -> [] ) 666 | [ HH.text item ] 667 | ) 668 | childState.items 669 | ] 670 | 671 | eval :: Query ~> H.ParentDSL State Query ChildQuery ChildSlot Message m 672 | eval = case _ of 673 | HandleSelect message next -> case message of 674 | Select.Searched string -> 675 | pure next 676 | 677 | Select.Selected item -> do 678 | st <- H.get 679 | _ <- H.query unit $ Select.setVisibility Select.Off 680 | _ <- H.query unit $ Select.replaceItems $ difference st.availableItems [ item ] 681 | H.modify _ { selectedItem = Just item } 682 | pure next 683 | 684 | Select.VisibilityChanged vis -> 685 | pure next 686 | 687 | Select.Emit query -> do 688 | eval query 689 | pure next 690 | ``` 691 | -------------------------------------------------------------------------------- /docs/tutorials/getting-started.md: -------------------------------------------------------------------------------- 1 | title: Project Setup for PureScript Halogen Select 2 | 3 | # Introduction 4 | Halogen is a powerful framework for building PureScript applications. It’s used by several companies, including SlamData and my own company, CitizenNet (a Condé Nast company), among others. The `Select` library is written for the Halogen framework, so if you don’t know how to use Halogen yet, you ought to start with the [Halogen guide](https://github.com/slamdata/purescript-halogen/tree/master/docs). That said, with only passing familiarity with Halogen, you should be able to follow along just fine! 5 | 6 | ## Setup 7 | Instead of creating a new Halogen project from scratch, we’ll start with a minimal starter template. This template includes the HTML, build scripts, and basic `Main.purs` file necessary to run your Halogen application. It also includes a component with the bare minimum definitions in place. This component does nothing at all, which is nice because we can easily use it to start building dropdowns, typeaheads, and other components. 8 | 9 | !!! info 10 | We prefer Yarn over NPM for package management and scripts, but either one will work. Anywhere you see `#!sh yarn