whole_func_string: function(name, scope, targetToolbars){
  			// targetToolbars are optional, we don't require a toolbar to function
  			if(!name || name === '') throw('textAngular Error: An editor requires a name');
  			if(!scope) throw('textAngular Error: An editor requires a scope');
  			if(editors[name]) throw('textAngular Error: An Editor with name "' + name + '" already exists');
  			// _toolbars is an ARRAY of toolbar scopes
  			var _toolbars = [];
  			angular.forEach(targetToolbars, function(_name){
  				if(toolbars[_name]) _toolbars.push(toolbars[_name]);
  				// if it doesn't exist it may not have been compiled yet and it will be added later
  			});
  			editors[name] = {
  				scope: scope,
  				toolbars: targetToolbars,
  				_registerToolbar: function(toolbarScope){
  					// add to the list late
  					if(this.toolbars.indexOf(toolbarScope.name) >= 0) _toolbars.push(toolbarScope);
  				},
  				// this is a suite of functions the editor should use to update all it's linked toolbars
  				editorFunctions: {
  					disable: function(){
  						// disable all linked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){ toolbarScope.disabled = true; });
  					},
  					enable: function(){
  						// enable all linked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){ toolbarScope.disabled = false; });
  					},
  					focus: function(){
  						// this should be called when the editor is focussed
  						angular.forEach(_toolbars, function(toolbarScope){
  							toolbarScope._parent = scope;
  							toolbarScope.disabled = false;
  							toolbarScope.focussed = true;
  							scope.focussed = true;
  						});
  					},
  					unfocus: function(){
  						// this should be called when the editor becomes unfocussed
  						angular.forEach(_toolbars, function(toolbarScope){
  							toolbarScope.disabled = true;
  							toolbarScope.focussed = false;
  						});
  						scope.focussed = false;
  					},
  					updateSelectedStyles: function(selectedElement){
  						// update the active state of all buttons on liked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){
  							angular.forEach(toolbarScope.tools, function(toolScope){
  								if(toolScope.activeState){
  									toolbarScope._parent = scope;
  									toolScope.active = toolScope.activeState(selectedElement);
  								}
  							});
  						});
  					},
  					sendKeyCommand: function(event){
  						// we return true if we applied an action, false otherwise
  						var result = false;
  						if(event.ctrlKey || event.metaKey) angular.forEach(taTools, function(tool, name){
  							if(tool.commandKeyCode && tool.commandKeyCode === event.which){
  								for(var _t = 0; _t < _toolbars.length; _t++){
  									if(_toolbars[_t].tools[name] !== undefined){
  										taToolExecuteAction.call(_toolbars[_t].tools[name], scope);
  										result = true;
  										break;
  									}
  								}
  							}
  						});
  						return result;
  					},
  					triggerElementSelect: function(event, element){
  						// search through the taTools to see if a match for the tag is made.
  						// if there is, see if the tool is on a registered toolbar and not disabled.
  						// NOTE: This can trigger on MULTIPLE tools simultaneously.
  						var elementHasAttrs = function(_element, attrs){
  							var result = true;
  							for(var i = 0; i < attrs.length; i++) result = result && _element.attr(attrs[i]);
  							return result;
  						};
  						var workerTools = [];
  						var unfilteredTools = {};
  						var result = false;
  						element = angular.element(element);
  						// get all valid tools by element name, keep track if one matches the
  						var onlyWithAttrsFilter = false;
  						angular.forEach(taTools, function(tool, name){
  							if(
  								tool.onElementSelect &&
  								tool.onElementSelect.element &&
  								tool.onElementSelect.element.toLowerCase() === element[0].tagName.toLowerCase() &&
  								(!tool.onElementSelect.filter || tool.onElementSelect.filter(element))
  							){
  								// this should only end up true if the element matches the only attributes
  								onlyWithAttrsFilter = onlyWithAttrsFilter ||
  									(angular.isArray(tool.onElementSelect.onlyWithAttrs) && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs));
  								if(!tool.onElementSelect.onlyWithAttrs || elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) unfilteredTools[name] = tool;
  							}
  						});
  						// if we matched attributes to filter on, then filter, else continue
  						if(onlyWithAttrsFilter){
  							angular.forEach(unfilteredTools, function(tool, name){
  								if(tool.onElementSelect.onlyWithAttrs && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) workerTools.push({'name': name, 'tool': tool});
  							});
  							// sort most specific (most attrs to find) first
  							workerTools.sort(function(a,b){
  								return b.tool.onElementSelect.onlyWithAttrs.length - a.tool.onElementSelect.onlyWithAttrs.length;
  							});
  						}else{
  							angular.forEach(unfilteredTools, function(tool, name){
  								workerTools.push({'name': name, 'tool': tool});
  							});
  						}
  						// Run the actions on the first visible filtered tool only
  						if(workerTools.length > 0){
  							for(var _i = 0; _i < workerTools.length; _i++){
  								var tool = workerTools[_i].tool;
  								var name = workerTools[_i].name;
  								for(var _t = 0; _t < _toolbars.length; _t++){
  									if(_toolbars[_t].tools[name] !== undefined){
  										tool.onElementSelect.action.call(_toolbars[_t].tools[name], event, element, scope);
  										result = true;
  										break;
  									}
  								}
  								if(result) break; 
  							}
  						}
  						return result;
  					}
  				}
  			};
  			return editors[name].editorFunctions;
  		}
