Synopsis: Warden([inheritor], [config])
or Warden.extend([inheritor], [config])
Description: Extends inheritor
with emit
, listen
, unlisten
and stream
methods. And returns extented object. If inheritor is empty then returns extended empty JS object. Inheritor can be function, object or array. If inheritor is an atomic value (e.g. boolean, string and number type) Warden
returns stream that contains given value.
Extension method is the base method of Warden, it takes objects/constructors/arrays and returns them extended by methods that implements Pub/Sub pattern. After extension you can use methods listen
, unlisten
, emit
, stream
.
If extending object already has Pub/Sub methods then Warden uses them. By default event emitting/triggering objects detects by methods addEventListener
, attachListener
and on
, but you can configure them too.
Names looks weird, but it's make to decrease namespace conflict situations, if you sure that there will no be any conflicts you can configure names of methods by property names
.
You can configure next terms:
emitter
- Name of native emitter function if you have such. For example $.trigger()
for jQuery. Use it if your framework has already have event emitter method and you creating emittor from object that contains native emittor. If you use jQuery you can't dont use this configuration item because Warden automaticaly find it.listener
- Name of native listener function if you have such. For example $.on() for jQuery, or .addEventListener for native browser's DOM API, both of them you can don't configure. By default equals addEventListener
names
- Object of names you want to take your mathods. Can contain next properties: emit
, listen
, stream
, unlisten
. Values are adequate to Warden's mehthods.Synopsis: object.listen(eventType, callback)
Description: Adds to the object
handler of events with type of eventType
.
This method simliar to native addEventListener
, first argument is event type (string) second is callback.
Warden also provides RegEx notation acces to event types, you can use regular expression-like type and set handler to all events which type will match to regex. RegEx notation works also for stream
.
object.listen('greet', function(greeting){
console.log(greeting);
});
object.emit('greet', 'Hello World!');
// --> Hello World!
// With regexps
object.listen('get:*', function(e){
console.log(e);
});
object.emit('get:one', 'foo');
//--> foo
object.emit('get:two', 'bar');
//--> bar
Note: you can't use RegEx notation to subscribe to the events emitted from native event emittind system (e.g. DOM, Node Event Emitter, ...).
It means you cant do: window.listen('mouse*', handler)
to subsribe all mouse events. But, you can subscribe to the different types of events by separating types by comma:
Warden(document);
document.listen('mousemove, mousedown, mouseup, mouseenter, mouseleave', function(){
console.log('Hey! I detected mouse events!');
});
Synopsis: object.unlisten(eventType, callback)
Description: Removes from object
handler of events with type of eventType
with name callback
.
Similiar to native removeEventListener
, when second arguments can be function or string which is name of handler.
Warning: you can't use RegEx notation in unlisten
method.
object.listen('x', function callback(v){
console.log(v);
});
object.emit('x', 1);
// --> 1
// Now removing handler
object.unlisten('x', 'callback');
object.emit('x', 1);
// nothing happens
Synopsis: object.emit(eventType, eventData)
or object.emit(event)
Description: Fires event on object
.
Similiar to jQuery's trigger
method. If you use map as single argument then event type is on type
property and it's required! Note that emit
- is method of object that emits event which you will should listen on same object.
object.listen('custom', function(e){
console.log(e);
})
object.emit('custom', 'value');
// -> value
object.emit({
type: 'custom',
value: 'value'
});
// -> {type: 'custom', value: 'value' }
Synopsis: object.stream(eventType, [context])
Description: Creates a stream of events by given type.
This method listens all events of given type and evaluates all streams conjuncted with adequate object (look DataBuses
). This method returns first DataBus associated with stream.
var clicks = $(document).stream('click');
clicks.listen(function(event){
console.log('Clicket at: X=' + event.clientX + ' Y=' + event.clientY);
})
By default context of evaluation is object self. Bus you can set context as second argument. Also you can use RegEx notation to set the types of stream.
Synopsis: Warden.Stream([creator], [context], [isStrict])
.
Description: Creates stream of data.
Warden.Stream
gives simple interface to create streams from any data source. Streams can contain data of any type.
var clicks = $(document).stream('click');
Can be rewrited as:
var clicks = Warden.Stream(function(fire){
$(document).on('click', fire);
}, $(document))
Obvious, nobody will do that, but this example shows how to use Warden.Stream
More common example:
var seconds = Warden.Stream(function(fire){
setInterval(fire, 1000);
})
var response = Warden.Stream(function(fire){
$.get('url', fire);
})
This method allows you to wrap all changes in system's state as data transmissions (synchronious or not), so you can manipulate your system easy with pure function without any side effects in while processing data.
Althrough, it's not required to use functional thinking while developing big applications with Warden. Warden gives a way to develop simple modules where you can incapsulate simple and predictive state. These modules can use dirty-functions, mutable state and not-protected data-sharing, and after, you can combine such simple components with each others with pure functions. It's a trade-off between true functional reactive programming and traditional event-driven style in imperative languages.
So context in Warden allows to implement these modules. In Warden.Stream
, second argumens is context in creator
function
var http = {};
var gets = Warden.Stream(function(fire){
this.get = function(url){
$.get(url).always(fire);
}
}, http);
gets.log();
http.get(location.href); //will logs curent document's markup to console
Or more idiomatic example, where we creating realy module with mutable state:
(Look at Away example in tutorials)
var pulsar = {};
var pulses = Warden.Stream(function(fire){
var timer;
this.start = function(interval){
timer = setInterval(fire, interval)
}
this.clear = function(){
clearInterval(timer);
}
this.start(1000);
}, pulsar);
pulses.map('PULSE').log();
pulsar.clear(); // stops pulses
pulsar.start(100); // runs pulses again with interval 100ms
isStrict
parameter configures would context creating strong. To avoid context overwriting use true
in third parameter.
var context = { some_value : ''};
Warden.Stream(function(){
this.some_value = 12;
}, context, true);
// -> ERROR!
// Coincidence: property: 'some_value' is already defined in stream context! Object {some_value: ""}
When streams transmiting values then they collect recieved value to the temporary storage of values stream.data.takes
.
var a = Warden(0);
// a - is a stream with 0 as last taken value:
a.data.last;
-> 0
a.value;
-> a
When you changes value of stream by stream.value
you makes stream evaluate with new value.
var stream = Warden.Stream().filter(10).watch();
// stream that can transmit only value equals to 10
stream.value;
-> null
stream.value = 20;
stream.value;
-> null
stream.value = 10;
stream.value;
-> 10
This method is deprecated and currently is an alias for Warden.Stream
Synopsis: stream.fire(value, [context])
.
Description: Pushes value to the stream. It doesn't fire all conjuncted and children streams, just given. The fastest method.
Synopsis: stream.listen(callback)
.
Description: Subsribes callback to the stream.
This methods creates listener of the given stream. Remember that in callback
context variable (this
) refered to the stream's evaluation context.
var context = {x: 100};
var object = Warden();
var ticks = object.stream('tick', context);
ticks.listen(function(data){
console.log('Transmitted data is:', data)
console.log('Context is:', this)
});
object.emit('tick', {value : '*_*'})
// -> Transmitted data is: Object { value: '*_*' }
// -> Context is: Object { x: 100 }
Synopsis: stream.mute([callback])
.
Description: Unsubsribes callback to the stream. callback
can be string or function.
This method removes from stream handlers given callback.
function log(e){
console.log(e);
}
// subsribing
stream.listen(log);
stream.mute('log');
//or
stream.mute(log);
Note that callback finds by name
property, so you can remove wrong function if you use callbacks with same name.
function callback(data){
// do something with data
}
stream.listen(callback);
// Next examples are totaly equaivalent.
stream.mute('callback');
stream.mute(callback);
stream.mute(function callback(){});
Synopsis: stream.clear()
.
Description: Unsubsribes all handlers from stream.
Warning: it can brokes all inheritance and logical conjunctions between streams. Use it onlu when you sure that it will not break you code.
Synopsis: stream.log([string])
.
Description: Alias for logging function with .listen
. Logs to console transmitted data or string
(if it given).
//.log equals to
stream.log(); //same to
stream.listen(function log(e){
console.log(e);
});
stream.log('hey'); // same to
stream.listen(function log(){
console.log('hey');
});
You can "unlog" with stream.mute('log')
.
Synopsis: stream.toggle(fn1, fn2)
.
Description: Unsubsribes to stream two handlers which calls after each other.
var stream = Warden.Stream();
stream.toggle(
function(){
console.log('Fired ODD times');
},
function(){
console.log('Fired EVEN times');
});
stream.fire()
// -> Fired ODD times
stream.fire()
// -> Fired EVEN times
stream.fire()
// -> Fired ODD times
Synopsis: stream.bindTo(object, [property])
.
Description: Binds stream's value to the object with a given signature.
This method provides syntax sugar for binding side-effects to the stream evaluation on recieving value. Every bindTo
signature can be replaced with listen
.
With bindTo
you can create and use side-effect fast and efficient. Behavior of handler depends of binding method's arguments signature.
Same as stream.listen(callback)
stream.bindTo(alert); // making side-effect alerting dialog
stream.bindTo(console, 'log');
// calling console['log'] with recieved
stream.bindTo(window, 'alert');
//calls window.alert with recived data
stream.bindTo($('body'), 'html');
// calls $('body').html with recieved data
var parent = {child: {anotherChild: null }};
stream.bindTo(parent, 'child/anotherChild');
stream.fire('LOL');
console.log(parent.child);
// -> Object: { anotherChild : 'LOL' }
var o = {a: 1, b : {c: 2 }};
stream.bindTo(o, a);
stream.fire('LOL');
console.log(o);
// -> Object : { a: 'LOL', b : Object : { c : 2} }
stream.bindTo(o, b);
stream.fire('WUT');
console.log(o);
// -> Object : { a: 'WUT', b : 'WUT' }
Note: when you use object/string signature Warden uses eval
Synopsis: stream.map(mapper)
.
Description: Returns new stream mapped by mapper
signature.
// creates stream from integers stream where all values mapped with given function
var stringIntegers = integers.map(function(integer){
return integer.toString();
});
Warden gives a lot of syntax sugar for map
function. So common task to take a property, or call method of given data, or call contex's method can be solved pretty fast:
//take a property
var values = stream.map(function(data){
return data.value;
});
//equals to
var values = stream.map('.value');
// Call a method:
var namesArray = namesStream.map(function(names){
return names.toArray();
});
var namesArray = namesStream.map('.toArray()');
// @-notation
// @-refers to the context variable
stream.map('@') //same to
stream.map(function(){
return this;
})
// so
var scrolls = $(window).stream('scroll');
var scrollTops = scrolls.map('@scrollTop()');
// also Warden can use alias object for map
stream.map({
newName : '.oldName',
justName : 'justValue',
newResult : '.method()',
thisVar : '@',
thisMethodResult : '@value()'
})
// same as :
stream.map(function(data){
return {
newName : data.oldName,
justName : 'justValue',
newResult : data.method(),
thisVar : this,
thisMethodResult : this.value(),
}
});
// also you can call map with array of parameters
stream.map(['.name', 'justValue', '.method()', '@', '@value()'])
// same as :
stream.map(function(data){
return [ data.name, 'justValue', data.method(), this, this.value()]
});
When you use short-syntax mapper as value (as example stream.map("@x + @y")
) Warden uses eval
to map your stream.
Synopsis: stream.get(route)
.
Description: Returns stream mapped with function which get's recieved data's property with access route equals to route
In this function route
is a string which represents place of property (or array's item) in given object. For example:
stream.get("foo/bar/[0]/baz"); // is same to
stream.map(function(object){
return object.foo.bar[0].baz;
});
Note: arrays should derive with object properties also with /
symbol
Synopsis: stream.nth(integer)
.
Description: Alias for strem.map
with function which takes recived objects (which must be iterable) n-th element.
stream.nth(0); // is same to
stream.map(function(object){
return object[0];
});
Synopsis: stream.filter([filterFn])
or stream.filter([value])
.
Description: Returns new filtered stream.
With functions.
Filtering function should return only boolean type. If function returns 0
or ""
it mean false.
var odds = integers.filter(function(number){
return number % 2 == 1;
});
var readFile;
// stream of read tries
var reads = Warden.Stream(function(fire){
readFile = function(url){
fs.readFile(url, function(err, data){
var success = err ? false : true;
fire({
file: data,
success : success
});
});
}
});
//stream of readed files
var files = reads.filter(function(response){
return respomse.success;
});
With values
If you use simple value then Warden creates filter function that compares recieved value with given. Comparing implemented with Warden.configure.cmp
function, which just compares given values with ===
operator.
stream.filter(true); // same to
stream.filter(function(e){
return Warden.configure.cmp(e, true);
});
// same to
stream.filter(function(e){
return e == true;
});
Synopsis: stream.reduce([init], fn)
.
Description: Scan stream and calls fn
with arguments of reduced value and current value.
var stream = Warden([1,2,3,4,5,6,7,8,9]).sequantiallty(1000);
stream.reduce(function(a, b){
return a + b;
}).log();
// -> 1
// -> 3
// -> 6
// -> 10 ... etc
// creepy way to check out retweets
var retweeters = retweets.reduce('', function(authors, author){
if(authors.length){
if(authors.split(',').length >= 3){
if(authors.indexOf('...')>=0){
return authors.replace(/and (\d+) more$/, function(str){
return str.replace(/\d+/, function(num){
return parseInt(num) + 1;
});
})
}else{
return authors + " ... and 1 more";
}
}else{
return authors + ", @" + author;
}
}else{
return "@" + author;
}
});
Synopsis: stream.take(integer)
.
Description: Takes only integer
values on stream (after last once unsubscribe all handlers).
Synopsis: stream.skip(integer)
.
Description: Skips first integer
values on stream.
Synopsis: stream.diff([compareFn])
.
Description: Takes only different values. Values compares with Warden.configure.cmp
if compareFn
don't set.
var diffed = stream.diff(function(a, b){
return a.value - b.value > 100;
}).log();
// comparing values with given function;
diffed.fire(100);
// -> 100
diffed.fire(250);
// -> 250
diffed.fire(280);
// nothing happens
Synopsis: stream.interpolate(string)
.
Description: Takes given string and interpolate to it recived value. Interpolations are simple atom data in mustache-style.
var str = "Hello, my name is {{user_name}}, I'm from {{user_city}}";
stream.interpolate(str).log();
stream.fire({
user_name: "Trdat",
user_city: "Moscow"
});
// -> Hello, my name is Trdat, I'm from Moscow
Synopsis: stream.mask(data)
.
Description: Takes data and interpolate it to the string recieved from stream.
var data = {
user_name: "Trdat",
user_city: "Moscow"
}
stream.mask(data).log();
stream.fire("Hello, my name is {{user_name}}, I'm from {{user_city}}");
// -> Hello, my name is Trdat, I'm from Moscow
Synopsis: stream.debounce(ms)
.
Description: Debouncing stream propagation in given ms
miliseconds.
var stream = Warden.Stream().debounce(100).log();
// synchronoius running stream
stream.fire(1)
stream.fire(2)
stream.fire(3)
// after 100 ms:
// -> 3
Synopsis: stream.collect(ms)
.
Description: Collecting all values of stream for given ms
miliseconds to array.
var stream = Warden.Stream().collect(100).log();
// synchronoius running stream
stream.fire(1)
stream.fire(2)
stream.fire(3)
// after 100 ms:
// -> [1, 2, 3]
// synchronoius running stream
stream.fire(1)
stream.fire(2)
stream.fire(3)
setTimeout(function(){
stream.fire(4)
}, 85);
// after 100 ms:
// -> [1, 2, 3, 4]
Synopsis: stream.delay(ms)
.
Description: Delay's stream propagation for given ms
miliseconds to array.
var stream = Warden.Stream().delay(100).log();
strea.fire('Hi!');
// after 100 ms:
// -> Hi!
Synopsis: stream.repeat(times, [delay])
.
Description: Repeats stream evaluation with every value given times with delay
. Note: repeats are async, even if you set delay to zero.
var stream = Warden.Stream().repeat(5, 100).log();
strea.fire('Hi!');
// every 100 ms:
// -> Hi!
// -> Hi!
// -> Hi!
// -> Hi!
// -> Hi!
Synopsis: stream1.merge(stream2, [stream3, stream4, ...])
.
Description: Merges two or more streams to one. Return new stream.
var clicks = $(document).stream('click'),
keydowns = $(document).stream('keydown');
var clicksOrKeydowns = clicks.merge(keydowns);
Synopsis: stream1.combine(stream2, combinerFn)
.
Description: Combines two streams with givetn function combineFn
.
function randomizer(max){
return (Math.random() * (max + 1)) >> 0;
}
var maxStream = stream1.combine(stream2, function (first, second){
return first > second ? first : second;
});
Synopsis: stream1.combine(stream2, resolver, [seed])
.
Description: Takes two streams and resolver function. Returns stream which gives value as a result of resolver function applyied to values of given streams.
function first(a, b){
return a > b ? a : b;
}
var maxStream = stream1.combine(stream2, function (first, second){
return first > second ? first : second;
});
Synopsis: stream1.collectFor(stream2)
.
Description: Returns streams which collects all recieved values and transmit all of them as array when stream2
evaluetes.
var keydowns = $(document).stream('keydown'),
clicks = $(document).stream('click');
keydowns.collectFor(clicks).log(); // will log array of keydowns after every click
Synopsis: stream1.filterFor(fn)
.
Description: Takes function which takes CGE Signature to filtrate values of stream.
var uniqueIntegers = stream.filterFor(function(e, p){
var taken = p.get();
if(taken && taken.indexOf(e)>=0){
p.stop();
}else{
p.next(e, function(data, val){
if(typeof data == 'object') {
data.push(val);
return data;
} else {
return [val]
}
});
}
});
Note: don't try to compose streams by filterFor
.
Synopsis: stream1.alternately(stream2)
.
Description: Takes two streams and returns stream which evaluates when evaluetes first stream alternately second.
var toggles = ons.alternately(offs);
// this stream quaranties that evaluation of ons will be strongly after offs and offs after ons
Synopsis: stream1.waitFor(stream2)
.
Description: Return stream which will evaluate only if stream2
has been already evaluated strongly before stream1
.
Synopsis: stream1.after(stream2)
.
Description: Return stream which evaluates only after stream2
(if it has been evaluated at least once).
Synopsis: stream1.sync(stream2, [stream3, stream4, ... streamN])
.
Description: synchronizes streams. Returns stream which will be evalueted only if all stream1, strem2, ... stremN
have been evaluated.
Synopsis: stream.swap(state)
.
Description: Swaps stream's state.
Synopsis: stream.toggleOn(stream2)
.
Description: Toggles streams state when stream2
is firing.
Synopsis: stream.lock()
.
Description: Locks stream and all stream's children (inherited streams).
Synopsis: stream.lockThis()
.
Description: Locks only given stream.
Synopsis: stream.unlock()
.
Description: Unlocks stream and all stream's children (inherited streams).
Synopsis: stream.unlockThis()
.
Description: Locks only given stream.
Synopsis: stream.watch()
.
Description: Pushes stream to the host's active streams. It means that stream can be evaluated by host's eval
method.
Synopsis: Warden(array);
.
Extends array with Pub/Sub methods, so you can subsribe to destructing changes of array such a push
or splice
.
Note: if you change array by array literal notation arr[arr.length] = val
instead of arr.push(val)
it's not work azazaza
Synopsis: arr.sequentially([interval]);
.
Description: Return stream which will be sequentially fired with all arr
values with given interval.
Synopsis: arr.repeatedly()
.
Description: Returns stream which will be repeatedbly fired with all values of arr
. Difference between .sequentially
that all fires will be synchronious.
Description: in this object stores methods and values to configure Warden's behavior. And some helper functions
By default: 3
Description: Integer. Configures length of Streams memoty array. 3 by default.
By default: function (a, b) { return a === b; }
.
Description: Default Warden's comprator that calls every times when Warden compares two given vallue (e.g. .diff
method).
Synopsis: Warden.configure.isStream([object]);
.
Description: Returns true if object
is instance of Stream.
Synopsis: Warden.configure.addToStream(name, fn, piped);
.
Description: Adds to the Stream's prototype fn
processor.
To understand pipes notation look at Warden.Pipeline
API.
// Extends streams by method .first()
Warden.configure.addToStream('first', function(/* no arguments */){
/* Here we can set up all constant data */
return function(data, pipe){
/*
* data is a value which transmits through stream
* pipe is a Warnde's pipeline, with pipe you should describe stream's behavior
*/
if(data[0] !== undefined){
pipe.next(data[0]) // accepts value through pipe
}else{
pipe.stop(); // declines value and break stream evaluation
}
}
});
Sometimes we don't need pipeline, so we can create stream processor by ready solutions. Let's write our .merge
method
Warden.configure.addToStream('myMerge', function(stream){
var self = this; // context variable is stream entity
return Warden.Stream(function(fire){
self.listen(fire);
stream.listen(fire);
});
}, true); // last argument - true means that we don't use pipline
//now we can use it like a:
stream.myMerge(stream2)
Synopsis: Warden.Host([context]);
.
Description: module which hosts streams on given context
Hosts makes streams faster and also they bind streams to the context by default. Sometimes we generate a lot of streams from one initial source (e.g. clicks on elements, scrolls of window etc). To not overload elements by bunch of listeners Warden provides hosts. Hosts are simple storage of active streams, and when events happens host's listener runs all active streams associated to the given host.
var stream = Warden.Stream();
stream.log();
stream.map('Hey!').log();
stream.fire('Value');
// fires only stream, so 'Hey!' will not log
// And now hosts:
var host = Warden.Host();
var stream = host.newStream();
stream.log();
stream.map('Hey!').log();
host.eval('Value');
// Value
// Hey!
// Host evaluation causes evaluation of every associated stream
Synopsis: host.eval(data, [context])
.
Description: evaluates host's active streams with given data. If context is not setted uses host's context
Synopsis: host.push(stream);
.
Description: push stream to the host's active streams query.
Synopsis: host.pop(stream)
.
Description: removes stream from hosts's active streams query.
Synopsis: host.newStream()
.
Description: return new stream assigned with given host.
Synopsis: Warden.Formula(dependencies, formula)
.
Description: Creates a stream which depends on streams in dependencies
array of streams with given function (formula
).
Formulas implement reactive calculations in Warden. In pure JS when we assign value to the variable we just say that variable literaly refers to the given value. When we change value, we don't change the references of value, it's obvious:
var a = 10;
var b = 20;
var c = a + b;
c;
// 30
a = 20;
c;
// 30 :)
Warden.Formula - is a way to say that values of given variable not just refers to other value but depends on values of other variables. It implemented with streams, so Warden.Formula
takes and returns streams object.
var a = Warden(10); //stream
var b = Warden(20);
var c = Warden.Formula([a,b], function(x, y){
//x and y are values of streams a and b
return x + y;
});
console.log(c.value);
// -> 30;
a.value = 20;
console.log(c.value);
// -> 40
Synopsis: Warden.Pipeline([processors], [context])
.
Description: Implementation of pipline model.
Pipeline provides the simple way to compose sync or async functions in one stream. Functionas can be binded to the context by second argument.
var pipeline = Warden.Pipeline();
var twiceAndDelay = pipieline.pipe(function(value, pipe){
return pipe.next(value * 2);
}).pipe(function(value, pipe){
setTimeout(function(){
pipe.next(value);
}, 1000);
});
twiceAndDelay(100, null, function callback(value){
console.log(value);
});
//after second
// -> 200
Synopsis: pipeline.pipe(processor)
pipe
method takes function which takes arguments: first is a recieved value, second is a pipe object.
Pipe object has 5 methods:
next
- transmits given value through the pipelinestop
- breaks the pipeline evaluation loop.pause
- pauses the pipeline evaluation loop. It means, that calls will come to this point and stops evaluation after it, but state will be remembered.play
- runs the pipeline evaluation loop from last remembered point.host
- returns hosting object of pipe.Synopsis: pipeline.start(value, context, handler)
Runs pipeline with given context and handler.