ApiBlaze: SPAC Framework Refactoring

Phase 0: Setup

  • Install the spac NPM package
  • Create the following directory structure
src
└── actions
│ └── SearchApiSpecAction.js
└── components
├── ApiSearchComponent.js
└── ApiSearchResultsComponent.js
└── pages
│ ├── IndexPage.js
│ ├── SearchApiElementsPage.js
└── index.js
  • Create an index.js file with the following content:
import { Controller } from 'spac'const controller = new Controller()controller.init()

Phase 1: Refactor Components

import { handleApiSearch } from '../controller.js'let state = {}
let _$root = undefined
function updateState (newState) {
state = { ...state, ...newState }
console.log('new state', state)
}
function getState () {
return state
}
function render (args) {
const html = `
<h2>Load an API</h2>
<input type="text" class="api-search-bar" id="api-search-bar" value="" spellcheck="false">
<div id="api-search-results" class="api-search-results">
`
return html
}
function mount ($root, ...args) {
_$root = $root
$root.innerHTML = render(args)
document
.getElementById('api-search-bar')
.addEventListener('keydown', e => handleKeydown(e))
// ...
}
function refresh (args) {
mount(_$root, args)
}
export { mount, getState, refresh }
import { Component } from 'spac'export default class SearchBarComponent extends Component {
render = () => {
return `
<h2>Load an API</h2>
<input type="text" class="api-search-bar" id="api-search-bar" value="${this.getState().apiSearchQuery}" spellcheck="false">
<div id="api-search-results" class="api-search-results">
`
}
mount () {
super.mount()
document
.querySelector('#api-search-query')
.addEventListener('keyup', e => this.handleKeyUp(e))
}
}
  • Create a class that extends Components
  • Move the content of the html constant to the render() functions
  • Move behavioral logic from the mount() function to the mount() instance function

Phase 2: Refactor Actions

  • Identify all external API function calls and calls to the backend
  • Encapsulate the call in an action
  • In the dependent component, import and execute this action

Phase 3: Refactor Pages

import { mount as mountSearchBar } from '../components/searchBar.js'function layout () {
const html = `
<section class="search-wrapper" id="search-wrapper">
<h2 id="heading-api-name">Search</h2>
<div id="search-mode"></div>
<div id="search-bar">
<div class="input-wrapper" id="input-wrapper"></div>
</div>
</section>
`
return html
}
function render ($domRoot) {
$domRoot.innerHTML = layout()
mountSearchMode(document.getElementById('search-mode'))
}
export { render }
import { Page } from 'spac'
import ApiSearchBarComponent from '../components/ApiSearchBarComponent.js'
export default class IndexPage extends Page {
constructor (rootDom) {
super(rootDom)
this.addComponents(new ApiSearchBarComponent('#api-search-spec'))
}
render = () => {
return `
<h1>ApiBlaze Explorer</h1>
<section class='api-search-page'>
<div id='api-search-spec' class='api-search-spec'></div>
</section>
`
}
mount () {
super.mount()
document.querySelector('button').addEventListener('click', () => {
// ....
})
return this
}
}
  • Move the pages HTML to the render() method
  • Define additional DOM manipulations, like adding event handlers, in the mount() method

Phase 4: Start and Host the Application

"scripts": {
"bootstrap": "node --input-type=module --experimental-modules --eval \"import {bootstrap} from 'spac'; bootstrap('./src')\""
}
import inventory from './inventory.json'const controller = new Controller({ inventory })

Review: ApiBlaze Project Requirements

  • ✅ SEA01 — Search for APIs by Keyword
  • ✅ SEA02 — Show search results in a popup
  • ✅ SEA03 — Select a search results with arrow keys, enter and mouse click
  • ✅ FRAME01 — Controller & Routing
  • ✅ FRAME02 — Stateful Pages & Components
  • ✅ FRAME03 — Actions
  • ✅ FRAME04 — Optimized Bundling
  • ✅ TECH01 — Use PlainJS & Custom Framework
  • ✅ TECH02 — Use SAAS for CSS

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store