docs: function( params ){
        var defaults = {};
        params = $$.util.extend({}, defaults, params);
        
        return function triggerImpl(events, extraParams, fnToTrigger){
          var self = this;
          var selfIsArrayLike = self.length !== undefined;
          var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
          var single = selfIsArrayLike ? self[0] : self;
          var eventsIsString = $$.is.string(events);
          var eventsIsObject = $$.is.plainObject(events);
          var eventsIsEvent = $$.is.event(events);
          var p = params;
          var cy = this._private.cy || this;
  
          if( eventsIsString ){ // then make a plain event object for each event name
            var evts = events.split(/\s+/);
            events = [];
  
            for( var i = 0; i < evts.length; i++ ){
              var evt = evts[i];
              if( $$.is.emptyString(evt) ){ continue; }
  
              var match = evt.match( $$.define.event.regex ); // type[.namespace]
              var type = match[1];
              var namespace = match[2] ? match[2] : undefined;
  
              events.push( {
                type: type,
                namespace: namespace
              } );
            }
          } else if( eventsIsObject ){ // put in length 1 array
            var eventArgObj = events;
  
            events = [ eventArgObj ];
          }
  
          if( extraParams ){
            if( !$$.is.array(extraParams) ){ // make sure extra params are in an array if specified
              extraParams = [ extraParams ];
            }
          } else { // otherwise, we've got nothing
            extraParams = [];
          }
  
          for( var i = 0; i < events.length; i++ ){ // trigger each event in order
            var evtObj = events[i];
            
            for( var j = 0; j < all.length; j++ ){ // for each
              var triggerer = all[j];
              var listeners = triggerer._private.listeners;
              var triggererIsElement = $$.is.element(triggerer);
              var bubbleUp = triggererIsElement;
  
              // create the event for this element from the event object
              var evt;
  
              if( eventsIsEvent ){ // then just get the object
                evt = evtObj;
                
                evt.cyTarget = evt.cyTarget || triggerer;
                evt.cy = evt.cy || cy;
                evt.namespace = evt.namespace || evtObj.namespace;
  
              } else { // then we have to make one
                evt = new $$.Event( evtObj, {
                  cyTarget: triggerer,
                  cy: cy,
                  namespace: evtObj.namespace
                } );
              }
  
              // Create a rendered position based on the passed position
              if( evt.cyPosition ){
                var pos = evt.cyPosition;
                var zoom = cy.zoom();
                var pan = cy.pan();
  
                evt.cyRenderedPosition = {
                  x: pos.x * zoom + pan.x,
                  y: pos.y * zoom + pan.y
                };
              }
  
              if( fnToTrigger ){ // then override the listeners list with just the one we specified
                listeners = [{
                  namespace: evt.namespace,
                  type: evt.type,
                  callback: fnToTrigger
                }];
              }
  
              for( var k = 0; k < listeners.length; k++ ){ // check each listener
                var lis = listeners[k];
                var nsMatches = !lis.namespace || lis.namespace === evt.namespace;
                var typeMatches = lis.type === evt.type;
                var targetMatches = lis.delegated ? ( triggerer !== evt.cyTarget && $$.is.element(evt.cyTarget) && lis.selObj.filter(evt.cyTarget).length > 0 ) : (true); // we're not going to validate the hierarchy; that's too expensive
                var listenerMatches = nsMatches && typeMatches && targetMatches;
  
                if( listenerMatches ){ // then trigger it
                  var args = [ evt ];
                  args = args.concat( extraParams ); // add extra params to args list
  
                  if( lis.data ){ // add on data plugged into binding
                    evt.data = lis.data;
                  } else { // or clear it in case the event obj is reused
                    evt.data = undefined;
                  }
  
                  if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener
                    listeners.splice(k, 1);
                    k--;
                  }
  
                  if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders
                    var binders = lis.binders;
                    for( var l = 0; l < binders.length; l++ ){
                      var binder = binders[l];
                      if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it
  
                      var binderListeners = binder._private.listeners;
                      for( var m = 0; m < binderListeners.length; m++ ){
                        var binderListener = binderListeners[m];
  
                        if( binderListener === lis ){ // delete listener from list
                          binderListeners.splice(m, 1);
                          m--;
                        }
                      }
                    }
                  }
  
                  // run the callback
                  var context = lis.delegated ? evt.cyTarget : triggerer;
                  var ret = lis.callback.apply( context, args );
  
                  if( ret === false || evt.isPropagationStopped() ){
                    // then don't bubble
                    bubbleUp = false;
  
                    if( ret === false ){
                      // returning false is a shorthand for stopping propagation and preventing the def. action
                      evt.stopPropagation();
                      evt.preventDefault();
                    }
                  }
                } // if listener matches
              } // for each listener
  
              // bubble up event for elements
              if( bubbleUp ){
                var parent = triggerer.parent();
                var hasParent = parent.length !== 0;
  
                if( hasParent ){ // then bubble up to parent
                  parent = parent[0];
                  parent.trigger(evt);
                } else { // otherwise, bubble up to the core
                  cy.trigger(evt);
                }
              }
  
            } // for each of all
          } // for each event
          
          return self; // maintain chaining
        }; // function
      }