npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

spadmin

v1.2.59

Published

SPA rest-to-admin interface RAD-style in vanilla js

Downloads

70

Readme

A wrapper around barebone npm-modules to easify framework-agnostic SPA rest-to-admin interface:

  • Spadmin.page: Single Page Application (SPA) using page
  • Spadmin.template: reactive templates using transparency
  • Spadmin.api: RESTapi-to-object mapping using restglue
  • Spadmin.loader: hipster toploaderbar using nano
  • Spadmin.fetch: fetch http request polyfill fetch
  • Spadmin.bus a stateful pubsub bus using stateful-event
  • Spadmin.registerElement: Custom html-elements using polyfill document-register-element

Usage

<script type="text/javascript" src="dist/spadmin.min.js"></script>
<style type="text/css"> .template { display: none } </style>

<!-- menu -->
<a href="/foo">foo</a> 

<!-- page -->
<div id="page"></div>

<!-- template -->
<div id="foo" class="template">
  <h1 class="title"></h1>
</div>

<script>
  var spadmin = new Spadmin()
  spadmin.init({
    content: '#page',   
    apiurl: 'http://localhost:3000'
  })

  // render template into #page
  spadmin.page('/foo', function(){
    spadmin.renderPage("#foo", {title: "My Title"}) 
  })

  // render remote template into #page
  spadmin.page('/bar', function(){
    spadmin.renderPage("/bar.html", {title: "My Title"}) 
  })
</script>

For a full example see simple.html

Template engine: rendering a menu

<ul id="#menu">
  <div class="items">
    <li><a class="item"></a></li>
  </div>
</ul>

<script>
  spadmin.render( '#menu', [{
    title: "Menu", 
    items: [
      {item: "/user/123",  foo: "#"}, 
      {item: "/users",  foo: "#"} 
    ]
  },{
    items: {
      item: {
        href: function(params){ return this.foo + params.element.innerHTML; }, 
        onmouseover: "blink(this)"
      }
    }
  }])
</script>

Functions:

Spadmin.prototype.init(opts)                             // initializes spadmin
Spadmin.prototype.executeScripts( el )                   // evaluates scripttags found in el.innerHTML
Spadmin.prototype.loadScript(url)                        // loads js-url and evaluates (synchronously)
Spadmin.prototype.renderPage(template, data)             // evaluates transparency-template+data into spadmin.pageid
Spadmin.prototype.render(template, data, targetid,  cb)  // evaluates transparency-template (url or domid) + data into targetid (dom id string)
Spadmin.prototype.renderDOM(domel, data,  targetid,  cb) // evaluates transparency-domtemplate+data into (dom) targetid-string (or replaces domtemplate)
Spadmin.prototype.renderHTML(domel, data)                // evaluates transparency-data into domelement (and returns html-string)
Spadmin.prototype.update(target, opts)                   // monkeypatchable function to control transitions between renderPage()-calls

Page Transitions

You can override the 'update' function like so :

Spadmin.prototype.update = function (target, opts){
  this.bus.publish("update",arguments.true)
  if( opts && opts.show != undefined){
    if( opts.show === false) this.loader.go(0)
    if( opts.show === true ) this.loader.go(100)
  }
  this.bus.publish("update/post",arguments)
  this.bus.state("normal")
}

REST / api

You can easily setup an api like this:

spadmin.api.addEndpoint("foo")

Voila! Now spadmin.api.foo gives you these functions:

getAll(payload, headers)
get(id, payload, headers)
post(id, payload, headers)
put(id, payload, headers)
patch(id, payload, headers)
options(id, payload, headers)

Example:

// request items from api
spadmin.api.foo.getAll().then(function(items){    // GET {apiurl}/foo

})

spadmin.api.foo.get('134').then(function(item){   // GET {apiurl}/foo/134

})

NOTE: pass the apiurl using spadmin.init()

Global headers & hooks could be used for api-specific purposes:

api.headers['Authorization'] = 'Basic '+btoa( me.data.ui.api.login+":"+me.data.ui.api.password )
api.headers['Accept']        = 'application/json'

api.beforeRequest( function(config){
  // do something
})

api.afterRequest( function(config){
  // do something
})

Multiple api's

spadmin.otherapi = new api("http://otherapi.com/v1")
spadmin.otherapi.addEndpoint("foo")
spadmin.otherapi.foo.getAll().then( function(data){
  // voila
})

Don't use spadmin.api.request or superagent directly: you'll lose the sandbox-, beforeRequest() and afterRequest() features.

States / events / channels

An eventbus is handy to easily distribute data/events:

spadmin.bus.state("normal")

spadmin.bus.debug = true                        // prints publish()-calls to console

var fooHandler = spadmin.bus.subscribe("foo", function(data,state){
  if( navigator.onLine && state == "normal") spadmin.bus.state("offline")
})

spadmin.bus.subscribe("cleanup", function(data,state){
  spadmin.bus.unsubscribe(fooHandler)
})

spadmin.bus.publish("foo", {foo:"bar"})

