-
Notifications
You must be signed in to change notification settings - Fork 2
Mixin Guide
QuickConnect has several environments that are presented to the developer when building their program. The options
argument in the QuickConnect constructor has a mixins
object where you can define functions to augment the functionality of each of these levels.
The mixins object has four fields: base
, isolate
, command
, and control
. These can optionally be defined by the developer as functions. These functions are called in the constructor of the environment object that is this
in the various environments of QC.
So when you do
var runsmthng = qc.command('run something')
or
qc.command('run something', function(){
var runsmthng = this
})
the variable runsmthng
is an instance of QCCommandEnvironment
. QCCommandEnvironment
has a prototype that defines the standard methods of runsmthng
: valcf
, dcf
, vcf
, and dstack
.
The constructor actually looks like this:
function QCCommandEnvironment(fn) {
if (fn) {
fn.call(this)
}
}
If you make a function at mixins.command
, it will be passed to this constructor. This pattern is the same for all the mixins.
-
base
is the QC instance you get fromnew QuickConnect(options)
-
isolate
is the object you get fromvar iso = qc.isolate('iso')
-
command
is the object you get fromvar com = (iso || qc).command('com')
-
control
is the objectthis
in a control function
Here is an example from the QC tests that shows aliasing of the basic commands and attributes:
mixin = new QuickConnect({
mixins: {
base:function () {
this.namespace = this.isolate
},
isolate:function () {
this.addNamespace = this.isolate
this.map = this.command
},
command:function () {
this.validate = this.valcf
this.data = this.dcf
this.view = this.vcf
},
control:function () {
this.go = this.STACK_CONTINUE
this.asyncGo = this.asyncStackContinue
this.wait = this.WAIT_FOR_DATA
}
}
})
module.exports = mixin
...
mixin.namespace('mixin')
.addNamespace('level 2')
.map('command')
.validate(function(){return true})
.data(function(){return this.go})
.data(function(data,qc){
setTimeout(function(){qc.asyncGo()}, 50)
return qc.wait
})
.view(function(data,qc){
data.yay = true
return qc.go
})
qc = new QuickConnect({
mixins: {
command:function () {
this.isSet = function(key){
this.valcf(function(data,qc){
return data[key] || "data."+key+" is not set"
})
}
this.isSet = function(key,type){
this.valcf(function(data,qc){
var hold = data[key]
return hold && hold.constructor == type || "data."+key+" is not a "+type.name
})
}
this.set = function(key,gen){
this.dcf(function(data,qc){
data[key] = gen(data)
return qc.STACK_CONTINUE
})
}
this.if = function(check, func){
this.dcf(function(d,qc){
if(check(d)){
return func.apply(this,arguments)
} else {
return qc.STACK_CONTINUE
}
})
}
}
}
})
...
qc.command('authenticate')
.isSet('username')
.isSet('password')
.is('id',Number)
.set("time",function(){return new Date})
.dcf(contactServer)
.if(response_2xx, praise_user('you are the bestest!'))
Here, in just a few lines of javascript, I was able to pull a bunch of flow control logic out of my functions and put it into the QC environment. Now I don't have to write a function that says 'praise user if 2xx response', I just wrote a praise_user function, and it I let QC determine if it should be called.
You can see on the last line that I called praise_user
--It will generate a function that will be the second argument for the if
mixin. Combining function generators with extracted control flow logic has proven to be an extreemly fast and convenient way to program. By writting my own mixins and generating functions for different cases, I can quickly tear down a stack of functions and rebuild it without needing to change the functions; just what is around it.
With mixins, QuickConnect will always be exactly what you need it to be.
If you subclass QuickConnect, you will be able to alter what is available on the QuickConnect instances, but you can't alter the environments that way.
Using the mixins is like subclassing the entire system (or parts of it) at once.
The mixins are intended to do more than just let you alias things so the standard QC vocab does not annoy you; they let you change the way you build things with qc to match your needs. If you are building a server, you could use methods like pathSection('users')
and pathTerminator(':name')
to define routes. If you don't want to return this.WAIT_FOR_DATA
you can give yourself a method called async(someFunc)
that wraps your function in one that will return the wait flag for you.
If you find that you have to call multiple async functions based on a list, you could define a helper function that tracks if everything has finished before moving on. If you want to log everything that happens, you could build automatic logging right into every control function.
The possibilities are endless.