{"id":20930629,"url":"https://github.com/intermine/im-tables-3","last_synced_at":"2025-07-04T19:33:37.242Z","repository":{"id":12639516,"uuid":"72425540","full_name":"intermine/im-tables-3","owner":"intermine","description":"clojurescript library to display and manipulate intermine query results on a webpage","archived":false,"fork":false,"pushed_at":"2023-07-09T18:52:57.000Z","size":928,"stargazers_count":5,"open_issues_count":26,"forks_count":5,"subscribers_count":2,"default_branch":"dev","last_synced_at":"2025-06-30T10:47:10.059Z","etag":null,"topics":["intermine"],"latest_commit_sha":null,"homepage":"http://www.intermine.org","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/intermine.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-10-31T10:20:15.000Z","updated_at":"2023-01-17T14:29:47.000Z","dependencies_parsed_at":"2024-11-18T21:40:44.383Z","dependency_job_id":"8da246f7-79c4-4805-aaab-e2ee198465a9","html_url":"https://github.com/intermine/im-tables-3","commit_stats":{"total_commits":421,"total_committers":5,"mean_commits":84.2,"dds":0.5795724465558194,"last_synced_commit":"b972ded2709399c94732bc86324b2c55d2a124df"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/intermine/im-tables-3","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intermine%2Fim-tables-3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intermine%2Fim-tables-3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intermine%2Fim-tables-3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intermine%2Fim-tables-3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/intermine","download_url":"https://codeload.github.com/intermine/im-tables-3/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intermine%2Fim-tables-3/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263606790,"owners_count":23487697,"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":["intermine"],"created_at":"2024-11-18T21:35:02.664Z","updated_at":"2025-07-04T19:33:37.185Z","avatar_url":"https://github.com/intermine.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# im-tables\n\n[![CircleCI](https://circleci.com/gh/intermine/im-tables-3.svg?style=svg)](https://circleci.com/gh/intermine/im-tables-3)\n[![Clojars Project](https://img.shields.io/clojars/v/org.intermine/im-tables.svg)](https://clojars.org/org.intermine/im-tables)\n\nA result viewer for Intermine data.\n\nDevelopment goals\n* Simplify im-tables for future developers\n* Build in clojure for easy integration with Intermine's new UI\n* Decrease file size (current im-tables is around 1.2mb uncompressed)\n* See [dev approach](devapproach.md) before attempting to make code changes.\n\n## Usage from within ClojureScript\n\nThere are two ways to render an im-table. You can mount the component passing a location vector and dispatching the *load* event yourself.\n\n```clojure\n[im-tables.core/table [:some-location :within-app-db :to-store-values]]\n\n;; In the `:component-did-mount` lifecycle or other suitable place.\n(re-frame.core/dispatch [:im-tables/load [:some-location :within-app-db :to-store-values]\n                         {:service {:root \"beta.flymine.org/beta\"\n                                    :model some-intermine-model         ; Optional\n                                    :summary-fields some-summary-fields ; Optional\n                                    }\n                          :settings {:pagination {:limit 10}}\n                          :response some-response-of-tablerows          ; Optional\n                          :query {:from \"Gene\"\n                                  :select [\"symbol\"\n                                           \"secondaryIdentifier\"\n                                           \"primaryIdentifier\"\n                                           \"organism.name\"\n                                           \"dataSets.name\"]\n                                  :where [{:path \"Gene.symbol\"\n                                           :op \"LIKE\"\n                                           :value \"M*\"}]}}])\n```\n\nThe other way is to pass in the entire map to have the im-table dispatch the *load* event automatically on mount.\n\n```clojure\n[im-tables.core/table {:location [:some-location :within-app-db :to-store-values]\n                       :service {:root \"beta.flymine.org/beta\"\n                                 :model some-intermine-model         ; Optional\n                                 :summary-fields some-summary-fields ; Optional\n                                 }\n                       :settings {:pagination {:limit 10}}\n                       :response some-response-of-tablerows          ; Optional\n                       :query {:from \"Gene\"\n                               :select [\"symbol\"\n                                        \"secondaryIdentifier\"\n                                        \"primaryIdentifier\"\n                                        \"organism.name\"\n                                        \"dataSets.name\"]\n                               :where [{:path \"Gene.symbol\"\n                                        :op \"LIKE\"\n                                        :value \"M*\"}]}}]\n```\n\nSome of the values are optional, and if supplied they will not be fetched when the component is mounted. This allows you to fetch a resource once and share it across components, or to render a table of query results that have already been fetched.\n\n## Development\n\nThere are just a few core concepts that must be understood before developing on the project. Seriously. Keeping these in mind will save you money spent on headache medication.\n\n#### 1. im-tables is not just a React component, it's also an application., and `:location` is everything\nIt uses re-frame as an in-memory database to store its values, and integrating a re-frame application into a parent re-frame application can be tricky. Why? If a parent application (BlueGenes) mounts two table components, events fired from one table to store values in app-db will override the other's state.\n\nThis makes the `:location` key of the options map crucial. It is a vector of your choosing, and it tells any given table (or tables!) to only update values in that sublocation of app-db. The `:location` value should be passed to *every single view* in im-tables, and then passed to _every single event handler_ as its first argument. Same goes for subscriptions. This is a manual process and no checks can be made to make this happen. If an event-handler using the `sandbox` interceptor is not passed a location vector as its first argument then you will likely not see your updates because they're being made to the `nil` key in app-db.\n\nThe sandbox interceptor extracts the sublocation of app-db and provides it to your event handler as if it were at the top level. This allows you to write your event handler functions as if im-tables was a standalone application rather than repeatedly reaching into a specific location in app-db. For example:\n\nUsing the `sandbox` interceptor, the following event can be written as such:\n\n```clojure\n(reg-event-db\n  :show-overlay\n  (sandbox)\n  (fn [db [_ location value]]\n    (assoc-in db [:cache :overlay?] value)))\n```\n\nWhereas a version without the `sandbox` interceptor is more verbose:\n```clojure\n(reg-event-db\n  :show-overlay\n  (sandbox)\n  (fn [db [_ location value]]\n    (update-in db location assoc-in [:cache :overlay?] value)))\n```\nThat's not a huge for simple handler functions, but it pays out when you have more complex ones that use `get-in` and `update-in` to access values in many parts of your app-db, and the function can be tested without having to prepare a mock app-db with nested values.\n\nSubscriptions don't have middleware so they need to take the table's app-db location into account:\n\n```\n(defn glue [path remainder-vec]\n  (reduce conj (or path []) remainder-vec))\n\n(reg-sub\n  :main/query-response\n  (fn [db [_ location]]\n    (get-in db (glue location [:response]))))\n```\n\n#### 2. The location is used by most subscriptions and is often bound in a component's outermost closure.\n\nAnother opportunity for a headache! Form 2 components are functions that return functions. When the component updates, the returned function is called to update the view. Values bound to the outermost function will never return anything but their original value. In this example `cake` **will never change**.\n\n```clojure\n; The component WILL NOT update when cake changes\n(defn [cake]\n  (fn []\n    [:h1 cake]))\n\n; ...whereas this component will\n(defn []\n  (fn [cake]\n    [:h1 cake]))\n```\nYou will often see location passed to the outermost function, but don't be caught in the trap of binding more volatile values there as well.\n\n#### 3. The view is optimised to render quickly\n\nWhen a table first loads it only renders the first page of the query results as bog standard html with minimal components. This allows many tables to be rendered to, say, a report page while reducing load on the browser. Mousing over the table triggers a change that forces the table into \"React\" mode when the table cells become more complex.\n\n## Building\n\n### Initial setup\n\nTo compile the css and run the unit tests, you'll need Node.js and npm. Once these have been installed, run the following.\n\n```\nnpm install\n```\n\n### Quickstart\n\nThese commands are explained in more depth below, but if you know what you want here's a quick reference of the most useful ones.\n\n```\nlein dev         # start dev server with hot-reloading\nlein repl        # start dev server with hot-reloading and nrepl (no clean or css)\nlein deploy      # build prod release and deploy to clojars\n\nlein format      # run cljfmt to fix code indentation\nlein kaocha      # run unit tests\n```\n\n### Compile css:\n\nWe use [less](http://lesscss.org/) to write our styles.\n\nYou can compile the Less sources to CSS with the command below:\n\n```\nlein less\n```\n\nIt will run in watch mode, recompiling the CSS after every change until you exit it with *Ctrl+C*. The browser page will automatically reload the updated CSS.\n\nWhen using the other lein commands to build or deploy, the less sources will automatically be compiled for production mode.\n\n### Run application:\n\n```\nlein clean\nlein figwheel dev\n```\n\nFigwheel will automatically push cljs changes to the browser.\n\nWait a bit, then browse to [http://localhost:3448](http://localhost:3448).\n\n### Run tests:\n\nMake sure to first run `npm install` to install the dependencies required to run the unit tests. You can also add `--watch` to the command below to automatically re-run tests when saving files.\n\n```\nlein kaocha\n```\n\n*Note: Running tests requires you to have a [Biotestmine](https://github.com/intermine/biotestmine) running locally. The easiest way to achieve this is by using [intermine_boot](https://github.com/intermine/intermine_boot).*\n\n## Production Build\n\n### Deploying to Heroku\n\n```\nlein clean\nlein uberjar\n```\n\nThat should compile the clojurescript code first, and then create the standalone jar.\n\nWhen you run the jar you can set the port the ring server will use by setting the environment variable PORT.\nIf it's not set, it will run on port 3000 by default.\n\nTo deploy to heroku, first create your app:\n\n```\nheroku create\n```\n\nThen deploy the application:\n\n```\ngit push heroku master\n```\n\nTo compile clojurescript to javascript:\n\n```\nlein clean\nlein cljsbuild once min\n```\n\n### Releasing a new version\n\nThe release process is a combination of the above commands, with some additional steps. Generally, you'll want to do the following.\n\n1. Update the version number in **project.clj**.\n1. Prepend changes since previous version to **CHANGELOG.md**.\n1. Commit this change and tag it using `git tag -a v1.0.0 -m \"Release v1.0.0\"`, replacing *1.0.0* with your version number.\n1. Push your commit and tag using `git push origin` followed by `git push origin v1.0.0` (again replace *1.0.0* with your version number). Make sure that you push to the intermine repository, not just your fork!\n1. Deploy a new uberjar to Clojars with `lein deploy`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintermine%2Fim-tables-3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintermine%2Fim-tables-3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintermine%2Fim-tables-3/lists"}