Zen.Editor = {}
/**
* The Editor class can be used to create simple text editors that support HTML,
* Textile and Markdown (more might be supported in the future). The generated
* output is very simple and very easy to style (the class doesn't inject any CSS).
*
* h2. Usage
*
* Adding an editor requires 2 steps. First you'll need to create a new instance
* of the editor:
*
* bc. editor = new Zen.Editor.Base('css selector');
*
* Once the instance is created you can call the display() method to show the editor:
*
* bc. editor.display();
*
* This will render the editor using the default format, HTML.
*
* h2. Customizing
*
* By default the editor will generate HTML tags but this can be changed by passing
* a JSON object as the second argument of the constructor method:
*
* bc. editor = new Zen.Editor.Base('css selector', {format: '...'});
*
* The second argument accepts the following 2 options:
*
* format: the formatting engine to use. You can choose from "html" (default),
* "textile" and "markdown". You can also create your own driver as long as you make
* sure the class is loaded and defined under Zen.Editor.DriverName, where DriverName
* is the PascalCased version of the driver name.
*
* buttons: an array of default buttons. Note that in most cased you'd want to use the
* addButtons() method as specifying an array of buttons in the constructor will prevent
* the default buttons from being added.
*
* h2. Adding Buttons
*
* The recommended way of adding buttons is using the addButtons method. This method takes
* a single argument which is an array of JSON objects. Each JSON object should contain the
* following keys:
*
* * name: the name of the button, used to generate the CSS class
* * html: the content of the button (text, html, etc)
* * callback: the function/method called whenever the button is clicked.
*
* Example:
*
* bc. editor.addButtons(
* [
* {name: 'hello', html: 'Hello', callback: function() { alert('Hello!'); }}
* ]);
*
* When the callback is invoked the instance of the editor to which the button belongs will
* be sent to the callback.
*
* h2. Drivers
*
* The Base class relies on so called "drivers": small classes that handle actions
* whenever a button is clicked. By default there are 3 drivers, HTML, Textile and Markdown.
* In order to use your own driver while still supporting the default buttons (bold, italic, etc)
* your class has to implement the following methods:
*
* * bold
* * italic
* * link
* * ol
* * ul
*
* When creating a method for a driver they should accept a single parameter, the instance
* of the editor to which the button belongs. Say your button wraps the currently selected
* text in an element you'd do something like the following:
*
* bc. my_method: function(editor)
* {
* editor.insertAroundCursor({before: '<', after: '>'})
* }
*
* Also keep in mind that you don't have to create an entire class, you can also specify
* a closure in the "callback" key when adding a button.
*
* h2. Markup
*
* The Editor class generates the following markup:
*
* bc.
*
* As you can see the markup is very simple and no CSS rules are injected. This does however
* mean you generally spend a bit more time styling the editor but it also gives
* you much more flexibility and less trouble.
*
* @author Yorick Peterse
* @link http://yorickpeterse.com/
* @license MIT License
* @package Zen
* @since 0.1
*
* Copyright (c) 2011, Yorick Peterse
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
Zen.Editor.Base = new Class(
{
Implements: Options,
/**
* JSON object with all default options.
* The following options are available:
*
* * format: the text format (textile, markdown, etc)
* * buttons: Array of all buttons and their callbacks.
* Note that by default this object is empty, all buttons will be
* added by the initialize() method as the callbacks won't be available
* until the drivers have been loaded.
*
* @type [Object]
*/
options:
{
format: 'html',
buttons: []
},
/**
* Collection of objects for the CSS selector specified
* when initializing this class.
*
* @type [Array]
*/
elements: [],
/**
* New instance of the driver for the current editor.
*
* @type [Object]
*/
driver: {},
/**
* Initializes a new instance of the visual editor. The first
* argument is the CSS selector and the second a JSON object
* with additional options.
*
* @author Yorick Peterse
* @param [String] css_selector The CSS selector for the editor.
* @param [Ojbect] options JSON object with additional options.
* @return [Object]
* @since 0.1
*/
initialize: function(css_selector, options)
{
this.setOptions(options);
this.elements = $$(css_selector);
// Load our driver
var driver = this.options.format.capitalize().camelCase();
this.driver = new Zen.Editor[driver]();
// Add our buttons
if ( this.options.buttons.length <= 0 )
{
this.options.buttons = [
{name: 'bold' , html: 'Bold' , callback: this.driver.bold},
{name: 'italic' , html: 'Italic' , callback: this.driver.italic},
{name: 'link' , html: 'Link' , callback: this.driver.link},
{name: 'ol' , html: 'Ordered list' , callback: this.driver.ol},
{name: 'ul' , html: 'Unordered list', callback: this.driver.ul}
];
}
},
/**
* Generates the required markup for the editor along
* with binding all events for all the available buttons.
*
* @author Yorick Peterse
* @return [Void]
*/
display: function()
{
// Ignore the entire process if no elements have been found
if ( this.elements.length == 0 ) { return; }
// Generate our markup
var toolbar = new Element('div', {'class': 'editor_toolbar'});
var ul = new Element('ul');
// Generate all the buttons
this.options.buttons.each(function(btn)
{
var li = new Element('li', {'class': btn.name});
// Add the html?
if ( btn.html ) { li.set('html', btn.html); }
li.addEvent('click', function()
{
// Get the editor closest to this button
var current_editor = this.getParent('.editor_container');
current_editor = current_editor.getElement('textarea');
btn.callback(current_editor);
});
li.inject(ul);
});
this.elements.each(function(element)
{
var container = new Element('div', {'class': 'editor_container'});
// Replace the text area with all our elements
ul.inject(toolbar);
toolbar.inject(container);
container.inject(element, 'before');
element.inject(container);
});
},
/**
* Adds the specified buttons to this.options.buttons.
* Buttons can also be added by directly setting them in the construct
* but that will prevent the default buttons from getting added.
*
* @author Yorick Peterse
* @param [Array] buttons An array of the buttons to add
* @return [Void]
*/
addButtons: function(buttons)
{
var self = this;
buttons.each(function(button)
{
self.options.buttons.push(button);
});
}
});