{"id":18084831,"url":"https://github.com/coderofsalvation/spadmin","last_synced_at":"2025-04-06T00:13:03.681Z","repository":{"id":57366860,"uuid":"59786685","full_name":"coderofsalvation/spadmin","owner":"coderofsalvation","description":" RAD framework for a SPA rest-to-admin interface","archived":false,"fork":false,"pushed_at":"2020-05-28T19:25:47.000Z","size":174,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-27T19:33:21.973Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coderofsalvation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":"https://gumroad.com/l/hGYGh"}},"created_at":"2016-05-26T22:17:36.000Z","updated_at":"2021-07-27T15:41:16.000Z","dependencies_parsed_at":"2022-08-23T20:10:40.026Z","dependency_job_id":null,"html_url":"https://github.com/coderofsalvation/spadmin","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2Fspadmin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2Fspadmin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2Fspadmin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2Fspadmin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coderofsalvation","download_url":"https://codeload.github.com/coderofsalvation/spadmin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247415976,"owners_count":20935387,"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-10-31T15:08:23.340Z","updated_at":"2025-04-06T00:13:03.656Z","avatar_url":"https://github.com/coderofsalvation.png","language":"JavaScript","funding_links":["https://gumroad.com/l/hGYGh"],"categories":[],"sub_categories":[],"readme":"A wrapper around barebone npm-modules to easify framework-agnostic SPA rest-to-admin interface:\n\n* `Spadmin.page`: Single Page Application (SPA) using [page](https://npmjs.org/package/page)\n* `Spadmin.template`: reactive templates using [transparency](https://npmjs.org/package/transparency) \n* `Spadmin.api`: RESTapi-to-object mapping using [restglue](https://npmjs.org/package/restglue)\n* `Spadmin.loader`: hipster toploaderbar using [nano](https://npmjs.org/package/nanobar)\n* `Spadmin.fetch`: fetch http request polyfill [fetch](https://github.com/github/fetch)\n* `Spadmin.bus` a stateful pubsub bus using [stateful-event](https://npmjs.org/package/stateful-event)\n* `Spadmin.registerElement`: Custom html-elements using polyfill [document-register-element](https://npmjs.org/package/document-register-element)\n\n## Usage \n\n    \u003cscript type=\"text/javascript\" src=\"dist/spadmin.min.js\"\u003e\u003c/script\u003e\n    \u003cstyle type=\"text/css\"\u003e .template { display: none } \u003c/style\u003e\n\n    \u003c!-- menu --\u003e\n    \u003ca href=\"/foo\"\u003efoo\u003c/a\u003e \n\n    \u003c!-- page --\u003e\n    \u003cdiv id=\"page\"\u003e\u003c/div\u003e\n\n    \u003c!-- template --\u003e\n    \u003cdiv id=\"foo\" class=\"template\"\u003e\n      \u003ch1 class=\"title\"\u003e\u003c/h1\u003e\n    \u003c/div\u003e\n\n    \u003cscript\u003e\n      var spadmin = new Spadmin()\n      spadmin.init({\n        content: '#page',   \n        apiurl: 'http://localhost:3000'\n      })\n\n      // render template into #page\n      spadmin.page('/foo', function(){\n        spadmin.renderPage(\"#foo\", {title: \"My Title\"}) \n      })\n\n      // render remote template into #page\n      spadmin.page('/bar', function(){\n        spadmin.renderPage(\"/bar.html\", {title: \"My Title\"}) \n      })\n    \u003c/script\u003e\n\nFor a full example see [simple.html](public/simple.html)\n\n## Template engine: rendering a menu \n\n    \u003cul id=\"#menu\"\u003e\n      \u003cdiv class=\"items\"\u003e\n        \u003cli\u003e\u003ca class=\"item\"\u003e\u003c/a\u003e\u003c/li\u003e\n      \u003c/div\u003e\n    \u003c/ul\u003e\n\n    \u003cscript\u003e\n      spadmin.render( '#menu', [{\n        title: \"Menu\", \n        items: [\n          {item: \"/user/123\",  foo: \"#\"}, \n          {item: \"/users\",  foo: \"#\"} \n        ]\n      },{\n        items: {\n          item: {\n            href: function(params){ return this.foo + params.element.innerHTML; }, \n            onmouseover: \"blink(this)\"\n          }\n        }\n      }])\n    \u003c/script\u003e\n\nFunctions:\n\n    Spadmin.prototype.init(opts)                             // initializes spadmin\n    Spadmin.prototype.executeScripts( el )                   // evaluates scripttags found in el.innerHTML\n    Spadmin.prototype.loadScript(url)                        // loads js-url and evaluates (synchronously)\n    Spadmin.prototype.renderPage(template, data)             // evaluates transparency-template+data into spadmin.pageid\n    Spadmin.prototype.render(template, data, targetid,  cb)  // evaluates transparency-template (url or domid) + data into targetid (dom id string)\n    Spadmin.prototype.renderDOM(domel, data,  targetid,  cb) // evaluates transparency-domtemplate+data into (dom) targetid-string (or replaces domtemplate)\n    Spadmin.prototype.renderHTML(domel, data)                // evaluates transparency-data into domelement (and returns html-string)\n    Spadmin.prototype.update(target, opts)                   // monkeypatchable function to control transitions between renderPage()-calls\n\n## Page Transitions\n\nYou can override the 'update' function like so :\n\n    Spadmin.prototype.update = function (target, opts){\n      this.bus.publish(\"update\",arguments.true)\n      if( opts \u0026\u0026 opts.show != undefined){\n        if( opts.show === false) this.loader.go(0)\n        if( opts.show === true ) this.loader.go(100)\n      }\n      this.bus.publish(\"update/post\",arguments)\n      this.bus.state(\"normal\")\n    }\n\n## REST / api\n\nYou can easily setup an api like this:\n\n    spadmin.api.addEndpoint(\"foo\")\n\nVoila! Now `spadmin.api.foo` gives you these functions:\n  \n    getAll(payload, headers)\n    get(id, payload, headers)\n    post(id, payload, headers)\n    put(id, payload, headers)\n    patch(id, payload, headers)\n    options(id, payload, headers)\n\nExample:\n\n    // request items from api\n    spadmin.api.foo.getAll().then(function(items){    // GET {apiurl}/foo\n\n    })\n\n    spadmin.api.foo.get('134').then(function(item){   // GET {apiurl}/foo/134\n\n    })\n\n\u003e NOTE: pass the apiurl using spadmin.init()\n\nGlobal headers \u0026 hooks could be used for api-specific purposes:\n\n    api.headers['Authorization'] = 'Basic '+btoa( me.data.ui.api.login+\":\"+me.data.ui.api.password )\n    api.headers['Accept']        = 'application/json'\n\n    api.beforeRequest( function(config){\n      // do something\n    })\n\n    api.afterRequest( function(config){\n      // do something\n    })\n\n## Multiple api's \n\n    spadmin.otherapi = new api(\"http://otherapi.com/v1\")\n    spadmin.otherapi.addEndpoint(\"foo\")\n    spadmin.otherapi.foo.getAll().then( function(data){\n      // voila\n    })\n\n\u003e Don't use `spadmin.api.request` or `superagent` directly: you'll lose the sandbox-, beforeRequest() and afterRequest() features.\n\n## States / events / channels\n\nAn eventbus is handy to easily distribute data/events:\n\n    spadmin.bus.state(\"normal\")\n\n    spadmin.bus.debug = true                        // prints publish()-calls to console\n\n    var fooHandler = spadmin.bus.subscribe(\"foo\", function(data,state){\n      if( navigator.onLine \u0026\u0026 state == \"normal\") spadmin.bus.state(\"offline\")\n    })\n    \n    spadmin.bus.subscribe(\"cleanup\", function(data,state){\n      spadmin.bus.unsubscribe(fooHandler)\n    })\n\n    spadmin.bus.publish(\"foo\", {foo:\"bar\"})\n\n    var mybus = new spadmin.bus // or roll your own stateful eventbus\n\nWhy state/bus is handy in an applications:\n\n* unregister/ignore code execution\n* merging datastreams\n\n## Example server \n\n    $ npm install restful-admin-spa request compression express body-parser\n    $ ln -s restful-admin-spa/app.js .\n    $ node app.js\n   \nnow surf to `http://localhost:3000`\n\n## FP / FRP / Reactive programming\n\nFP/FRP is hot atm, and it promotes (arguably) clean code.\nInstead of including frameworks like RSJX/Baconjs, `Spadmin.fp` includes some barebone fp functions to facilitate reactive/streams:\n\n    createEventStream('.button', ['click','mouseover'] )(\n      chain( \n        pick('target.innerHTML'), \n        haltOnEmptyString, \n        handleButton\n      ) \n    )\n\n\u003e The snippet above will fire the chain on any __click__ or __mouseover__ events from all buttons with classname '.button'\n\nThe Functions:\n\n    spadmin.fp.chain()                                      // pass functions as arguments and have them returned as one function\n    spadmin.fp.ncurry(n, f, as)                             // finite curry, ncurry(2, add)(1)(2) will output 3\n    spadmin.fp.eq(str, path)                                // eq(\"foo\") or eq(\"foo\", 'path.to.value') does stringcompare on function input\n    spadmin.fp.curry(f)                                     // anonymous curry, curry(add)(1)(2) will output 3                \n    spadmin.fp.pick(x)                                      // pick value from input\n    spadmin.fp.createEventStream(selector, event_or_events) // allows barebones DOM eventstreams (think baconjs/rxjs)\n    spadmin.fp.mapAsync(arr, done, next)                    // async loop over array\n    spadmin.fp.flipargs(function)                           // returns same function (but with reversed argument-order)\n    spadmin.fp.delay(ms)                                    // will execute 2nd argument (=function) with setTimeout(function,ms)\n    spadmin.fp.throttle(ms)                                 // will execute call, but ignore calls within x milliseconds \n    spadmin.fp.throttleDelay(ms)                            // will only execute last call, and ignore calls within x milliseconds \n\nAn example:\n\n    // your own fp-function\n    var continueWhenStateIsNormal = spadmin.fp.continueWhenStateIsNormal = function(e){\n      if( e \u0026\u0026 spadmin.bus.state() == \"normal\" ) return e\n    }\n\n    var handleButton = function(value){\n      if( !value ) return\n      // do stuff \n    }\n\n    createEventStream('.button', ['click','mouseover'] )(\n      chain( \n        continueWhenStateIsNormal,\n        pick('target.textContent'), \n        handleButton\n      ) \n    )\n\n    spadmin.bus.state(\"normal\")     // event will cascade into handleButton\n    //spadmin.bus.state(\"offline\")  // event will not cascade into handleButton\n\n## Offline sandbox \n\nYou can fake responses (for offline development etc) in 2 ways, like so:\n\n    spadmin.api.addEndpoint(\"foobar\")\n    spadmin.api.addEndpoint(\"foo\")\n\n    spadmin.api.sandboxUrl('/foobar',       {'data':{\"foo\":true}}  ) \n    spadmin.api.sandboxUrl('/myapi',        {'path':\"/js/sandbox\"} )\n    spadmin.api.sandboxUrl( /some.*regex/,  \"/js/foo\" )\n\n    spadmin.api.foobar.getAll().then(function(data){    \n      // data = {\"foo\":true}\n    })\n\n    spadmin.api.foo.getAll().then(function(data){    \n      // data = /js/sandbox/foo/get.json instead of GET {apiurl}/myapi/foo \n    })\n\n\u003e NOTE: {apiurl} is passed using `spadmin.init()`    \n\n## Custom HTML elements\nG\nExample:\n\n    \u003c-- include this after spadmin.min.js --\u003e\n    \u003cscript type=\"text/javascript\" src\"js/element/my-element.js\"/\u003e\n\n    \u003cmy-element\u003e\u003c/my-element\u003e\n\nNow put this inside `js/element/my-element.js` :\n\n    var myElement = function(){\n      this.createdCallback          = function(){}\n      this.attachedCallback         = function(){\n        this.innerHTML        = \"Foobar\"       // set HTMLElement properties\n        this.somevariable  = \"Hello world\"     // store/reference data \n      }\n      this.detachedCallback         = function(){}\n      this.attributeChangedCallback = function( name, previousValue, value ){}\n    }\n    Spadmin.prototype.registerElement(\"my-element\", new myElement )\n\n## Javascript modules / Lazy loading \n\nInstead of cool libs like `requirejs`, you can just register to the bus which is global state \n\nmain.js: \n\n      var spadmin = new Spadmin()\n      spadmin.init({\n        content: '#page',   \n        apiurl: 'http://localhost:3000'\n      })\n\nmodule.js:\n      Spadmin.prototype.bus.subscribe(\"init/post\",  function(spadmin){\n        // do stuff\n      }) \n\n      Spadmin.prototype.bus.subscribe(\"init/post\",  function(spadmin){\n        // do stuff\n      }) \n\n## Initializing \u0026 Global state \n\nBy default spadmin will have a bus which is initialized in global state.\nIf you don't want this (you are running 2 spadmins etc), then initialize like this:\n\n      var spadmin = new Spadmin()\n      spadmin.init({\n        bus: new bus({defaultstate:\"notloggedin\"}) // defaulstate is 'normal' by default\n        content: '#page',   \n        apiurl: 'http://localhost:3000'\n      })\n\n\u003e What happened here is that this spadmin got its own bus (instead of the global one)\n\n## Philosophy\n\n* framework-agnostic javascript micro-framework\n* Theme/CSS agnostic so you can roll your own (or use Metronics/AdminLTE/SB Admin)\n* easy setting up api's\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoderofsalvation%2Fspadmin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoderofsalvation%2Fspadmin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoderofsalvation%2Fspadmin/lists"}