1 var func = require("../base/functions"),
  2         obj = require("../base/object"),
  3         Promise = require("../promise").Promise,
  4         define = require("../define").define;
  5 
  6 
  7 var Middleware = define(null, {
  8             instance : {
  9                 /** @lends comb.plugins.Middleware.prototype */
 10 
 11 
 12                 __hooks : {pre : {}, post : {}},
 13 
 14 
 15                 /**
 16                  * @class Plugin to enable middleware on a class
 17                  *
 18                  * @example
 19                  *
 20                  * var Mammal = define(comb.plugins.Middleware, {
 21                  *  instance : {
 22                  *
 23                  *    constructor: function(options) {
 24                  *        options = options || {};
 25                  *        this.super(arguments);
 26                  *        this._type = options.type || "mammal";
 27                  *    },
 28                  *
 29                  *    speak : function() {
 30                  *        var ret = new comb.Promise();
 31                  *        this._hook("pre", "speak")
 32                  *                .then(comb.hitch(this, "_hook", "post", "speak"), hitch(ret, "errback"))
 33                  *                .then(comb.hitch(ret, "callback"), comb.hitch(ret, "errback"));
 34                  *        return ret;
 35                  *    }
 36                  *  }
 37                  *});
 38                  *
 39                  *  Mammal.pre('speak', function(next){
 40                  *     //do something meaningful
 41                  *     next();
 42                  *  });
 43                  *  var m = new Mammal({color : "gold"});
 44                  *  m.speak();
 45                  *
 46                  * @constructs
 47                  */
 48                 constructor : function() {
 49                     this.__hooks = obj.merge({}, this.__hooks);
 50                     this._super(arguments);
 51                 },
 52 
 53                 /**
 54                  * <p>Protected!</p>
 55                  *
 56                  * <p>Call to initiate middleware for the topic</p>
 57                  * <p><b>NOTE:</b> this function takes a variable number of arguments
 58                  *       whatever comes after the op param will be passed into
 59                  *       the listening function, with the last argument to the listenting
 60                  *       function being the next function</p>
 61                  *
 62                  *
 63                  * @public
 64                  * @param {"pre"|"post"} state the state in which the hook should be called
 65                  * @param {String} op the operation that is being acted upong
 66                  * @param args arguments to be passed into the listening functions.
 67                  * @returns {comb.Promise} a promise to use after middleware chain completes
 68                  *
 69                  */
 70                 _hook : function(state, op, args) {
 71                     args = args || [];
 72                     var promise = new Promise();
 73                     var funcs, length;
 74                     if (this.__hooks[state] && (funcs = this.__hooks[state][op]) != null && (length = funcs.length) > 0) {
 75                         var count = 0;
 76                         var next = func.hitch(this, function() {
 77                             //if Ive looped through all of them callback
 78                             if (count == length) {
 79                                 promise.callback();
 80                             } else {
 81                                 //call next
 82                                 var nextArgs = args.slice(0);
 83                                 nextArgs.unshift(next);
 84                                 funcs[count++].apply(this, nextArgs);
 85                             }
 86                         });
 87                         next();
 88                     } else {
 89                         promise.callback();
 90                     }
 91                     return promise;
 92                 },
 93 
 94                 /**
 95                  * Use to listen to before an event occurred i.e. pre save
 96                  *
 97                  * <b>NOTE:</b></br>
 98                  * <ul>
 99                  *     <li>You must call next in order for the middleware chain to complete</li>
100                  *      <li>This connects to events on the instance of an object, not all instances!</li>
101                  *      <li>Hooks are called in the order they are received!</li>
102                  *      <li> When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
103                  *  </ul>
104                  *
105                  * @example
106                  *      instance.pre("save", function(args,...., next){
107                  *          //do something...
108                  *          //you have to call next!!!!!
109                  *          next();
110                  *      });
111                  *
112                  * */
113                 pre : function(fun, callback) {
114                     var hook = this.__hooks.pre[fun];
115                     if (!hook) {
116                         hook = this.__hooks.pre[fun] = [];
117                     }
118                     hook.push(callback);
119                 },
120 
121                 /**
122                  * <p>Use to listen to after an event has occurred i.e. post save</p>
123                  * <b>NOTE:</b></br>
124                  * <ul>
125                  *     <li>You must call next in order for the middleware chain to complete</li>
126                  *      <li>This connects to events on the instance of an object, NOT all instances!</li>
127                  *      <li>Hooks are called in the order they are received!</li>
128                  *      <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
129                  *  </ul>
130                  * @example
131                  *
132                  * instance.post("save", function(next){
133                  *                //do something...
134                  *                 //you have to call next!!!!!
135                  *                 next();
136                  *          });
137                  * */
138                 post : function(fun, callback) {
139                     var hook = this.__hooks.post[fun];
140                     //if I havent initialized it create it;
141                     if (hook == undefined) {
142                         hook = this.__hooks.post[fun] = [];
143                     }
144                     hook.push(callback);
145                 }
146             },
147 
148             static : {
149                 /** @lends comb.plugins.Middleware */
150 
151                 /**
152                  *<p> Use to listen to after an event has occurred i.e. post save</p>
153                  *
154                  * <b>NOTE:</b></br>
155                  * <ul>
156                  *     <li>You must call next in order for the middleware chain to complete</li>
157                  *      <li>This connects to events on ALL instances of an object</li>
158                  *      <li>Hooks are called in the order they are received!</li>
159                  *      <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
160                  *  </ul>
161                  *
162                  * @example
163                  * Class.pre("save", function(next){
164                  *               ...
165                  *               //you must call next
166                  *          });
167                  * */
168                 pre : function(name, cb) {
169                     var hooks = this.prototype.__hooks;
170                     var hook = hooks.pre[name];
171                     if (!hook) {
172                         hook = hooks.pre[name] = [];
173                     }
174                     hook.push(cb);
175                 },
176 
177                 /**
178                  *<p>Use to listen to after an event has occurred i.e. post save</p>
179                  *
180                  *<b>NOTE:</b></br>
181                  * <ul>
182                  *     <li>You must call next in order for the middleware chain to complete</li>
183                  *      <li>This connects to events on ALL instances of an object</li>
184                  *      <li>Hooks are called in the order they are received!</li>
185                  *      <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
186                  *  </ul>
187                  *
188                  * @example
189                  * Class.post("save", function(next){
190                  *               ...
191                  *               //you must call next
192                  *          });
193                  * */
194                 post : function(name, cb) {
195                     var hooks = this.prototype.__hooks;
196                     var hook = hooks.post[name];
197                     if (!hook) {
198                         hook = hooks.post[name] = [];
199                     }
200                     hook.push(cb);
201                 }
202             }
203 
204         });
205 
206 module.exports = exports = Middleware;