var mybus = new spadmin.bus // or roll your own stateful eventbus

Why state/bus is handy in an applications:

  • unregister/ignore code execution
  • merging datastreams

Example server

$ npm install restful-admin-spa request compression express body-parser
$ ln -s restful-admin-spa/app.js .
$ node app.js

now surf to http://localhost:3000

FP / FRP / Reactive programming

FP/FRP is hot atm, and it promotes (arguably) clean code. Instead of including frameworks like RSJX/Baconjs, Spadmin.fp includes some barebone fp functions to facilitate reactive/streams:

createEventStream('.button', ['click','mouseover'] )(
  chain( 
    pick('target.innerHTML'), 
    haltOnEmptyString, 
    handleButton
  ) 
)

The snippet above will fire the chain on any click or mouseover events from all buttons with classname '.button'

The Functions:

spadmin.fp.chain()                                      // pass functions as arguments and have them returned as one function
spadmin.fp.ncurry(n, f, as)                             // finite curry, ncurry(2, add)(1)(2) will output 3
spadmin.fp.eq(str, path)                                // eq("foo") or eq("foo", 'path.to.value') does stringcompare on function input
spadmin.fp.curry(f)                                     // anonymous curry, curry(add)(1)(2) will output 3                
spadmin.fp.pick(x)                                      // pick value from input
spadmin.fp.createEventStream(selector, event_or_events) // allows barebones DOM eventstreams (think baconjs/rxjs)
spadmin.fp.mapAsync(arr, done, next)                    // async loop over array
spadmin.fp.flipargs(function)                           // returns same function (but with reversed argument-order)
spadmin.fp.delay(ms)                                    // will execute 2nd argument (=function) with setTimeout(function,ms)
spadmin.fp.throttle(ms)                                 // will execute call, but ignore calls within x milliseconds 
spadmin.fp.throttleDelay(ms)                            // will only execute last call, and ignore calls within x milliseconds 

An example:

// your own fp-function
var continueWhenStateIsNormal = spadmin.fp.continueWhenStateIsNormal = function(e){
  if( e && spadmin.bus.state() == "normal" ) return e
}

var handleButton = function(value){
  if( !value ) return
  // do stuff 
}

createEventStream('.button', ['click','mouseover'] )(
  chain( 
    continueWhenStateIsNormal,
    pick('target.textContent'), 
    handleButton
  ) 
)

spadmin.bus.state("normal")     // event will cascade into handleButton
//spadmin.bus.state("offline")  // event will not cascade into handleButton

Offline sandbox

You can fake responses (for offline development etc) in 2 ways, like so:

spadmin.api.addEndpoint("foobar")
spadmin.api.addEndpoint("foo")

spadmin.api.sandboxUrl('/foobar',       {'data':{"foo":true}}  ) 
spadmin.api.sandboxUrl('/myapi',        {'path':"/js/sandbox"} )
spadmin.api.sandboxUrl( /some.*regex/,  "/js/foo" )

spadmin.api.foobar.getAll().then(function(data){    
  // data = {"foo":true}
})

spadmin.api.foo.getAll().then(function(data){    
  // data = /js/sandbox/foo/get.json instead of GET {apiurl}/myapi/foo 
})

NOTE: {apiurl} is passed using spadmin.init()

Custom HTML elements

G Example:

<-- include this after spadmin.min.js -->
<script type="text/javascript" src"js/element/my-element.js"/>

<my-element></my-element>

Now put this inside js/element/my-element.js :

var myElement = function(){
  this.createdCallback          = function(){}
  this.attachedCallback         = function(){
    this.innerHTML        = "Foobar"       // set HTMLElement properties
    this.somevariable  = "Hello world"     // store/reference data 
  }
  this.detachedCallback         = function(){}
  this.attributeChangedCallback = function( name, previousValue, value ){}
}
Spadmin.prototype.registerElement("my-element", new myElement )

Javascript modules / Lazy loading

Instead of cool libs like requirejs, you can just register to the bus which is global state

main.js:

  var spadmin = new Spadmin()
  spadmin.init({
    content: '#page',   
    apiurl: 'http://localhost:3000'
  })

module.js: Spadmin.prototype.bus.subscribe("init/post", function(spadmin){ // do stuff })

  Spadmin.prototype.bus.subscribe("init/post",  function(spadmin){
    // do stuff
  }) 

Initializing & Global state

By default spadmin will have a bus which is initialized in global state. If you don't want this (you are running 2 spadmins etc), then initialize like this:

  var spadmin = new Spadmin()
  spadmin.init({
    bus: new bus({defaultstate:"notloggedin"}) // defaulstate is 'normal' by default
    content: '#page',   
    apiurl: 'http://localhost:3000'
  })

What happened here is that this spadmin got its own bus (instead of the global one)

Philosophy

  • framework-agnostic javascript micro-framework
  • Theme/CSS agnostic so you can roll your own (or use Metronics/AdminLTE/SB Admin)
  • easy setting up api's