func_code_string: function(name, scope, targetToolbars){
  			// targetToolbars are optional, we don't require a toolbar to function
  			if(!name || name === '') throw('textAngular Error: An editor requires a name');
  			if(!scope) throw('textAngular Error: An editor requires a scope');
  			if(editors[name]) throw('textAngular Error: An Editor with name "' + name + '" already exists');
  			// _toolbars is an ARRAY of toolbar scopes
  			var _toolbars = [];
  			angular.forEach(targetToolbars, function(_name){
  				if(toolbars[_name]) _toolbars.push(toolbars[_name]);
  				// if it doesn't exist it may not have been compiled yet and it will be added later
  			});
  			editors[name] = {
  				scope: scope,
  				toolbars: targetToolbars,
  				_registerToolbar: function(toolbarScope){
  					// add to the list late
  					if(this.toolbars.indexOf(toolbarScope.name) >= 0) _toolbars.push(toolbarScope);
  				},
  				// this is a suite of functions the editor should use to update all it's linked toolbars
  				editorFunctions: {
  					disable: function(){
  						// disable all linked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){ toolbarScope.disabled = true; });
  					},
  					enable: function(){
  						// enable all linked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){ toolbarScope.disabled = false; });
  					},
  					focus: function(){
  						// this should be called when the editor is focussed
  						angular.forEach(_toolbars, function(toolbarScope){
  							toolbarScope._parent = scope;
  							toolbarScope.disabled = false;
  							toolbarScope.focussed = true;
  							scope.focussed = true;
  						});
  					},
  					unfocus: function(){
  						// this should be called when the editor becomes unfocussed
  						angular.forEach(_toolbars, function(toolbarScope){
  							toolbarScope.disabled = true;
  							toolbarScope.focussed = false;
  						});
  						scope.focussed = false;
  					},
  					updateSelectedStyles: function(selectedElement){
  						// update the active state of all buttons on liked toolbars
  						angular.forEach(_toolbars, function(toolbarScope){
  							angular.forEach(toolbarScope.tools, function(toolScope){
  								if(toolScope.activeState){
  									toolbarScope._parent = scope;
  									toolScope.active = toolScope.activeState(selectedElement);
  								}
  							});
  						});
  					},
  					sendKeyCommand: function(event){
  						// we return true if we applied an action, false otherwise
  						var result = false;
  						if(event.ctrlKey || event.metaKey) angular.forEach(taTools, function(tool, name){
  							if(tool.commandKeyCode && tool.commandKeyCode === event.which){
  								for(var _t = 0; _t < _toolbars.length; _t++){
  									if(_toolbars[_t].tools[name] !== undefined){
  										taToolExecuteAction.call(_toolbars[_t].tools[name], scope);
  										result = true;
  										break;
  									}
  								}
  							}
  						});
  						return result;
  					},
  					triggerElementSelect: function(event, element){
  						// search through the taTools to see if a match for the tag is made.
  						// if there is, see if the tool is on a registered toolbar and not disabled.
  						// NOTE: This can trigger on MULTIPLE tools simultaneously.
  						var elementHasAttrs = function(_element, attrs){
  							var result = true;
  							for(var i = 0; i < attrs.length; i++) result = result && _element.attr(attrs[i]);
  							return result;
  						};
  						var workerTools = [];
  						var unfilteredTools = {};
  						var result = false;
  						element = angular.element(element);
  						// get all valid tools by element name, keep track if one matches the
  						var onlyWithAttrsFilter = false;
  						angular.forEach(taTools, function(tool, name){
  							if(
  								tool.onElementSelect &&
  								tool.onElementSelect.element &&
  								tool.onElementSelect.element.toLowerCase() === element[0].tagName.toLowerCase() &&
  								(!tool.onElementSelect.filter || tool.onElementSelect.filter(element))
  							){
  								// this should only end up true if the element matches the only attributes
  								onlyWithAttrsFilter = onlyWithAttrsFilter ||
  									(angular.isArray(tool.onElementSelect.onlyWithAttrs) && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs));
  								if(!tool.onElementSelect.onlyWithAttrs || elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) unfilteredTools[name] = tool;
  							}
  						});
  						// if we matched attributes to filter on, then filter, else continue
  						if(onlyWithAttrsFilter){
  							angular.forEach(unfilteredTools, function(tool, name){
  								if(tool.onElementSelect.onlyWithAttrs && elementHasAttrs(element, tool.onElementSelect.onlyWithAttrs)) workerTools.push({'name': name, 'tool': tool});
  							});
  							// sort most specific (most attrs to find) first
  							workerTools.sort(function(a,b){
  								return b.tool.onElementSelect.onlyWithAttrs.length - a.tool.onElementSelect.onlyWithAttrs.length;
  							});
  						}else{
  							angular.forEach(unfilteredTools, function(tool, name){
  								workerTools.push({'name': name, 'tool': tool});
  							});
  						}
  						// Run the actions on the first visible filtered tool only
  						if(workerTools.length > 0){
  							for(var _i = 0; _i < workerTools.length; _i++){
  								var tool = workerTools[_i].tool;
  								var name = workerTools[_i].name;
  								for(var _t = 0; _t < _toolbars.length; _t++){
  									if(_toolbars[_t].tools[name] !== undefined){
  										tool.onElementSelect.action.call(_toolbars[_t].tools[name], event, element, scope);
  										result = true;
  										break;
  									}
  								}
  								if(result) break; 
  							}
  						}
  						return result;
  					}
  				}
  			};
  			return editors[name].editorFunctions;
  		}
func_documentation_string: register an editor and the toolbars that it is affected by