thunderjuice
v0.0.0
Published
Thunder Juice organizes the execution and relationships between function executions. It is based upon structures found in developer language when used to describe complex systems they are developing.
Downloads
3
Readme
#ThunderJuice
Develop As We Think
- ThunderJuice loves you.
- ThunderJuice thinks you're pretty (not just you ladies).
- ThunderJuice cares.
ThunderJuice is a javascript library to assist developers decompose a project’s complex concurrently executing bits. That is, it is a framework for events, callbacks, Deferreds, animations, data fetches, ajax calls, etc. It is particularly good at creating readable, maintainable and extendable code for even the most complex asynchronous and callback-filled systems. It's representation is based upon research on the spoken language and artifacts used by developers to describe complex systems as they develop.
var helloworld = new ThunderJuice.Cn("HelloWorld") // create a concept
.nST([ ["Hello", function() { alert("Hello World"); } ] ]) // create a state and callback
.Init(); // seal it off
helloworld.sy("hello").Trigger(); // trigger it, popping up "Hello World"
###Four-Tiered Representation ThunderJuice is based on Concept-Oriented Design, using four tiers inside a Concept object in an executable and maintainable format. This is about capturing understanding during development as you progressively decompose the system into modules.
This might seem weird at first to a developer; especially one who has mastered their existing tools. So, bear with me. Also, if you have a simple system, ThunderJuice might not suit your needs. Don't worry, ThunderJuice still cares.
##What it looks like This example is simple and demonstrates the basic API calls. It creates two causaly related Concepts with one state each. State machine transitions between states through symbols so the last line triggers a symbol 'foo' which starts the execution.
var result = "";
var foo = new ThunderJuice.Cn("foo")
.nST([ ["Foo"],
function() { result +="foo"; ])
.Init();
var bar = new ThunderJuice.Cn("bar")
.nWT(foo,"Foo","bar") // When state foo:Foo then symbol bar.
.nST([ ["Bar"],
function() { result +="bar"; ])
.Init();
foo.sy("foo").Trigger(); // start execution
// result is “foobar”
NOTE: Traditional state machines require states, symbols and transitions to be defined but ThunderJuice assumes many things to shorten coding times. For instance, it assumes all states have a symbol named after them in all lowercase. It also assumes that symbol transitions to its state, whatever the state of the Concept is (i.e. stateless transitions). These assumptions are easily removed for concepts that are more flowchart-like in behavior.
###Basic API ThunderJuice uses a puny syntax; everything is short (this can make it hard to read... at first.) and it chains function calls so it looks compact and happy. This is purposeful so you can see all your tiers on one page. As your functions become longer, I suggest you used named functions to push code away from the other ThunderJuice tiers.
- Cn(name[,description]) - creates a new Concept. Think of a Concept as a generic unit of development, like an 'object'. The description is assumed to be the 'what' of Envisioned Behavior (see below).
- Cn.nEB(what,how) - adds to the envisioned behavior of the Concept in terms of what it does, and how it does it.
- Cn.nEBw(what) - adds to the 'what' of Envisioned Behavior
- Cn.nEBh(how) - adds to the 'how' of Envisioned Behavior
- Cn.nWT(state,symbol) - short for "new When/Then relationship". When a state in one concept is entered, it triggers a symbol of this concept into itself. This is how concepts respond to each other. This creates a loose coupling allowing for greater extensibility and hopefully good readabilty. NOTICE: for clarity in its parameters, nWT starts with something external and results with an internal symbol.
- Cn.nWT(object,stateoreventname,symbol) - like above, but this allows connecting to supported tools like jQuery and Backbone; more can be added in time
- Cn.nST(array) - short for "new states". Creates an internal state in the flow of this concept. States have a name, which by convention is in camel case, and can have a function to execute that may or may not return a symbol (as a string or the symbol itself. The individual items in the array are: statename[,description[,function]].
- Cn.nSY(array) - short for "new symbols". Creates symbols in this concept. I do not have to create symbols as ThunderJuice creates symbols that are named after each state by default. ThunderJuice assumes a lowercase symbol name goes to the state of the same name. You can change this by setting a concept to be strict. The individual items in the array are: symbolname[,description].
- Cn.nTR(array) - short for "new transition". Creats a transition: from state, to state, by state. The array is therefor packed with arrays containing these three strings. "Assumed transitions" are for symbols to states of the same name (ignoring case), unless you set this concept as strict. Transitions you define take prescedence over assumed transitions.
- Init() - called when the Concept is finished. Must be called.
- Cn.sy(string) - returns the symbol of the given name
- Cn.st(string) - returns the state of the given name
- Cn.nData(object) - creates a data item in the concept with the passed in object's properties.
- Cn.sStrict(ontrans,onsymbolcreation,onincrementalstates) - set ontrans to true, to turn off assummed transitions; set onsymbolcreation to true to not create symbols named after states, set onincrementalstates to true if you want this to allow incomplete states to be created for testing purposes
- Sy.Trigger([data[,fromstring]]) - triggers the given symbol, causing a cascade of events to execute. Data can be passed, along with a string about what triggered this.
Data
You can set data for the concept to manage. I don't know why this is useful right now but it's there.
- Cn.nData({}) - creates the data with the values provided.
- Cn.sData("key","val") - sets the data with the key and value provided.
- Cn.gData("key") - returns the value for the given key
Sets and Dele (short for Delegate Object)
It is nice to be able to say, "When any in this set enter a state X, notify me with my symbol Y." Or, "when I enter state X, notify all in the set with their symbol 'Y'."
- new Set(name[,desc[,objs]]) - creates the Set
- Set.get(index) - returns the item in the set at the given index
- Set.push(x) - pushes the item into the set
- Set.set(index,x) - inserts the item at the index, pushing back others
Dele objects are useful for defining causal relationships that change as to which object they are attached to. For example, a Dele object can be used to name an object being dragged around in drag and drop.
- new Dele(name[,desc]) - creates the Dele
- Dele.set(x), Dele.push(x) - sets the object in this Dele
- Dele.get() - returns the Dele object
- Dele.rem() - removes the Dele object
###Longer Example This shows two interconnected Concepts adding to a string. Notice the order of events, handling When/Then before returned symbols.
var astring = "";
var A = new ThunderJuice
.Cn("A") // create a concept "A"
.nST([ ["State1", function() { astring+="A1"; return "state2"; } ] // create states
,["State2", function() { astring+="A2"; } ] ])
.Init(); // done
var B = new ThunderJuice
.Cn("B") // create a concept "B"
.nWT(A.st("State1"),"state1") // When state State1 in A is reached, then trigger symbol state1 in B.
.nWT(A.st("State2"),"state2") // When state State2 in A is reached, then trigger symbol state2 in B.
.nST([ ["State1", function() { astring+="B1"; return "state2"; } ] // create states
,["State2", function() { astring+="B2"; } ] ])
.Init(); // done
B.sy("state1").Trigger(); // astring is "B1B2"
astring = "";
A.sy("state1").Trigger(); // astring is "A1B1A2B2"
#Flows Ok, so you want to use the flow tier. This is what it's good for:
- It helps you break ideas down. So, it's different looking. Don't think of it like code or you won't like it.
- It's API is designed to match how you decompose ideas. This makes it longer and less direct, but gives you steps.
- It's a process. Learn the process, then decide if you like it.
- It's not for everything. So, don't use it for everything. It is good for flows, anything you would use a Sequence diagram for, or a flowchart.
Flow Functionality
// Flow and Step creation/work
concept.newFlow(_name,_sentence) // creates a new flow in this concept
concept.workFlow(_name,_function/*(_f)*/) // passes the named flow to a user-provided function to work on
concept.getFlow(_key) // returns the flow of the given name or index
flow.newStep(_description) // creates a step with the given spoken-language description
flow.newStepAt(_index,_description) // inserts a step into your flow, push other steps back.
flow.getStep(_key) // gets the step with the provided name.or index
flow.flowConvert() // Turn the flow into executable code. It fails if you have not
// properly worked the steps in the flow.
flow.flowValidate() // Validate each step, to see if it is able to turn into code.
// This catches most errors at the step level, but other errors
// can occur on the interaction between steps. But, hey, this helps
// out and returns an nice error message object.
// Identify parts of step
step.setObject(_object) // This step is in regards to the named concept. If the concept does
// not exist, it will be created for you.
step.setVerbPhrase(_verb,_Apposite) // The verb is the action to be done on the Apposite object.
step.setClause(_object,_verb,_Apposite) // Helper function equivalent to setObject and setVerbPhrase
step.setTarget(_concept) // The concept that the step is acting towards. So, if you said,
// "The customer request a balance from the bank", Bank is the target
// with Customer/O requests/v balance/A from the Bank/T.
step.addWith(_w,_loc) // Storing data associated with this step. The _loc is under construction.
step.addPrepPhrase(_pp,_loc) // Storing extra descriptions with this step. These are prepositional phrases
// usually. The _loc is under construction.
// Action Specifics - determines what all the parts of speech of the step are up to
// external - Object interacts with a Target to do something - requests,returns,manipulates
step.setAsRequests(_f) // ex. Customer/O requests/aS Balance/A from Bank/T.
step.setAsReturns() // ex. Bank/O returns/aS Balance/A to Customer/T.
step.setAsManipulates() // ex. Bank/O checks/a access/A with SecurityServer/T.
// decision - A decision step alters the flow's direction.
step.setAsDecides(_d,_dAlt,_dALT) // ex. SecuirtySever/O decides/a ifAccessToBeGranted/d otherwise noaccess/dAlt
// internal - Object does computation or decision. These are interchangeable, so don't worry about which
// to use as they are just syntactic sugar.
step.setAsSelects() // ex. Bank/O selects/aS Balance/A from ReturnedAccount/T.
step.setAsDoes() // ex. Bank/O does/aS getsBalance/a+A.
step.setAsDone() // ex.
step.setAsUses() // ex. Bank/O uses/aS Balance/A.
step.setAsCreates() // ex. Bank/O creates/aS Balance/A.
// Alternate paths
step.setStart(_step) // For the 1st step in an alt path flow, the starting step.
step.setContinuesTo(_symbol,_state) // For the last step in an alt path, a symbol to transition to a step.
// If the symbol isn't named after a state, you can named the state directly.
// Other Functions
step.getOut() // returns the step as a natural language string, as best it can using the
// parts of speech you have identified.
step.isInternalSpecific() // is this an internal action?
step.displayMe(_i,_msg) // prints out the named parts of this step with _i indentation and optional message.
NOTE: in English, the _concept would be the Noun or Noun phrase while the Object would normally refer to the object of the verb phrase. My appologies to English majors. In coding, Object is usually the target of the activity, so I used that convention and call, what an English major would call the verb's object, an Apposite, as it is apt to the step.
## Flow Walkthrough
It works like this.
####Flow Step 1: List Your Flows
```javascript
var VendingMachine = new ThunderJuice.Concept("VendingMachine")
.newFlow("InsertCoin", "Customer inserts a coin.") // name, and description of the flow
.newFlow("MakeSelection","Customer presses a button to select an item.") // keep
.newFlow("GiveReturn", "Customer asks for money back.");
This step 1 is all about just listing out what you are thinking this Concept does. In this case, we list the external interactions with the VendingMachine, which is basically the customer pushing buttons.
####Flow Step 2: Identify Steps
Now, let's do some work wiht each flow, starting by breaking it into separate steps. The workFlow
function is allows you to define a function that operates on the flow you named. So, below, _f
is the InsertCoin flow we will break into steps.
VendingMachine
.workFlow("InsertCoin", function(_f) {
_f.newStep("Insert coin");
_f.newStep("Check if the vending machine can give a soda now");
_f.newStep("Vend the item");
_f.newStep("return credit");
})
Hmm, not very code-like is it. I mean, seriously, that's just comments inside code blocks right? Well, yes. Yes it, is. We will make the comments into something executable in a second. Don't worry, you'll be able to write code. But first, let's make sure we understand what we are doing. As a coder, you are going to make mistakes, so let's make them early and in something easier to change than code.
####Flow Step 3: Decompose Steps Let's look at the first two steps and see what they tell us.
VendingMachine
.workFlow("InsertCoin",function(_f) {
_f.newStep("Insert coin").setNoun("Customer").setVerbPhrase("inserts","Coin").setAsDoes();
_f.newStep("Check if it can give a soda now").setObject("VendingMachine").setAsDecides("IfCanVend");
_f.newStep("Vend the item");
_f.newStep("return credit");
})
Seriously, 'Noun' and 'VerbPhrase' and 'Does'? What are you talking about? This is about transforming your step into a the following structure:
<Noun> <verb> <Object>. ex. The Customer inserts a coin. ---> Customer/N inserts/v a Coin/O
What about setAsDoes
and setAsDecides
? These create steps named: InsertCoin
and DecideIfCanVend
. Seriously? Seriously.
Also, these tell us what action is being performed and that a decision is being made, respectively. So, Customer.insertsCoin()
needs to be called on entry to the InsertCoin
state and DecideIfCanVend
needs to call VendingMachine.decideIfCanVend()
to learn if it returns a symbol.
##Connecting to jQuery/Backbone Events and Deferreds This integrates with other event-based or callback systems easily. Pretty much everything uses events or callbacks. ThunderJuice just helps to order them into flows and treat them all similarly.
var Button = new ThunderJuice
.Cn("Button") // create a concept "Button"
.nData( { downtime : null } )
.nST([ ["Down", function() { Button.sD("downtime",new Date().getTime()) ] ,["Up"] ])
.nWT($("img#clickme"),"mousedown","down") // connects to jQuery mousedown event on img element
.nWT($("img#clickme"),"mouseup","up") // connects to jQuery mouseup event on img element
.Init(); // done
Connecting to an AJax request / Deferred, handling failed cases easily.
var d = $.ajax({ url: "test.html",... });
var WaitForAjax = new ThunderJuice
.Cn("WaitForAjax")
.nWT(d,"done","start")
.nWT(d,"fail","failed")
.nST([ ["Start"],["Failed"]... ])
.Init();
##Log Event Processing I've found it quite useful to have good logging and tracing of events. There are different event levels and amounts of debugging information. The following are useful functions but the API contains many more features (i.e. read the code if you need more info).
ThunderJuiceQ - This global is always defined when you load ThunderJuice. It is the machinery that processes events.
ThunderJuiceQ.LoggingLevel(TJ.LOG.MIN) - this is the shallowest level and it logs which symbols are being processed. There are three other levels that provide a lot more information. I'm probably the only human that cares about these levels of debugging however.
ThunderJuiceQ.logClear() - resets the log
ThunderJuiceQ.logGet() - returns the log as a string
ThunderJuiceQ.logSnatch() - returns the log as a string and resets it
ThunderJuiceQ.TJLog(string) - add your own comments to the log
ThunderJuiceQ.LOGSPEW - instead of adding to the log, setting LOGSPEW to true immediately prints the log to
ThunderJuiceQ.displayMe() - a quick console log spew of the events qued up
Cn.displayMe() - a quick console log spew of the Concept
##ThunderJuice-Viz A separate project, ThunderJuice-Viz generates graphical representations of your concepts (all tiers but code). It uses GraphViz to generate these and can even popup a graph on the fly using SVG (whch can be clicked on to go away). These commands are assuming you 'required' the project as TJV. Thanks to the Viz.js project!
- TJV.popGraph() - generates an SVG graph overlaying your html, of every concept in existence
- TJV.popGraph(TJV.nGraph([...])) - same as before, but only for the concepts in the array. can be string names.
- TJV.nGraph([...]) - generates a GraphViz Dot representation for your concepts
- TJV.rGraph(_dot,_format,_engine) - renders the GraphViz Dot representation into the format provided using the GraphViz engine provided. Defaults to SVG and some engine that works nicely. Other formats are "plain" for plain text.
Here's the longer example above created with: ThunderJuiceViz.popGraph(["A","B"]).
##How to Program with ThunderJuice
- Step 1: Stop coding. Seriously, stop. Don't premature code.
- Step 2: Name a Concept and write what you are thinking it should do (envisioned behavior)
-- ex.
javascript var concept = new ThunderJuice.Cn(name,description)
- Step 3: Is this concept simple to understand and self-contained? Can you define each state the system flows between? Skip to step 7.
- Step 4: What external factors influence this? these are your causal statements of When/Then and your symbols.
-- ex.
javascript concept.nWT(otherconcept,it's state name, concept's symbol)
- Step 5: Are any of these external factors other Concepts? Go to step 1 for them and return when they are done, linking their states to your causal statements in step 4.
- Step 6: Figure out the functionality as a flowchart, making states and symbols for this that are triggered by external factors or even direct API calls
-- ex.
javascript concept.nST([ *states* ]).nSY([ *symbols* ]).nTR([ *transitions* ])
- Step 7: As needed, write callback functions triggered for each state of this flow. Have them return symbols if the state is a decision state in the flow.
-- ex.
javascript concept.nST([ ["State1", function() { do something } ] ] );
- Step 8: For if/then decisions in the state, break apart a state, returning a symbol in its state function to decide the Concept's flow
-- ex.
javascript concept.nST([ ["DetermineDay","determine if today is an odd/even day", function() { if ( new Date().getDay() % 2 ) return "oddday"; else return "evenday"; } ] ] );
- Step 9: Wish all these steps flowed as smoothly as these directions make them out to be. It comes close most of the time but can get harry in understanding the flow some times and what should be a state versus a variable vs another concept.
##Event-Processing Dictum
An event must be processed in ThunderJuice before any stateful data that a Concept depends on changes.
This is important as proper enforcement of this removes the burden of ordering events from you, the developer. This allows Concepts to continue to function without sideeffects, as the original developer of the system intended, despite change to the system, repurposing of the Concept or reuse in a different system.
NOTE: This is not a perfect science since javascript has variables representing state outside the automata tier, but the closer we get to this, the closer we are to happily functioning, surprise free, Concepts.
###Event Types
##FAQ Please let me know if you have questions so I can add them here.
What is a ThunderJuice.Sy (Symbol) versus a ThunderJuice.Event?
- A symbol is used to change the state of a Concept to a new state, across a transition. When a symbol is triggered, it is said to be an Event (i.e. an instance of that symbol occurring). That Event is then processed in the correct order with all the other events.
I want to do something, i.e. trigger a symbol to advance the Concept, when a $.Deferred is done.
- Just use a function in the Deferred's done() method to fire off a symbol.
var mydef = $.Deferred(); // or var mydef = mymodel.fetch();
var myconcept = ThunderJuice.Cn(...);
...
mydef.done( function() { myconcept.sy("asymbol").Trigger(); } );
##Naming Conventions
classes : [Cap][camel]*
new item, preferred : n[Cap][Cap]
new item, lower level : n[Cap][lower|cap]
- states and symbols can be generated on the fly, or explicitly named. This was a hard decision because of the possiblity for sloppy mistakes, but net devs love this freedom and it leads to shorter code, so I went this way. I could create an option for explicit state/symbol creation in a Concept.
- symbols that are all lowercase of state names, are considered "always go to this state". This is a shortening convention and will probably lead to errors but can again greatly shorten code.
- Why do I use a minimalistic approach to syntax and then have such a long project name? B/c you probably should not type ThunderJuice other than Q initialization and in Concept creation and if you do it more than that, you're doing it wrong. Plus, ThunderJuice is such an over-the-top name... it's awesome.