6ced35756bbde513a08c6ecfb93424b787989e18
[aai/esr-gui.git] /
1 /**
2  * Hooks are useful if we want to add a method that automatically has `pre` and `post` hooks.
3  * For example, it would be convenient to have `pre` and `post` hooks for `save`.
4  * _.extend(Model, mixins.hooks);
5  * Model.hook('save', function () {
6  *  console.log('saving');
7  * });
8  * Model.pre('save', function (next, done) {
9  *  console.log('about to save');
10  *  next();
11  * });
12  * Model.post('save', function (next, done) {
13  *  console.log('saved');
14  *  next();
15  * });
16  *
17  * var m = new Model();
18  * m.save();
19  * // about to save
20  * // saving
21  * // saved 
22  */
23
24 // TODO Add in pre and post skipping options
25 module.exports = {
26   /**
27    *  Declares a new hook to which you can add pres and posts
28    *  @param {String} name of the function
29    *  @param {Function} the method
30    *  @param {Function} the error handler callback
31    */
32   hook: function (name, fn, err) {
33     if (arguments.length === 1 && typeof name === 'object') {
34       for (var k in name) { // `name` is a hash of hookName->hookFn
35         this.hook(k, name[k]);
36       }
37       return;
38     }
39
40     if (!err) err = fn;
41
42     var proto = this.prototype || this
43       , pres = proto._pres = proto._pres || {}
44       , posts = proto._posts = proto._posts || {};
45     pres[name] = pres[name] || [];
46     posts[name] = posts[name] || [];
47
48     function noop () {}
49
50     proto[name] = function () {
51       var self = this
52         , pres = this._pres[name]
53         , posts = this._posts[name]
54         , numAsyncPres = 0
55         , hookArgs = [].slice.call(arguments)
56         , preChain = pres.map( function (pre, i) {
57             var wrapper = function () {
58               if (arguments[0] instanceof Error)
59                 return err(arguments[0]);
60               if (numAsyncPres) {
61                 // arguments[1] === asyncComplete
62                 if (arguments.length)
63                   hookArgs = [].slice.call(arguments, 2);
64                 pre.apply(self, 
65                   [ preChain[i+1] || allPresInvoked, 
66                     asyncComplete
67                   ].concat(hookArgs)
68                 );
69               } else {
70                 if (arguments.length)
71                   hookArgs = [].slice.call(arguments);
72                 pre.apply(self,
73                   [ preChain[i+1] || allPresDone ].concat(hookArgs));
74               }
75             }; // end wrapper = function () {...
76             if (wrapper.isAsync = pre.isAsync)
77               numAsyncPres++;
78             return wrapper;
79           }); // end posts.map(...)
80       function allPresInvoked () {
81         if (arguments[0] instanceof Error)
82           err(arguments[0]);
83       }
84
85       function allPresDone () {
86         if (arguments[0] instanceof Error)
87           return err(arguments[0]);
88         if (arguments.length)
89           hookArgs = [].slice.call(arguments);
90         fn.apply(self, hookArgs);
91         var postChain = posts.map( function (post, i) {
92           var wrapper = function () {
93             if (arguments[0] instanceof Error)
94               return err(arguments[0]);
95             if (arguments.length)
96               hookArgs = [].slice.call(arguments);
97             post.apply(self,
98               [ postChain[i+1] || noop].concat(hookArgs));
99           }; // end wrapper = function () {...
100           return wrapper;
101         }); // end posts.map(...)
102         if (postChain.length) postChain[0]();
103       }
104
105       if (numAsyncPres) {
106         complete = numAsyncPres;
107         function asyncComplete () {
108           if (arguments[0] instanceof Error)
109             return err(arguments[0]);
110           --complete || allPresDone.call(this);
111         }
112       }
113       (preChain[0] || allPresDone)();
114     };
115
116     return this;
117   },
118
119   pre: function (name, fn, isAsync) {
120     var proto = this.prototype
121       , pres = proto._pres = proto._pres || {};
122     if (fn.isAsync = isAsync) {
123       this.prototype[name].numAsyncPres++;
124     }
125     (pres[name] = pres[name] || []).push(fn);
126     return this;
127   },
128   post: function (name, fn, isAsync) {
129     var proto = this.prototype
130       , posts = proto._posts = proto._posts || {};
131     (posts[name] = posts[name] || []).push(fn);
132     return this;
133   }
134 };