hh.js - August 19th - Peter Seliger / @petsel
Joinpoint
, Pointcut
, Advice
and Aspect
from a JavaScript Point of View.Function.prototype[
before
|after
|around
]
Traits
and Mixins
...
var requestBasketUpdate = function (evt, onSuccess) {
var
control = evt.target,
form = control.form
;
showMiniBasket();
LightboxController.switchOnLoadingState();
$.ajax({
cache : false,
url : form.action,
type : form.method.toUpperCase(),
data : $(form).serialize(),
dataType : "text",
success : onSuccess.before(
LightboxController.switchOffLoadingState
).after(function () {
timeoutIdMiniBasket = set_timeout(hideMiniBasket, 60000);
}),
error: reloadCurrentPage
});
};
Function.prototype.before
Function.prototype.before = function (behaviorBefore, target) {
var proceedAfter = this;
return function () {
var args = arguments;
behaviorBefore.apply(target, args);
return proceedAfter.apply(target, args);
};
};
var hi = function () {console.log("hi");};
var ho = function () {console.log("ho");};
hi(); // "hi"
ho(); // "ho"
var hohi = hi.before(ho);
var hiho = ho.before(hi);
hohi(); // "ho", "hi"
hiho(); // "hi", "ho"
Function.prototype.after
Function.prototype.after = function (behaviorAfter, target) {
var proceedBefore = this;
return function () {
var args = arguments;
proceedBefore.apply(target, args);
return behaviorAfter.apply(target, args);
};
};
var he = function () {console.log("he");};
he(); // "he"
ho(); // "ho"
var heho = he.after(ho);
var hohe = ho.after(he);
heho(); // "he", "ho"
hohe(); // "ho", "he"
Function.prototype.around
Function.prototype.around = function (behaviorAround, target) {
var proceedEnclosed = this;
return function () {
return behaviorAround.call(target, proceedEnclosed,
behaviorAround, arguments, target);
};
};
var hehiho = hi.around(function (proceed, around, args, target) {
he();
proceed();
ho();
console.log("proceed : ", proceed);
console.log("around : ", around);
console.log("args : ", args);
console.log("target : ", target);
console.log("this : ", this);
});
hehiho("t", "e", "s", "t");
// he
// hi
// ho
//
// proceed : function () {console.log("hi");}
// around : function (proceed, around, args, target) {
//
// he();
// proceed();
// ho();
//
// console.log("proceed : ", proceed);
// console.log("around : ", around);
// console.log("args : ", args);
// console.log("target : ", target);
//
// console.log("this : ", this);
// }
// args : ["t", "e", "s", "t"]
// target : undefined
// this : Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
around
, before
, after
, afterThrowing
, afterReturning
,
as kind of a minimal AOP influenced base set that already supports library (framework) agnostic modification of function based control flow
by just wrapping additional behaviors (advice handlers) around existing methods(functions).
Joinpoint
, Pointcut
, Advice
and Aspect
;
especially from this point of view of what makes them distinct from existing approaches in compiled and/or non dynamic and/or non functional programming languages.
// [VariationsController] depended method modification
SubmitController.isAnyToSubmit = SubmitController.isAnyToSubmit
.around(function (isAnyToSubmit, interceptor, args, target) {
var evt = args[0];
evt.isColorChanged = true;
return isAnyToSubmit.call(target, evt); // proceed
})
;
// [SubmitController] specific functionality
SubmitController.isAnyToSubmit = function (evt) {
var isChecked =
evt.isColorChanged
|| sizeRadioInputs.toArray().some(function (elm/*, idx, arr*/) {
return elm.checked;
})
;
if (isChecked) {
/*
... submit specific stuff ...
*/
}
return isChecked;
};
Joinpoint
in JavaScript always needs to feature both a method that is bound to an object
and this very object itself (regardless of either this couple is locally scoped or not). One might even
think about a label that optionally gets assigned to a joinpoint.
/*var jpIsAnyToSubmit = */ao.Joinpoint.add({
target : SubmitController
methodName : "isAnyToSubmit",
//label : "controllers.SubmitController.isAnyToSubmit",
});
Pointcut
in JavaScript always should be able to return a
collection of joinpoints that are filtered according to certain criteria.
var pcIsAnyToSubmit = ao.Pointcut.add({
//id : "isAnyToSubmit", // if omitted UUID will be generated
filter : function (jp) {
return (jp.getMethodName() == "isAnyToSubmit");
//return (jp.getLabel().indexOf("...") >= 0);
//return (jp.getTarget() === ...);
}
});
var avColorChangedVariant = ao.Advice.add({
//id : "colorChangedVariant", // if omitted UUID will be generated
type : "around",
handler : function (proceed, handler, args, target/*, joinpoint*/) {
var evt = args[0];
evt.isColorChanged = true;
return proceed.call(target, evt);
}
});
Aspect
in JavaScript needs to feature just a sole function
that enables folding of advices and pointcuts within it's function body.
var asColorChangedVariant = ao.Aspect.add({
//id : "colorChangedVariant", // if omitted UUID will be generated
handler : function (linkAdviceToPointcut, ao) {
linkAdviceToPointcut(avColorChangedVariant, pcIsAnyToSubmit);
}
});
confirm
or deny
.
off
and on
again.
»modification.ao«
var ao = require("modification.ao") // aspect oriented system
// static properties
ao.Joinpoint // [Object] // module
ao.Pointcut // [Object] // module
ao.Advice // [Object] // module
ao.Aspect // [Object] // module
// static methods
ao.isOff // [Function]:true|false
ao.isOn // [Function]:true|false
ao.off // [Function]:void
ao.on // [Function]:void
ao.reboot // [Function]:void
ao.Joinpoint.add // [Function]:[Joinpoint]|undefined|false
ao.Joinpoint.remove // [Function]:[Joinpoint]|undefined|false
ao.Joinpoint.isJoinpoint // [Function]:true|false
ao.Joinpoint.isJoinpointLike// [Function]:true|false
ao.Pointcut.getById // [Function]:[Pointcut]|undefined
ao.Pointcut.add // [Function]:[Pointcut]|undefined|false
ao.Pointcut.remove // [Function]:[Pointcut]|undefined|false
ao.Pointcut.isPointcut // [Function]:true|false
ao.Pointcut.isPointcutLike // [Function]:true|false
ao.Advice.getById // [Function]:[Advice]|undefined
ao.Advice.add // [Function]:[Advice]|undefined|false
ao.Advice.remove // [Function]:[Advice]|undefined|false
ao.Advice.isAdvice // [Function]:true|false
ao.Advice.isAdviceLike // [Function]:true|false
ao.Aspect.getById // [Function]:[Aspect]|undefined
ao.Aspect.add // [Function]:[Aspect]|undefined|false
ao.Aspect.remove // [Function]:[Aspect]|undefined|false
ao.Aspect.isAspect // [Function]:true|false
ao.Aspect.isAspectLike // [Function]:true|false
// instance methods
var jp = ao.Joinpoint.add(/*config:{
target :[Object], // required
methodName :string, // required
//label :string // optional
}*/); // :[Joinpoint]|undefined|false
jp.getLabel // [Function]:string
jp.getTarget // [Function]:[Object]
jp.getMethodName // [Function]:string
jp.getBaseMethod // [Function]:[Function]
jp.equals // [Function]:true|false
var pc = ao.Pointcut.add(/*config{
//id :string, // if omitted UUID will be generated
filter :[Function] // a joinpoint filter is required
}*/); // :[Pointcut]|undefined|false
pc.getId // [Function]:string
pc.getFilter // [Function]:[Function]
pc.getJoinpoints // [Function]:[Array:[Joinpoint]]
pc.equals // [Function]:true|false
var av = ao.Advice.add(/*config{
//id :string, // if omitted UUID will be generated
type :string, // required: valid quantifier / type
handler :[Function] // required: behavior / advice handler
}*/); // :[Advice]|undefined|false
av.getId // [Function]:string
av.getType // [Function]:string("after"..."before")
av.getHandler // [Function]:[Function]
av.equals // [Function]:true|false
var as = ao.Aspect.add(/*config{
//id :string, // if omitted UUID will be generated
handler :[Function] // required: "callback" function that
// links advices to pointcuts
}*/); // :[Aspect]|undefined|false
as.getId // [Function]:string
as.getHandler // [Function]:[Function]
as.getLinkList // [Function]:[Array:[Advice][Pointcut]]
as.isReconfirm // [Function]:true|false
as.isConfirmed // [Function]:true|false
as.isDenied // [Function]:true|false
as.confirm // [Function]:void
as.deny // [Function]:void
as.equals // [Function]:true|false