Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions docs/customization/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam
},
components: {},
wrapComponents: {},
afterLoad: (system) => {}
rootInjects: {},
afterLoad: (system) => {},
fn: {},
}
```
Expand Down Expand Up @@ -364,19 +365,39 @@ const MyWrapComponentPlugin = function(system) {
}
```

##### `rootInjects`

The `rootInjects` interface allows you to inject values at the top level of the system.

This interface takes an object, which will be merged in with the top-level system object at runtime.

```js
const MyRootInjectsPlugin = function(system) {
return {
rootInjects: {
myConstant: 123,
myMethod: (...params) => console.log(...params)
}
}
}
```

##### `afterLoad`

The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered with the system.
The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered.

This interface is used in the core code to attach methods that are driven by bound selectors or actions. You can also use it to execute logic that requires your plugin to already be ready, for example fetching initial data from a remote endpoint and passing it to an action your plugin creates.

This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the system.
The plugin context, which is bound to `this`, is undocumented, but below is an example of how to attach a bound action as a top-level method:

```javascript
const MyMethodProvidingPlugin = function() {
return {
afterLoad(system) {
// at this point in time, your actions have been bound into the system
// so you can do things with them
system.myMethod = system.exampleActions.updateFavoriteColor
this.rootInjects = this.rootInjects || {}
this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor
},
statePlugins: {
example: {
Expand Down
3 changes: 2 additions & 1 deletion src/core/plugins/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import * as specWrapActionReplacements from "./spec-wrap-actions"
export default function() {
return {
afterLoad(system) {
system.initOAuth = system.authActions.configureAuth
this.rootInjects = this.rootInjects || {}
this.rootInjects.initOAuth = system.authActions.configureAuth
},
statePlugins: {
auth: {
Expand Down
31 changes: 24 additions & 7 deletions src/core/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,11 @@ export default class Store {
if(rebuild) {
this.buildSystem()
}

if(Array.isArray(plugins)) {
plugins.forEach(plugin => {
if(plugin.afterLoad) {
plugin.afterLoad(this.getSystem())
}
})

const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem())

if(needAnotherRebuild) {
this.buildSystem()
}
}

Expand Down Expand Up @@ -328,6 +326,25 @@ function combinePlugins(plugins, toolbox) {
return {}
}

function callAfterLoad(plugins, system, { hasLoaded } = {}) {
let calledSomething = hasLoaded
if(isObject(plugins) && !isArray(plugins)) {
if(typeof plugins.afterLoad === "function") {
calledSomething = true
plugins.afterLoad.call(this, system)
}
}

if(isFunc(plugins))
return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething })

if(isArray(plugins)) {
return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething }))
}

return calledSomething
}

// Wraps deepExtend, to account for certain fields, being wrappers.
// Ie: we need to convert some fields into arrays, and append to them.
// Rather than overwrite
Expand Down
87 changes: 85 additions & 2 deletions test/core/system/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,13 +684,13 @@ describe("bound system", function(){
})

describe("afterLoad", function() {
it("should call an plugin's `afterLoad` method after the plugin is loaded", function() {
it("should call a plugin's `afterLoad` method after the plugin is loaded", function() {
// Given
const system = new System({
plugins: [
{
afterLoad(system) {
system.wow = system.dogeSelectors.wow
this.rootInjects.wow = system.dogeSelectors.wow
},
statePlugins: {
doge: {
Expand All @@ -705,6 +705,89 @@ describe("bound system", function(){
]
})

// When
var res = system.getSystem().wow()
expect(res).toEqual("so selective")
})
it("should call a preset plugin's `afterLoad` method after the plugin is loaded", function() {
// Given
const MyPlugin = {
afterLoad(system) {
this.rootInjects.wow = system.dogeSelectors.wow
},
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "so selective"
}
}
}
}
}

const system = new System({
plugins: [
[MyPlugin]
]
})

// When
var res = system.getSystem().wow()
expect(res).toEqual("so selective")
})
it("should call a function preset plugin's `afterLoad` method after the plugin is loaded", function() {
// Given
const MyPlugin = {
afterLoad(system) {
this.rootInjects.wow = system.dogeSelectors.wow
},
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "so selective"
}
}
}
}
}

const system = new System({
plugins: [
() => {
return [MyPlugin]
}
]
})

// When
var res = system.getSystem().wow()
expect(res).toEqual("so selective")
})
it("should call a registered plugin's `afterLoad` method after the plugin is loaded", function() {
// Given
const MyPlugin = {
afterLoad(system) {
this.rootInjects.wow = system.dogeSelectors.wow
},
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "so selective"
}
}
}
}
}

const system = new System({
plugins: []
})

system.register([MyPlugin])

// When
var res = system.getSystem().wow()
expect(res).toEqual("so selective")
Expand Down