{"id":21804863,"url":"https://github.com/pez/clerk","last_synced_at":"2025-07-29T19:09:13.630Z","repository":{"id":33185712,"uuid":"153875190","full_name":"PEZ/clerk","owner":"PEZ","description":"In-page navigation (scrolling) for ClojureScript SPAs.","archived":false,"fork":false,"pushed_at":"2022-03-07T17:00:18.000Z","size":495,"stargazers_count":76,"open_issues_count":2,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-03T01:11:12.288Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PEZ.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-20T06:09:03.000Z","updated_at":"2025-03-09T18:06:43.000Z","dependencies_parsed_at":"2022-08-07T20:15:16.852Z","dependency_job_id":null,"html_url":"https://github.com/PEZ/clerk","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PEZ%2Fclerk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PEZ%2Fclerk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PEZ%2Fclerk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PEZ%2Fclerk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PEZ","download_url":"https://codeload.github.com/PEZ/clerk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161265,"owners_count":21057554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-27T11:57:11.523Z","updated_at":"2025-04-10T04:58:42.055Z","avatar_url":"https://github.com/PEZ.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clerk\n\nA ClojureScript library designed to make it easy to get your Single Page Application to behave more like a ”regular” site would do when it comes to navigating between, and within, pages.\n\nOnline demo here: [clerk-demo.netlify.com](https://clerk-demo.netlify.com)\n\n[![Clojars Project](https://img.shields.io/clojars/v/pez/clerk.svg)](https://clojars.org/pez/clerk)\n\nClerk takes care of the scroll positioning when:\n* Navigating to a new page, e.g. if the user clicks a link to another page.\n  * Scroll is set to the top of the page in these cases.\n* Navigation to target anchors within the page.\n  * Scroll is smoothly adjusted to the top of the target element.\n  * _This only works if the routing library you are using supports hash targets._ [Secretary](https://github.com/gf3/secretary) doesn't really. But [Bidi](https://github.com/juxt/bidi) does.\n* Navigating back/forth using the web browser history navigation.\n  * Scroll position is restored to whatever it was when the user left it.\n\nClerk does not deal with anything else beside the above. Use it together wih your routing and HTML5 history libararies of choice.\n\n## The Problem\nToday's web browsers handle all this automatic scroll positioning perfectly for regular sites. But the S in SPA really means that everything happens on the same page, even if it looks to the user as if navigatin between pages happens. A new page is just the result of rendering new content. So without managing the scroll positioning we have this UX problem:\n\n\u003ca href=\"Without Clerk.png\"\u003e\u003cimg alt=\"Without Clerk\" src=\"Without Clerk.png\" width=\"100%\"/\u003e\u003c/a\u003e\n\nIn addition to this:\n\n* The browser's default scroll restoration for history navigation can't be trusted within an SPA. It sometimes looks like it works, but then comes with big time surprises at other times.\n* In-page navigation to anchor targets doesn't happen at all unless we add code for it.\n\nLet Clerk take care of all this for you!\n\n## Usage\n\nA super easy way to try out Clerk in a new project is to use the [Leingen Reagent Template](https://github.com/reagent-project/reagent-template):\n\n```bash\n$ lein new reagent \u003cproject-name\u003e\n```\n\nFor other scenarious, read on.\n\n### Setup\n\nAdd the dependency:\n```clojure\n[pez/clerk \"1.0.0\"]\n```\n\nFor the instructions below, I will assume Clerk is required like so:\n```clojure\n(:require\n ...\n [clerk.core :as clerk]\n ...\n```\n\n### Initialize\nInitialize Clerk as early as possible when your app is starting:\n```clojure\n(clerk/initialize!)\n```\n\n### After Navigation Dispatch\nAfter any routing/navigation dispatch of your app you need to tell Clerk about the new path\n```clojure\n(clerk/navigate-page! path)\n```\n\n### After Render\nThen just one more thing. To avoid flicker, Clerk deferrs scroll adjustment until after the page is rendered. You need to tell Clerk when rendering is done:\n```clojure\n(clerk/after-render!)\n```\n\nDepending on your project the after render notification will need to be injected in different ways. Here are exemples for two common ClojureScript React frameworks, [Rum](https://github.com/tonsky/rum) and [Reagent](http://reagent-project.github.io):\n\n#### Rum\nRum has a utility callback `:after-render` that can be used in a mixin for this purpose, like so:\n```clojure\n(defc page \u003c rum/reactive\n  {:after-render\n   (fn [state]\n     (after-render!)\n     state)})\n  ...\n```\n\n#### Reagent\nFor Reagent, you can use the `reagent/after-render` function, which calls any function you provide to it when rendering is done:\n```clojure\n(reagent/after-render clerk/after-render!)\n```\n\n(You can also hook it in to the compenent life cycle, `:component-did-mount` and `:component-did-update`, if that suits your project and taste better.)\n\n### Putting it together\nThe Leiningen [Reagent template](https://github.com/reagent-project/reagent-template)'s `init!` function will look like so with all clerky stuff added:\n```clojure\n(defn init! []\n  (clerk/initialize!)\n  (accountant/configure-navigation!\n   {:nav-handler\n    (fn [path]\n      (reagent/after-render clerk/after-render!)\n      (secretary/dispatch! path)\n      (clerk/navigate-page! path))\n    :path-exists?\n    (fn [path]\n      (secretary/locate-route path))})\n  (accountant/dispatch-current!)\n  (mount-root))\n```\n\n(For Rum it will look very similar, except that you need to use a mixin for your page component's `:after-render`callback instead of using the `reagent/after-render`.)\n\n### It is not Always this Simple\nRegistering `clerk/after-render!` on the ”page component” or on navigation dispatch is not sufficient for some applications. Some pages get loaded and rendered in phases and for some apps it can take quite a while before they have all the data they need to render the final page. (And lots of other cases.) Finding the right entry point to inject the Clerk functions/commands will sometimes be a challenge. I am very interested to hear about challanges and solutions!\n\n\n## Caveats\n\n* **IMPORTANT**: If you are using some kind of analytics (like Google Analytics) for stats on site usage for your SPA, take care with any history change events resulting in ”virtual” page hits. Clerk uses the browser's history state to store the current scroll position for the page. Specifically, *the default History Change Trigger of Google Tag Manager can't be used as is*. You risk spamming your stats with ”page views” that really are just the user scrolling.\n* Clerk depeds on HTML 5 history and does not handle routing that rely on prefixing route paths with '#'. If you still need to target browsers that do not have HTML 5 history: no Clerk for you.\n\n## What About the Name?\n\n*In Scotland, the term scrow was used from about the 13th to the 17th centuries for scroll, writing, or documents in list or schedule form. There existed an office of Clerk of the Scrow (Rotulorum Clericus) meaning the Clerk of the Rolls or Clerk of the Register.* (From [Wikipedia](https://en.wikipedia.org/wiki/Scroll#Scotland).)\n\nAlso, it is quite beautiful to imagine that with some projects maybe [Secretary](https://github.com/gf3/secretary), [Accountant](https://github.com/venantius/accountant) and Clerk will work together to get the SPA to behave according to the expectations of its users.\n\n## Happy Coding ❤️ Feedback Welcome\n\nQuestions, suggestions, PRs. Just throw it at me. File issues at will. You can also most often find me at the [Clojurians Slack](https://clojurians.slack.com). Have praise? Tweet it! Tag [@pappapez](https://twitter.com/pappapez).\n\n## License\n\nCopyright © 2018 Peter Strömberg\n\nDistributed under the Eclipse Public License, either version 1.0 or (at your option) any later version.\n\n## Tested with Browserstack\n\nI'm pretty confident about that Clerk works in reasonably modern web browsers, much thanks to [BrowserStack](https://browserStack.com).\n\n\u003ca href=\"https://browserStack.com\"\u003e\u003cimg src=\"resources/Browserstack-logo.svg\" width=\"240px\"/\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpez%2Fclerk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpez%2Fclerk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpez%2Fclerk/lists"}