{"id":16515674,"url":"https://github.com/insin/sacrum","last_synced_at":"2025-07-29T14:09:44.310Z","repository":{"id":66198982,"uuid":"2201069","full_name":"insin/sacrum","owner":"insin","description":"One codebase, two environments - single-page JavaScript apps in the browser, forms 'n links webapps on Node.js for almost-free","archived":false,"fork":false,"pushed_at":"2012-07-14T12:22:35.000Z","size":464,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-08T16:11:14.216Z","etag":null,"topics":["experiment","isomorphic","javscript","universal"],"latest_commit_sha":null,"homepage":"http://insin.github.io/sacrum/fragile.html","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/insin.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.rst","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-08-13T09:53:11.000Z","updated_at":"2024-03-17T19:11:10.000Z","dependencies_parsed_at":"2023-02-20T00:00:35.404Z","dependency_job_id":null,"html_url":"https://github.com/insin/sacrum","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/insin/sacrum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insin%2Fsacrum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insin%2Fsacrum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insin%2Fsacrum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insin%2Fsacrum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/insin","download_url":"https://codeload.github.com/insin/sacrum/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insin%2Fsacrum/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267701222,"owners_count":24130447,"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","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["experiment","isomorphic","javscript","universal"],"created_at":"2024-10-11T16:18:02.426Z","updated_at":"2025-07-29T14:09:44.288Z","avatar_url":"https://github.com/insin.png","language":"JavaScript","readme":"======\nSacrum\n======\n\n*Sacrum is on the back burner for a while - if I can make it work, this is what it's going to be:*\n\nSacrum provides components and conventions for writing single-page JavaScript\napps which also run on `Node.js`_ for almost-free as good ol' forms 'n links\nwebapps, allowing you to ditch the hashbangs and **use real URLs like TBL\nintended**, without having to expend significant effort on a separate backend\nand without leaving user agents which *Don't Do JavaScript* out in the cold.\n\nIt gives you a baseline from which to progressively enhance for those who\n*Definitely Do Do JavaScript* by running the JavaScript everybody else can't or\nwon't run for them.\n\n----\n\nThe sample application for Sacrum is `Fragile`_ - it serves as the test-bed\nand proving ground for new features, components and ideas.\n\n.. _`Node.js`: http://nodejs.org\n.. _`Fragile`: http://jonathan.buchanan153.users.btopenworld.com/sacrum/fragile/fragile.html\n\nCurrent Development Focus\n=========================\n\nIn order to run relatively painlessly in both environments, Sacrum needs to get\nits async on.\n\nThe current example app code is all written in a synchronous style, but the\nfollowing operations and components will need to support asynchronous usage:\n\nModels\n  * lookups when instances are not cached locally, for display and validation.\n  * saving/updating of instances needs to be implemented.\n\nForms\n  * `newforms`_ will need to add an async API for validations which involve I/O,\n    such as performing a HEAD request to verify that a URL exists and is\n    reachable.\n  * ModelChoiceFields may need to perform an async lookup before a form can be\n    displayed or validated.\n\nTemplating\n  * it's possible that async operations would need to be performed by items being\n    rendered in `DOMBuilder`_ templates. To support this, we would need to be able\n    to specify when this is the case and...\n\n    * defer displaying until callbacks are called?\n    * (client) display as much as we can, with placeholders to be filled upon\n      completion?\n    * (server) sending as much of the response as we can until each async\n      operation completes?\n\nDependencies \u0026 Demo\n===================\n\nSacrum is built using a number of dual-sided components - that is, JavaScript\nmodules which are designed to run on both client and server. Browser\ndependencies which have not yet been ported to use Node.js-style modules are\nbundled in the ``deps/`` directory.\n\nNode.js dependencies are available via `npm`_. If you're cloning this repo,\nrun ``npm install`` to install them locally in a ``node_modules`` directory.\n\n- `isomorph`_ provides a dual-sided utility toolbelt.\n\n- `urlresolve`_ provides dual-sided routing/URL configuration.\n\n- `Concur`_ provides dual-sided inheritance sugar for Models and Views.\n\n- `DOMBuilder`_ provides dual-sided templating in code with template\n  inheritance, generating DOM Elements and registering event handlers on the\n  browser and generating HTML on Node.js with hooks for event handlers to be\n  attached later.\n\n- `newforms`_ provides dual-sided form display, user input validation and type\n  coercion. It also requires DOMBuilder for rendering across environments.\n\n- `express`_  is being used to hook the sample application up in Node.js.\n\n  Run ``node server.js`` to bring it up and open http://localhost:8000/admin/\n  in a browser. Now disable JavaScript and notice how you're still able to\n  navigate and use the (bits of the) app (which have been implemented so far).\n\n.. _`isomorph`: https://github.com/insin/isomorph\n.. _`urlresolve`: https://github.com/insin/urlresolve\n.. _`Concur`: https://github.com/insin/concur\n.. _`npm`: http://npmjs.org\n.. _`express`: http://expressjs.com\n\nSacrum Components\n=================\n\nThe following components are defined in `sacrum.js`_ and (in browsers) exposed to\nthe global scope as properties of a ``Sacrum`` object, which is omitted here for\nbrevity.\n\n.. _`sacrum.js`: https://github.com/insin/fragile/blob/master/sacrum.js\n\nModels\n------\n\n``Model(props)``\n\n   Base constructor for data types, which sets the given instance properties.\n\n``Model.extend(prototypeProps, constructorProps)``\n\n   Creates a constructor which inherits from ``Model`` and sets its prototype and\n   constructor properties in one fell swoop.\n\n   The ``prototypeProps`` argument is **required** and is expected to contain, at\n   the very least, a ``Meta`` object which defines the model's name.\n\n   If a child constructor is not provided via ``prototypeProps.constructor``, a\n   new constructor which calls ``Model(props)`` on instantiation will be created\n   for you.\n\n   Extended Model constructors and prototypes will have a ``_meta`` property which\n   is an instance of a ``ModelOptions`` object, with the following properties:\n\n   ``name``\n      The model's name.\n\n   ``namePlural``\n      Plural form of the model's name - if not provided in the ``Meta`` object,\n      this will be defaulted to ``name`` followed by an ``'s'``.\n\nDon't forget to add a ``toString`` method to the prototype when extending\n``Model``, to define its default display::\n\n   var Vehicle = Model.extend({\n     toString: function() {\n       return this.reg\n     }\n   , Meta: {\n       name: 'Vehicle'\n     }\n   })\n\n``Storage(model)``\n\n   Storage and retrieval of instances of a particular ``Model``. Not persistent\n   yet.\n\n   **Prototype methods:**\n\n   ``add(instance)``\n      Generates and id for and adds the given instance.\n\n   ``remove(instance)``\n      Removes the given instance.\n\n   ``all()``\n      Gets all instances.\n\n   ``get(id)``\n      Gets an instance by id.\n\n   ``query()``\n      Creates a Query returning all instances.\n\n::\n\n   var Vehicles = new Storage(Vehicle)\n\n``Query(storage)``\n\n   Provides access to results of querying a ``Storage``, and a means to perform\n   further queries/filtering.\n\n   **Prototype methods:**\n\n   ``__iter__()``\n      Returns query results - currently just ``storage.all()``\n\n   ``get(id)``\n      Gets an instance by id.\n\nModel Validation\n~~~~~~~~~~~~~~~~\n\nSacrum doesn't offer any hooks for doing so yet, but it does let `newforms`_ know\nhow its ``Storage`` objects work, which enables use of ``forms.ModelChoiceField``\nfor display, selection and validation of related models.\n\n::\n\n   var DriverForm = forms.Form({\n     name: forms.CharField({maxLength: 255})\n   , vehicle: forms.ModelChoiceField(Vehicles.query())\n   })\n\n.. _`NOTES.rst`: https://github.com/insin/fragile/blob/master/NOTES.rst\n\nViews\n-----\n\nA ``Views`` object contains a bunch of related functions which implement control\nand display logic.\n\n``Views(props)``\n\n   Base constructor for objects containing functions which implement display and\n   control logic. Use this constructor if you only need a singleton, setting its\n   view functions as instance properties.\n\n``Views.extend(prototypeProps, constructorProps)``\n\n   Creates a constructor which inherits from ``Views`` and sets its prototype and\n   constructor properties in one fell swoop, if provided.\n\n   If a child constructor is not provided via ``prototypeProps.constructor``, a\n   new constructor which calls ``Views(props)`` on instantiation will be created\n   for you.\n\n   ``Views.prototype`` methods  expect the following instance properties:\n\n   ``name`` *(String)*\n      Name for the collection of view functions.\n\n      For example, if you have a bunch of view functions which handle listing\n      and editing ``Vehicle`` objects, a logical name would be ``'VehicleViews'``.\n\n   ``el`` *(Element)*\n      The element which contains the views' contents.\n\n   These don't have to be set at construction time - you could defer setting\n   them until the views' ``init()`` method is called, if appropriate, or in\n   the case of ``el``, it will be populated with an element if not already set\n   when the ``display()`` method is used.\n\n   **Prototype attributes:**\n\n   ``tagName``\n      The tagName used by ``_ensureElement`` to automatically create an\n      element if needed - defaults to ``'div'``.\n\n   **Prototype methods:**\n\n   ``render(templateName, context, events)``\n      Renders a DOMBuilder template with the given context data.\n\n      ``templateName`` *(String)*\n         Name of a DOMBuilder template.\n      ``context`` *(Object)*\n         Template rendering context data.\n      ``events`` *(Object.\u003cString, Function\u003e)*\n         Named event handling functions - if provided, these functions will be\n         bound to this Views instance and added to the template context as an\n         ``'events'`` property.\n\n   ``display(templateName, context, events)``\n      On browsers:\n         Ensures this view has an element which content can be inserted into by\n         first calling ``_ensureElement()``, renders a DOMBuilder template,\n         replaces the contents of the element with the rendered contents and\n         returns the element.\n\n      On servers:\n         Calls ``render`` and returns rendered contents.\n\n      To support usage in both environments, you should always return the result of\n      calling this method when it signifies that your view function is finished\n      doing its thing.\n\n   ``replaceContents(el, contents)``\n      Replaces the contents of an element and returns it.\n\n   ``_ensureElement()``\n      If an ``el`` instance property does not exist, creates and populates it with\n      a suitable element which content can be appended to.\n\n   ``log(...)``, ``warn(...)``, ``error(...)``\n      Console logging methods, which include the views' name in logs, passing\n      all given arguments to console logging functions.\n\n::\n\n   var VehicleViews = Sacrum.Views.extend({\n     name: 'VehicleViews'\n\n   , init: function() {\n       this.el = document.getElementById(\"vehicles\")\n     }\n\n   , list: function() {\n       this.debug('list')\n       var vehicles = Vehicles.all()\n       return this.display('vehicleList', {vehicles: vehicles})\n     }\n\n     // ...\n   })\n\nURLConf\n-------\n\nURL patterns can be configuredto map URLs to views, capturing named parameters\nin the process, and to reverse-resolve a URL name and parameters to obtain\na URL.\n\n``URLConf``\n\n   Application URL configuration should be set in ``URLConf.patterns``, which\n   should contain a list of pattens for resolution.\n\n   ``resolve(path)``\n      Resolves the given URL path, returning an object with ``func``, ``args`` and\n      ``urlName`` properties if successful, otherwise throwing a ``Resolver404``\n      error.\n\n   ``reverse(urlName, args)``\n      Reverse-resolves the given named URL with the given args (if applicable),\n      returning a URL string if successful, otherwise throwing a ``NoReverseMatch``\n      error.\n\n``handleURLChange(e)``\n\n   Event handling function which prevents navigation from occurring and instead\n   simulates it, resolving the target URL, extracting arguments if necessary and\n   calling the configured view function with them.\n\n   This function knows how to deal with:\n\n   * Links (``\u003ca\u003e`` elements), handling their ``onclick`` event.\n   * Forms (``\u003cform\u003e`` elements), handling their ``onsubmit`` event.\n\n   If used with a form's ``onsubmit`` event, submission of form parameters will\n   be simulated as an object passed as the last argument to the view function.\n   Values for multiple fields with the same ``name`` will be passed as a list.\n\n::\n\n   var VehicleViews = new Sacrum.Views({\n     // ...\n\n   , index: function() {\n        return this.display('index')\n     }\n\n   , details: function(id) {\n       var vehicle = Vehicles.get(id)\n       return this.display('vehicleDetails', {vehicle: vehicle})\n     }\n\n   , getURLs: function() {\n       return urlresolve.patterns(this\n       , urlresolve.url('',      'index',   'vehicle_index')\n       , urlresolve.url('list/', 'list',    'vehicle_list')\n       , urlresolve.url(':id/',  'details', 'vehicle_details')\n       )\n     }\n\n     // ..\n   })\n\n   URLConf.patterns = VehicleViews.getURLs()\n\nTemplates\n---------\n\nSacrum doesn't insist that you use any particular templating engine, but comes\nwith helpers out of the box to use `DOMBuilder`_'s templating mode.\n\nThe default implementation of Views' ``render()`` method uses DOMBuilder\ntemplates and the following additional helpers are also provided.\n\n``URLNode(urlName, args, options)``\n\n  A ``TemplateNode`` which reverse-resolves using the given URL details.\n\n  If an ``{as: 'someName'}`` options object is passed, the URL will be added\n  to the template context under the given variable name, otherwise it will be\n  returned.\n\nThe following convenience accessors are added to ``DOMBuilder.template``:\n\n``$resolve``\n   A reference to ``handleURLChange(e)``\n\n``$url(urlName, args, options)``\n  Creates a ``URLNode``.\n\n::\n\n   $template('vehicleList'\n   , TABLE({'class': 'list'}\n     , THEAD(TR(\n         TH('Registration')\n       , TH('# Wheels')\n       ))\n     , TBODY($for('vehicle in vehicles'\n       , $url('vehicle_details', ['{{ vehicle.id }}'], {as: 'detailsURL'})\n       , TR({'class': $cycle(['odd', 'even'])}\n         , TD(\n             A({href: '{{ detailsURL }}', click: $resolve}, '{{ vehicle.reg }}')\n           )\n         , TD('{{ vehicle.wheels }}')\n         )\n       ))\n     )\n   )\n\n\n.. _`DOMBuilder`: https://github.com/insin/DOMBuilder\n\nHistory\n-------\n\nTODO\n\nSacrum.Admin Components\n=======================\n\nThe following components are defined in `admin.js`_ and exposed (in browsers) as\nproperties of a ``Sacrum.Admin`` object, which is omitted here for brevity.\n\n.. _`admin.js`: https://github.com/insin/fragile/blob/master/admin.js\n\nAdminViews\n----------\n\nAn *instance* of ``Views`` which makes use of any ``ModelAdminViews`` which have\nbeen created to display a basic admin section.\n\n``AdminViews`` contains the following properties and functions:\n\n   ``init()``\n      Initialises the view element and registers all ``ModelAdminViews`` which\n      have been created so far. Each ``ModelAdminViews`` registered will have its\n      ``el`` set to this views' element.\n\n   ``modelViews`` (Array)\n      ModelAdminViews registered by ``init()``\n\n   ``index()``\n      Displays an index listing ModelAdminViews for use.\n\n   ``getURLs()``\n      Creates and returns URL patterns for the index view and includes\n      patterns for each ModelAdminViews.\n\nModelAdminViews\n---------------\n\nAn extended ``Views`` constructor which takes care of some of the repetitive work\ninvolved in creating basic Create  / Retrieve / Update / Delete (CRUD)\nfunctionality for a ``Model``.\n\n``ModelAdminViews(props)``\n\n   Creates an ``ModelAdminViews`` instance using a passed-in object defining\n   instance properties.\n\n   This specialised version of ``Views`` expects to find the following instance\n   properties:\n\n   ``namespace`` *(String)*\n      Unique namespace for the instance - used in base templates to ensure\n      created element ids are unique and when looking up templates which\n      override the base templates.\n\n   ``storage`` *(Storage)*\n      A Storage object used to create, retrieve, update and delete model\n      instances.\n\n   ``form`` *(forms.Form)*\n      A newforms ``Form`` used to take and validate user input when creating and\n      updating model instances.\n\n   ``elementId`` *(String)*\n      The id of the element in which content should be displayed, if appropriate.\n      This should be provided if using ``ModelAdminView`` for standalone CRUD\n      functionality. If using ``AdminView``, it will provide the view element.\n\n\n::\n\n   var VehicleAdminViews = new ModelAdminViews({\n     name: 'VehicleAdminViews'\n   , namespace: 'vehicles'\n   , storage: Vehicles\n   , form: VehicleForm\n   })\n\nTemplates\n---------\n\nThe Admin uses the following DOMBuilder templates, which you may wish to\nextend to customise display.\n\n+-------------------+--------------------------------------------+---------------------------------------+\n| Template          | Description                                | Blocks                                |\n+===================+============================================+=======================================+\n| ``admin:base``    | base template for admin display            | breadCrumbs, contents                 |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:index``   | table listing of ModelAdminViews           | N/A                                   |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:list``    | table listing of model instances           | itemTable, headers, controls          |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:listRow`` | table row displayed in list view           | linkText, extraCells                  |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:add``     | add form for creating a new model instance | formRows                              |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:detail``  | details of a selected model instance       | top, detail, detailRows, controls     |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:edit``    | edit form for a model instance             | formRows                              |\n+-------------------+--------------------------------------------+---------------------------------------+\n| ``admin:delete``  | confirms deletion of a model instance      | N/A                                   |\n+-------------------+--------------------------------------------+---------------------------------------+\n\nIn the above template names, ``'admin'`` is a namespace.\n\nWhen loading templates, ModelAdminViews first attempts to load a template using\nthe namespace which was provided when it was instantiated, so to override one of\nits templates, you just need to define a template named using your own, leading,\nnamespace.\n\nIn our vehicles example, you could extend these templates to display a vehicle's\nregistration and the number of wheels it has in the list template, like so::\n\n   with (DOMBuilder.template) {\n\n   $template({name: 'vehicles:admin:list', extend: 'admin:list'}\n   , $block('headers'\n     , TH('Registration')\n     , TH('# Wheels')\n     )\n   )\n\n   $template({name: 'vehicles:admin:listRow', extend: 'admin:listRow'}\n   , $block('linkText', '{{ item.reg }}')\n   , $block('extraCells'\n     , TD('{{ item.wheels }}')\n     )\n   )\n\n   }\n\nSpiel (Y U NIH?)\n================\n\nThis started out as (and still is, at the moment) a single-page app I was\nplaying around with to get back into writing single-page apps.\n\nI was planning to try out Backbone and Spine with when I was offline for a\nweek on holiday, but in the absence of help from the internet and that nagging\nfeeling that I wasn't fully 'getting' the abstractions or that I was using them\nas the author intended, I started playing around with my own code and extracting\nreusable components, also making use of `DOMBuilder`_ and `newforms`_ for\ntemplating, form display and input validation/type coercion.\n\nI've been writing those libraries with use on the browser and backend as an\nexpressly-stated goal, but I wasn't actually *using* them in anger on the\nbackend, so it's time to remedy that, too...\n\n.. _`DOMBuilder`: https://github.com/insin/DOMBuilder\n.. _`newforms`: https://github.com/insin/newforms\n\nMIT License\n===========\n\nCopyright (c) 2011, Jonathan Buchanan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsin%2Fsacrum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finsin%2Fsacrum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsin%2Fsacrum/lists"}