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