WYMeditor Documentation¶
Welcome to WYMeditor’s documentation.
Description¶
WYMeditor is an open source web-based WYSIWYM editor with semantics and standards in mind.
The “WYM” part stands for What You Mean (is what you get). This is in contrast with the more common WYSIWYG—What You See Is What You Get.
Thus WYMeditor is different from the more common editors (like TinyMCE and CKEditor).
Its focus is on providing a simple experience for users as well as the separation of the content of the document from the presentation of the document.
It also adheres to web standards.
And its versioning adheres to Semantic Versioning 2.
Resources¶
Documentation | WYMeditor documentation | |
Code repository | WYMeditor GitHub repository | |
Website | WYMeditor website | http://wymeditor.github.io/wymeditor |
Chat | Gitter.im room | |
Support | WYMeditor questions in Stack Overflow | |
Issues | WYMeditor issue tracker | |
Examples | WYMeditor online examples | |
CI testing | WYMeditor Travis-CI report | |
Bower | Bower manifest | |
Project mgmt | Waffle.io board | ![]() |
Chapters and Sections¶
About WYMeditor¶
Description¶
WYMeditor is an open source web-based WYSIWYM editor with semantics and standards in mind.
The “WYM” part stands for What You Mean (is what you get). This is in contrast with the more common WYSIWYG—What You See Is What You Get.
Thus WYMeditor is different from the more common editors (like TinyMCE and CKEditor).
Its focus is on providing a simple experience for users as well as the separation of the content of the document from the presentation of the document.
It also adheres to web standards.
And its versioning adheres to Semantic Versioning 2.
Resources¶
Documentation | WYMeditor documentation | |
Code repository | WYMeditor GitHub repository | |
Website | WYMeditor website | http://wymeditor.github.io/wymeditor |
Chat | Gitter.im room | |
Support | WYMeditor questions in Stack Overflow | |
Issues | WYMeditor issue tracker | |
Examples | WYMeditor online examples | |
CI testing | WYMeditor Travis-CI report | |
Bower | Bower manifest | |
Project mgmt | Waffle.io board | ![]() |
Why WYMeditor?¶
If your project requires that users produce consistent, standards-compliant and clean content, they’ll thank you for implementing WYMeditor.
There are lots of choices when it comes to a browser–based editor and many of them are stable, mature projects with thousands of users.
If you require an editor that gives the end–user total control and flexibility then WYMeditor is probably not for you. On the other hand, if you want an editor that can be customized to provide the specific capabilities that are required in your project, and you want to ensure that users are focused on the structure of their content instead of tweaking fonts and margins, perhaps you should give WYMeditor a try.
WYMeditor also fully supports Internet Explorer 8.
Try It¶
Want to see what WYMeditor can do? Try the examples online, right now.
Browser Compatibility¶
Internet Explorer | ![]() |
8 – 11 |
Mozilla Firefox | ![]() |
LTS and latests two major versions |
Opera | ![]() |
Latest version |
Safari | ![]() |
Latest version |
Google Chrome | ![]() |
Latest two major versions |
Requirements¶
- jQuery: any version between 1.4.4 and 2.1.x. With jQuery 2.x and newer, there is no support for IE8 and older.
- For IE8, ES5 shims are required. Tested with es5-shim and shams.
Global Pollution¶
window.jQuery.browser
: jquery.browser v``~0.0.6``window.rangy
: Rangy v``1.2.2`` (includes the selection save and restore module)
Copyright¶
Copyright (c) 2005 - 2015 Jean-Francois Hovinne,
Dual licensed under the MIT (MIT-license.txt
)
and GPL (GPL-license.txt
) licenses.
Getting Started With WYMeditor¶
Setting up WYMeditor¶
Quick Start Instructions¶
Include jQuery¶
WYMeditor requires a version of jQuery between 1.4.4 and 2.1.x.
Make sure that your page includes it.
Note
With jQuery 2.x and newer, there is no support for IE8 and older.
Download a Pre-built Release of WYMeditor¶
Download a pre-built WYMeditor release
from the WYMeditor Releases Github Page.
Extract the contents of the archive
in to a folder in your project
(eg. media/js/wymeditor
).
Or install via Bower¶
WYMeditor is available via Bower.
jQuery 2.x does not support IE8 and older.
WYMeditor does support IE8.
WYMeditor’s Bower manifest defines a range of jQuery versions as a dependency.
The latest jQuery version in this range is the newest jQuery version that still supports these browsers.
WYMeditor does support jQuery 2.x, with the acknowledgement that it will not function in IE8.
If you decide to use jQuery 2.x, please feel free to override the top limit, while making sure you supply a jQuery version that WYMeditor supports.
See Include jQuery for WYMeditor’s range of supported jQuery versions.
Source the WYMeditor Javascript¶
Include the dependencies (e.g. jQuery, ES5 shim and ES5 Sham)
and wymeditor/jquery.wymeditor.min.js
file
on your page.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WYMeditor Quickstart</title>
<script type="text/javascript" src="jquery/jquery.js"></script>
<!-- for IE8, ES5 shims are required-->
<!--[if IE 8]>
<script type="text/javascript" src="es5-shim.js"></script>
<script type="text/javascript" src="es5-sham.js"></script>
<![endif]-->
<script type="text/javascript" src="wymeditor/jquery.wymeditor.min.js"></script>
</head>
<body>
... SNIP
</body>
</html>
Create a textarea
for the Editor¶
WYMeditor instances are tied to textarea
inputs.
The textarea
provides a few things:
- Its text is used as the initial HTML inside the editor.
- Whenever
html()
is called on the editor, the resulting parsed html is placed inside the (hidden)textarea
. - The editor UI appears in the same location
previously occupied by the
textarea
.
Let’s create a textarea
and give it a submit
button
with the wymupdate
class.
Let’s also start with some existing content.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WYMeditor Quickstart</title>
<script type="text/javascript" src="jquery/jquery.js"></script>
<!-- for IE8, ES5 shims are required-->
<!--[if IE 8]>
<script type="text/javascript" src="es5-shim.js"></script>
<script type="text/javascript" src="es5-sham.js"></script>
<![endif]-->
<script type="text/javascript" src="wymeditor/jquery.wymeditor.min.js"></script>
</head>
<body>
<form method="post" action="">
<textarea id="my-wymeditor"><p>I'm initial content!</p></textarea>
<input type="submit" class="wymupdate" />
</form>
</body>
</html>
Note
The wymupdate
class is just a convenience
so that your textarea
automatically receives the updated HTML
on form submit.
Otherwise,
you’ll need to call html()
yourself.
Use wymeditor()
to Create an Editor¶
Creating a WYMeditor editor instance happens
via a jQuery plugin,
aptly named wymeditor
,
that you call on a textarea
element.
Let’s use the wymeditor()
function
to select the my-wymeditor
textarea
element
and turn it in to a WYMeditor instance.
$(document).ready(function() {
$('#my-wymeditor').wymeditor();
});
Note
We use the $(document).ready
to wait until the DOM is loaded.
Most users will want to do this,
but it’s not strictly necessary.
See Anatomy of an Editor Initialization for more details.
All Together Now¶
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WYMeditor Quickstart</title>
<script type="text/javascript" src="jquery/jquery.js"></script>
<!-- for IE8, ES5 shims are required-->
<!--[if IE 8]>
<script type="text/javascript" src="es5-shim.js"></script>
<script type="text/javascript" src="es5-sham.js"></script>
<![endif]-->
<script type="text/javascript" src="wymeditor/jquery.wymeditor.min.js"></script>
</head>
<body>
<form method="post" action="">
<textarea id="my-wymeditor"><p>I'm initial content!</p></textarea>
<input type="submit" class="wymupdate" />
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#my-wymeditor').wymeditor();
});
</script>
</body>
</html>
Troubleshooting¶
If things aren’t behaving as you’d expect, the first step is to open your browser’s development tools. Chrome, Firefox and recent IE all have acceptable versions. Look for error messages and 404s retrieving files.
It’s also a good idea to compare your code to some of the Contributed Examples and Cookbooks.
Because WYMeditor is based on an iframe, there are restrictions about loading files across domains. That means that you need to serve the WYMeditor media from your current domain.
WYMeditor automagically detects the paths
of required CSS and JS files.
You’ll need to initialize basePath
,
cssPath
and jQueryPath
if you don’t use default file names.
Those are jquery.wymeditor.js
,
wymeditor/skins/{skin name}/screen.css
,
and jquery.js
, respectively.
For details, see Customizing WYMeditor.
Example¶
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WYMeditor Quickstart</title>
<script type="text/javascript" src="jquery/jquery.js"></script>
<!-- for IE8, ES5 shims are required-->
<!--[if IE 8]>
<script type="text/javascript" src="es5-shim.js"></script>
<script type="text/javascript" src="es5-sham.js"></script>
<![endif]-->
<script type="text/javascript" src="wymeditor/jquery.wymeditor.min.js"></script>
</head>
<body>
<form method="post" action="">
<textarea id="my-wymeditor"><p>I'm initial content!</p></textarea>
<input type="submit" class="wymupdate" />
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#my-wymeditor').wymeditor();
});
</script>
</body>
</html>
WYMeditor Integrations¶
Most developers who encounter WYMeditor will do so via another tool, whether that’s a CMS, a web framework or another project. In an effort to encourage code re-use, we try to keep tabs on the projects that help you integrate WYMeditor.
Content Management Systems¶
Django CMS¶
Refinery CMS¶
Refinery CMS is an awesome Rails-based CMS.
Midgard CMS Framework¶
Bundled with the Midgard CMS Framework
Web Frameworks¶
Open Source Projects¶
Commercial Projects¶
Kickstarter¶
Kickstarter is a funding platform for creative projects. As of August 2013, they had raised over $700 million in pledges for more than 45000 creative projects.
They use WYMeditor for their project “Story” editor.
PolicyStat¶
PolicyStat is a policy and procedure management system, primarily targeted towards healthcare.
They use WYMeditor exclusively as their document editor, logging more than 1.25 million editor sessions to date.
Using WYMeditor¶
WYMeditor Skins¶
Loading a Skin¶
The default WYMeditor distribution
now includes all skin javascript and CSS
as part of the bundle.
If you’re using an existing distribution of WYMeditor,
and wish to use another one of the bundled skins,
you simply need to set the skin
option.
If you’re migrating from a version of WYMeditor before 1.0, the differences are explained in the migration documentation on migration-to-1-skin-auto-loading.
Third-party Skins and Skins in Development¶
When doing WYMeditor development or using a non-bundled skin, your desired skin won’t be included in the single bundles of WYMeditor javascript and CSS. To use a skin then, simply include the appropriate CSS and Javascript files on the page before you initialize the editor.
Optimizations¶
For enhanced optimization, you can create your own WYMeditor bundle only containing the skin that you will load, but that will be a very low-impact optimization for most users, as the amount of CSS/Javascript in a skin is very small relative to the impact of WYMeditor itself.
Included Skins¶
Seamless¶
We haven’t found a reliable way to detect that the editor window needs to shrink after content has been removed for IE8 and higher.
If there are wide tables/images, things get a little goofy. The user must use the arrow keys to navigate within the table.
To fix this, we need to set widths for headers/paragraphs/etc and a min-width for the whole iframe, but allow the width to increase if needed. That will provide a whole-page horizontal scrollbar in the case of wide tables, which is probably better than current behavior.
IE has some vertical centering issues:
- IE8: Too much space at the top and not enough at the bottom
- IE9: Too much space at the bottom and not enough at the top
IE8 has some horizontal centering issues:
With full-line content on initialization, IE8 has too much space on the left and not enough on the right.
When the box is open to view HTML, it should float under the toolbar instead of only being visible at the top. Otherwise, it’s kind of annoying to edit HTML for large documents.
- Give the toolbar a gray background so that it contrasts with the page and with the content blocks. That will require changing the background of the containers/classes dropdowns.
- Round the top corners of the toolbar.
- Round the corners for the container/classes dropdowns.
- Round the bottom corners of the iframe body to match the toolbar top.
If you insert an image, the editor height doesn’t automatically update.
Third-Party Skins¶
wymeditor-refine¶
wymeditor-refine is a skin for WYMEditor extracted and tweaked from Refinery CMS
Using WYMeditor Plugins¶
Common WYMeditor Options¶
Getting Help¶
- Wiki/Docs: https://github.com/wymeditor/wymeditor/wiki
- Forum: http://community.wymeditor.org
- Issue tracking: https://github.com/wymeditor/wymeditor/issues
- Official branch: https://github.com/wymeditor/wymeditor
To Read more on contributing, see Contributing.
Anatomy of an Editor Initialization¶
So what actually happens when you call
$('#my-textarea').wymeditor()
?
- Your existing
textarea
is hidden. - An iframe is created in that location.
- The attribute
data-wym-initialized
is added to thetextarea
. - The WYMeditor toolbars and interface load.
- The iframe is initialized with the content from your
textarea
.
Upgrade Guides¶
Upgrading to v1¶
Attention
This document is version–agnostic. For the most accurate, up–to–date information, make sure that you’re reading the latest development version of it.
This page describes the backwards incompatible changes in v1.
High Impact Backwards Incompatible Changes¶
Semantic Versioning¶
From this release onwards, versioning is according to Semantic Versioning 2.
Internet Explorer 7 Not Supported¶
“It’s faster, easier, and more secure than Internet Explorer 6”
This 8 year old browser was a pain to maintain (pun intended) for. It is no longer supported. #679.
Internet Explorer 8 Requires Shims¶
Some shimmable ES5 features are now used in the code. So for IE8, some shims are required. We use, test with, and recommend es5-shim (both shims and shams) (#680).
No jQuery-migrate¶
jQuery-migrate is no longer required (and no longer included, of course).
New Document Getters and Setters¶
- #546
Buttons that cause dialog windows to open
should now be marked with the class
wym_opens_dialog
. Without thisclassName
they will pop up in the background instead of in the foreground. Notice that the class must be on the list item. - #576
editor._html
, the document content setter, was completely removed.editor.xhtml
, the document content getter, was also completely removed.editor.html
, which was deprecated, is now the official document content getter, as well as setter.- #576
editor.rawHtml
is a new document content setter and getter. Witheditor.rawHtml
, contrary toeditor.html
, the contents are not passed through the parser for cleanups and corrections. Instead, they are set or got, exactly as they are.
Test Your Dialogs¶
The wym.dialog
method has a new signature.
It is supposed to be backwards compatible
but it is strongly recommended
that existing implementations
will be modified to use the new signature
or, at least, tested.
Also, dialogs cannot be opened using the exec
method.
Use the wym.dialog
method, instead.
Plugins Should Register Modifications¶
There is a new undo/redo mechanism in place.
In order for document modifications
to be considered “a modification”
and thus allowing them
to be specifically undone/redone by the user,
editor.registerModification()
must be called after each modification
(#512).
Object Re-sizing is Gone¶
Re-sizing of document elements such as images and tables is no longer available (#647, #648, #641)
Using the Legacy Skin and Editor Styles¶
WYMeditor version 1.0 made some significant changes to the default skin and editor styles. For folks who need the old UI, we’ve included a skin and iframe to meet their needs.
These are both labeled as legacy
.
To use them,
first load the legacy skins CSS and JavaScript,
then choose the following options on editor initialization:
$(function() {
$('.wymeditor').wymeditor({
iframeBasePath: "../wymeditor/iframe/legacy/",
skin: "legacy"
});
});
See Example 21-legacy for a demonstration of the pre-1.0 user interface.
It’s easy to provide your own skin and iframe, though, so these will be removed according to WYMeditor’s deprecation policy.
No More Skin Auto-loading¶
Versions of WYMeditor prior to 1.0 would use JavaScript to automatically load your chosen skin’s JavaScript and CSS. While this was a small first-usage usability improvement, it created some “magic” that quickly became confusing when it came time for an optimized, production-ready deployment. In production, you should be looking to reduce the number of HTTP requests as much as possible, which means including the skin’s assets along with your other combined/minified/compressed assets.
The default WYMeditor distribution
now includes all skin JavaScript and CSS
as part of the bundle.
They’re only activated
based on your choice of skin
option, though.
For enhanced optimization,
you can create your own WYMeditor bundle
only containing the skin that you will load,
but that will be
a very low-impact optimization for most users,
as the amount of CSS/Javascript in a skin
is very small relative to the impact of WYMeditor itself.
For more details, see the documentation.
skinPath
No More CSS Configuration Options¶
Versions prior to 1.0 had various options that supported the ability to set CSS rules on various editor and dialog components. In the spirit of “no more skin auto-loading” (see above), and moving WYMeditor closer to just an idiomatic collection of JavaScript/CSS/HTML, we’re no longer supporting those options. All of the things that were previously accomplished with them can be better-accomplished by actually including those rules in stylesheets.
If you’re having difficulty determining the best strategy for migration please open an issue and we’ll be happy to document your use case and help you with a plan.
styles
stylesheet
editorStyles
dialogStyles
addCssRules()
addCssRule()
No More Language Automatic Loading¶
Instead of doing an additional HTTP request to load a language file, the default WYMeditor distribution comes bundled with all of the translation files. If you’re creating your own bundle, you’ll need to include those files on the page before the editor is initialized.
langPath
Low Impact Backwards Incompatible Changes¶
WYMeditor.editor.switchTo
no longer sets the caret into the switched element by default. #540- #588 Bower packaging was fixed and documented. Might be backwards-incompatible. Just in case.
- The jQuery.browser plugin,
a new dependency,
is included in the distribution.
It is required for all jQuery versions;
even the older ones,
which still had a
jQuery.browser
object included. It matters because thejQuery.browser
that is in old jQuery versions thinks that IE11 is Mozilla (can’t blame it, really). - [#605] Removed deprecated
WYMeditor.editor.container
. - [#605]
WYMeditor.editor.mainContainer
was split intoWYMeditor.editor.getRootContainer
andWYMeditor.editor.setRootContainer
. - #613
editor.setSingleSelectionRange
is no longer public API. - #620
editor.rawHtml
no longer callseditor.prepareDocForEditing
. - #639
The
jQueryPath
option was removed. - The
wym._index
editor instance property is removed. - Removed
wym.wrap
andwym.unwrap
editor methods.
Customizing WYMeditor¶
Note
If you’re more of a
“Learning by Doing” person,
there are tons of
hosted examples
to try out if you’re looking for ideas.
You can use View Source
from there
or look in the examples/
directory
of your WYMeditor repository or distribution.
Basics of Customization¶
There are four primary methods of customizing WYMeditor. All of them come down to passing in options on editor initialization and possibly including extra javascript and CSS on your page. The methods are:
1. Using WYMeditor Initialization Options¶
Using WYMeditor Options is the easiest method of customization and the most common. By changing the proper WYMeditor Options, you can change the user interface’s skin, choose from a wide range of plugins, add or remove items from the toolbar, include custom CSS classes for your users to choose and lots more.
2. Using a WYMeditor “Skin”¶
We use WYMeditor Skins to package up user interface tweaks and customizations. There are many included options from which to choose along with the ability to write your own.
3. Using WYMeditor Plugins¶
By Using WYMeditor Plugins you can enable a broad range of extra behaviors. If none of the Included Plugins with Download meet your needs, there’s a range of Third Party Plugins available.
Note
Still can’t find what you need?
No problem.
We have documentation for writing_plugins/index
too.
4. Using a Custom Iframe¶
If you’d like to make tweaks
to the way that your content looks inside the editor
(the part with the blue background),
then you can also choose a custom iframe.
Check out the wymeditor/iframes
directory
for some existing options,
or roll your own.
Using WYMeditor Options¶
All WYMeditor Options are set by passing an options object as the only parameter to the WYMeditor initialization function.
$(".my-wymeditor-class").wymeditor(options)
Example: Start with Existing HTML and Alert on Load¶
Let’s say you want to edit some existing HTML and then annoy your users with an alert box. You’ve got a mean streak, but we can absolutely do that.
The postInit option
lets us define a callback after initialization
and the editor is automatically initialized
with the contents of its textarea
element.
Our options object should look like this:
var options = {
postInit: function() {
alert('OK');
}
};
Full HTML¶
Our stripped-down full example then looks like this:
<html>
<head>
<link rel="stylesheet" type="text/css" href="wymeditor/wymeditor.css" />
</head>
<body>
<form method="post" action="">
<textarea class="my-wymeditor-class">
<p>Hello, World!</p>
</textarea>
<input type="submit" class="wymupdate" />
</form>
<script type="text/javascript" src="jquery/jquery.js"></script>
<script type="text/javascript" src="wymeditor/jquery.wymeditor.js"></script>
<script type="text/javascript">
$(function() {
var options = {
postInit: function() {
alert('OK');
}
};
jQuery(".wymeditor").wymeditor(options);
</script>
</body>
</html>
WYMeditor Options¶
Note
All options and their defaults are located
in wymeditor/core.js
in the jQuery.fn.wymeditor
method.
Though if they’re not documented here,
they’re subject to change between releases
without following our deprecation policy.
Common Options¶
html
¶
Initializes the editor’s HTML content.
html: "<p>Hello, World!<\/p>"
As an alternative, you can just fill the textarea WYMeditor is replacing with the initial HTML.
lang
¶
The language to use with WYMeditor. Default is English (en). Codes are in ISO-639-1 format.
Language packs are stored in the wymeditor/lang
directory.
To use the Polish translation instead,
use the value pl
.
$('.wymeditor').wymeditor({
lang: 'pl'
});
toolsItems
¶
An array of tools buttons, inserted in the tools panel, in the form of:
[
{
'name': 'value',
'title': 'value',
'css': 'value'
}
]
toolsItems: [
{'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
{'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'}
]
toolsItems: [
{'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
{'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
{'name': 'Superscript', 'title': 'Superscript', 'css': 'wym_tools_superscript'},
{'name': 'Subscript', 'title': 'Subscript', 'css': 'wym_tools_subscript'},
{'name': 'InsertOrderedList', 'title': 'Ordered_List', 'css': 'wym_tools_ordered_list'},
{'name': 'InsertUnorderedList', 'title': 'Unordered_List', 'css': 'wym_tools_unordered_list'},
{'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
{'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
{'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
{'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
{'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
{'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
{'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
{'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
{'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'},
{'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
{'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
]
containersItems
¶
An array of containers buttons, inserted in the containers panel in the form of:
[
{
'name': 'value',
'title': 'value',
'css': 'value'
}
]
containersItems: [
{'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
{'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'}
]
classesItems
¶
An array of classes buttons, inserted in the classes panel in the form of:
[
{
'name': 'value',
'title': 'value',
'expr': 'value'
}
]
expr
is a jQuery selector that allows you
to control to which elements
the class defined by name
can be applied.
Only elements matching the selector expr
will be given the class
on a user’s click.
Let’s support adding the class date
to paragraphs,
and the class hidden-note
to paragraphs
that don’t already have the class important
.
classesItems: [
{'name': 'date', 'title': 'PARA: Date', 'expr': 'p'},
{'name': 'hidden-note', 'title': 'PARA: Hidden note', 'expr': 'p[@class!="important"]'}
]
preInit
¶
A custom function which will be executed directly before WYMeditor’s initialization.
- wym: the WYMeditor instance
Uncommon Options¶
Most of these options are only required in abnormal deployments, or useful for deep customization.
Note
Any options not documented are considered private and are subject to change between releases without following the deprecation policy.
basePath
¶
WYMeditor’s relative/absolute base path (including the trailing slash).
This value is automatically guessed by computeWymPath
,
which looks for the script
element
loading jquery.wymeditor.js
.
basePath: "/admin/edit/wymeditor/"
wymPath
¶
WYMeditor’s main JS file path.
Similarly to basePath,
this value is automatically guessed by computeBasePath
.
iframeBasePath
¶
The path to the directory containing the iframe that will be initialized inside the editor body itself.
This value is automatically guessed,
based on the basePath
value.
updateSelector
and updateEvent
¶
Note
Will be removed in 1.0.
Instead,
users should do their own event handling/registration
and make a call to wym.update()
.
Allows you to update the value
of the element replaced by WYMeditor
(typically a textarea
)
with the editor’s content
on .while e.g. clicking on a
button in your page.
UI Customization Options¶
These options allow deep customization of the structure of WYMeditor’s user interface by changing the HTML templates used to generate various UI components. Rather than using these, most user’s prefer WYMeditor Skins.
List of HTML Template Options¶
boxHtml
logoHtml
iframeHtml
toolsHtml
toolsItemHtml
containersHtml
containersItemHtml
classesHtml
classesItemHtml
statusHtml
htmlHtml
dialogHtml
dialogLinkHtml
dialogFeatures The dialogs’ default features. e.g.
dialogFeatures: "menubar=no,titlebar=no,toolbar=no,resizable=no,width=560,height=300,top=0,left=0"
dialogImageHtml
dialogTableHtml
dialogPasteHtml
dialogPreviewHtml
UI Selectors Options¶
These selectors are used internally by WYMeditor to bind events and control the user interface. You’ll probably only want to modify them if you’ve already changed one of the UI Customization Options.
List of Selectors¶
- boxSelector
- toolsSelector
- toolsListSelector
- containersSelector
- classesSelector
- htmlSelector
- iframeSelector
- statusSelector
- toolSelector
- containerSelector
- classSelector
- htmlValSelector
- hrefSelector
- srcSelector
- titleSelector
- altSelector
- textSelector
- rowsSelector
- colsSelector
- captionSelector
- submitSelector
- cancelSelector
- previewSelector
- dialogLinkSelector
- dialogImageSelector
- dialogTableSelector
- dialogPasteSelector
- dialogPreviewSelector
- updateSelector
Example Selector Customization¶
classesSelector: ".wym_classes"
Each release of WYMeditor comes bundled with a set of examples (available online here). To run these locally, they need to be served on a proper web server. If not, WYMeditor might not work properly as most modern browsers block certain JavaScript functionality for local files.
If you’ve finished Configuring Your Development Environment, then serving the examples is easy:
$ grunt server:dist
$ google-chrome "http://localhost:9000/examples/"
I have managed to easily integrate WYMeditor into Django’s administrative interface.
Here is how I did it...
First I copied the wymeditor to my project’s static-served files directory,
which in my case had an URL prefix of /site/media/
There I put the wymeditor
, jquery
and a special file
admin_textarea.js
, that I have written myself consisting of:
$(document).ready(function() {
$('head', document).append('<link rel="stylesheet" type="text/css" media="screen" href="/site/media/wymeditor/skins/default/screen.css" />');
$("textarea").wymeditor({
updateSelector: "input:submit",
updateEvent: "click"
});
});
This file instructs the browser to load an additional WYMeditor’s CSS and to
convert each <textarea>
HTML tag into a WYMeditor.
In each of the Django models that I whished to set WYMeditor for, I have added
the following js
setting to the Admin section:
class ExampleModel(models.Model):
text = models.TextField() # each TextField will have WYM editing enabled
class Admin:
js = ('/site/media/jquery.js',
'/site/media/wymeditor/jquery.wymeditor.js',
'/site/media/admin_textarea.js')
That is it. If you wish to use WYM in your own Django app, just follow the
steps and replace the /site/media/...
with whatever your static media
prefix is.
To integrate Wymeditor with the Django filebrowser, put the code below in a file,
set the fb_url
variable to point to your filebrowser instance and add the
file to your Javascript headers:
<script type="text/javascript" src="/media/wymeditor/plugins/jquery.wymeditor.filebrowser.js"></script>
or in your admin.py:
class Media:
js = ('/media/wymeditor/plugins/jquery.wymeditor.filebrowser.js',)
Add the postInitDialog
parameter to the Wymeditor initialization:
$('textarea').wymeditor({
postInitDialog: wymeditor_filebrowser
});
If you already have a postInitDialog
function, you need to put a call to
wymeditor_filebrowser
inside that function:
$('textarea').wymeditor({
postInitDialog: function (wym, wdw) {
// Your code here...
// Filebrowser callback
wymeditor_filebrowser(wym, wdw);
}
});
Then you should be able to click on the Filebrowser link to select an image.
Code:
wymeditor_filebrowser = function(wym, wdw) {
// the URL to the Django filebrowser, depends on your URLconf
var fb_url = '/admin/filebrowser/';
var dlg = jQuery(wdw.document.body);
if (dlg.hasClass('wym_dialog_image')) {
// this is an image dialog
dlg.find('.wym_src').css('width', '200px').attr('id', 'filebrowser')
.after('<a id="fb_link" title="Filebrowser" href="#">Filebrowser</a>');
dlg.find('fieldset')
.append('<a id="link_filebrowser"><img id="image_filebrowser" /></a>' +
'<br /><span id="help_filebrowser"></span>');
dlg.find('#fb_link')
.click(function() {
fb_window = wdw.open(fb_url + '?pop=1', 'filebrowser', 'height=600,width=840,resizable=yes,scrollbars=yes');
fb_window.focus();
return false;
});
}
}
The purpose of this page is to explain how an image gallery built with jQuery’s jCarousel plugin and based on data loaded via AJAX can be integrated into WYMEditor. The example is built using CodeIgniter (CI) for structure and queries. It uses a series of JS includes in the image dialog, a CI controller function called via AJAX, and a CI model function containing an active record query on an images database. This example presupposes that you have a functioning install of WYMEditor v0.4 and you are relatively familiar with hacking it. It also assumes that you have the source files for and know how to implement an image gallery based on jCarousel. More information and source downloads for jCarousel can be found here: http://sorgalla.com/jcarousel/
You will need to include 3 js files and 2 css files into the dialogs’ HTML.
This is done by using the dialogHtml
option, which makes up the outer shell
of the dialog boxes that are opened by the top buttons in WYMeditor. You will
need to change the paths here to match up with whatever your setup is:
$('.wymeditor').wymeditor({
//options
dialogHtml: "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
+ " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
+ "<html><head>"
+ "<link rel='stylesheet' type='text/css' media='screen'"
+ " href='"
+ WYM_CSS_PATH
+ "' />"
+ "<title>"
+ WYM_DIALOG_TITLE
+ "</title>"
+ "<script type='text/javascript'"
+ " src='"
+ WYM_JQUERY_PATH
+ "'></script>"
+ "<script type='text/javascript'"
+ " src='"
+ WYM_BASE_PATH
+ "jquery.wymeditor.js'></script>"
+ "<script type='text/javascript' src='/scripts/jquery.imager.js'></script>"
+ "<script type='text/javascript' src='/scripts/easing.js'></script>"
+ "<script type='text/javascript' src='/scripts/jquery.jcarousel.js'></script>"
+ "<link rel='stylesheet' type='text/css' href='/css/jquery.jcarousel.css' />"
+ "<link rel='stylesheet' type='text/css' href='/css/skins/tango/skin.css' />"
+ "<style type='text/css'>"
+ ".jcarousel-skin-tango.jcarousel-container-horizontal { width: 85%; }"
+ ".jcarousel-skin-tango .jcarousel-clip-horizontal { width: 100%; }"
+ "</style>"
+ "</head>"
+ WYM_DIALOG_BODY
+ "</html>",
dialogImageHtml: "<body class='wym_dialog wym_dialog_image'"
+ " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
+ ">"
+ "<form>"
+ "<fieldset>"
+ "<legend>{Image}</legend>"
+ "<div class='row'>"
+ "<label>{URL}</label>"
+ "<input type='text' class='wym_src' value='' size='40' />"
+ "</div>"
+ "<div class='row'>"
+ "<label>{Alternative_Text}</label>"
+ "<input type='text' class='wym_alt' value='' size='40' />"
+ "</div>"
+ "<div class='row'>"
+ "<label>{Title}</label>"
+ "<input type='text' class='wym_title' value='' size='40' />"
+ "</div>"
+ "<div class='row row-indent'>"
+ "<input class='wym_submit' type='button'"
+ " value='{Submit}' />"
+ "<input class='wym_cancel' type='button'"
+ "value='{Cancel}' />"
+ "</div>"
+ "</fieldset>"
+ "</form>"
+ "<div id='gallery'><h2>Loading images, please wait...</h2></div>"
+ "</body>"
//other options
});
Note
Options specified when calling a new WYMEditor instance must be separated by commas. If you add in some other options to the example shown above, you will need to add a comma to the end of the dialogImageHtml assignment as well as all the rest of the new options except the last one.
jquery.js, easing.js, and jquery.jcarousel.js are all pre-built components that can be downloaded from various sites - see the jCarousel link above. jquery.imager.js is a custom script that will be built below. jquery.jcarousel.css and skins/tango/skin.css are parts of jCarousel, please refer to the jCarousel link above for more information. The header styles are used to make jCarousel expand / contract based on the width of the dialog window.
OK, now all of our scripts should be in place. Upload the file, refresh your WYMEditor install and open the image dialog; you should see all that stuff in the head of the document. It’s usually a good idea to copy the paths out of the source here, paste them into another browser window, and make sure they open; this is just to make sure you have the paths correct and all the files are in the right place.
The next step is to add a target div
to the image dialog body HTML. This
gives us a place to inject our dynamic image list a bit later once we have
built it with AJAX and PHP. In the dialogImageHtml
string (around line 619)
add the following line:
+ "<div id='gallery'><h2>Loading images, please wait...</h2></div>"
This line should go after the form but before the close body tag, like so:
+ "</div>"
+ "</fieldset>"
+ "</form>"
+ "<div id='gallery'><h2>Loading images, please wait...</h2></div>"
+ "</body>",
Once this is in place, upload and refresh WYMEditor again, then re-open the image dialog. You should now see a big fat “Loading images, please wait...” message underneath the form. OK now its time for the slick stuff.
Make a new javascript file and save it as jquery.imager.js or whatever else you want to call it; just make sure it’s included in the dialog HTML. Paste the following code into the js file:
// JavaScript Document
var $j = jQuery.noConflict();
$j(function() {
//set up your AJAX call
$.ajax({
type: "POST",
url: "http://www.your-domain.com/controller/ajaxer", //path to your PHP function
data: "img=test&stamp=now", //not required for this example, but you can POST data to your PHP function like this
success: function(msg){ //trigger this code if the PHP function successfully returns data
/*
The PHP function needs to return an image UL with the following prototype:
<ul id="mycarousel" class="jcarousel-skin-tango">
<li><img src='http://www.test.com/upload/titan.jpg' width='68' height='60' alt='Tennessee Titans Running Back Taken Christmas Eve 2006 At Ralph Wilson Stadium In Buffalo Ny - added 04:05 PM, 07/02/2007' title='titan' /></li>
<li><img src='http://www.test.com/upload/canyon.jpg' width='68' height='60' alt='Grand Canyon With Storm Clouds Viewed From South Western Edge - added 04:03 PM, 07/02/2007' title='nosebleed' /></li>
<li><img src='http://www.test.com/upload/img_2_big.jpg' width='68' height='60' alt='Another Chair From Fidm Museum - added 06:19 AM, 06/30/2007' title='chair' /></li>
</ul>
The returning HTML is contained in the msg variable.
*/
//inject the image list into the target div with ID of "gallery"
$("div#gallery").html(msg);
//Once the list is in place we can create a new instance of jCarousel and point it at the image list
//which has an ID of 'mycarousel'. For more information on these options see http://sorgalla.com/jcarousel/
jQuery('#mycarousel').jcarousel({
easing: 'backinout',
visible: 5,
animation: 500
});
//assign behaviors to the jCarousel thumbnails, triggered when they are clicked upon.
$(".jcarousel-skin-tango img").click(function() {
//$(this) is a reference to the thumbnail that got clicked
$("input.wym_src").val($(this).attr('src')); //inject the thumb's src attribute into the wym_src input
$("input.wym_alt").val($(this).attr('alt')); //inject the thumb's alt attribute into the wym_alt input
$("input.wym_title").val($(this).attr('title')); //inject the thumb's title attribute into the wym_title input
//loop through all the images and remove their "on" states if it exists
$(".jcarousel-skin-tango img").each(function(i){
$(this).removeClass("on");
});
//add "on" state to the selected image
$(this).addClass("on").fadeIn('slow');
})
}
});
});
Now you have a structure and behaviors for inserting your image code into the dialog. Now all you need is some images!
I have an images mySQL table with the following structure:
img_id int(11)
img_upload_date int(11)
img_upload_by int(11)
img_name varchar(64)
img_file_name varchar(64)
img_size float
img_width int(11)
img_height int(11)
img_string varchar(64)
img_alt varchar(255)
Basically you can make an AJAX call to any PHP page that will return a list of
the images you want to display in the gallery. It can be connect to a database
or not; that’s up to you. For my particular setup, I have a CI function in my
Content model called get_images()
that returns either a list of all images
in the DB or a specific image if you send a valid ID. The model function goes
like this:
//ARGS: image ID (int) or -1 to get all images
function get_images($img_id)
{
if($img_id != -1){ $this->db->where('img_id', $img_id); }
$this->db->orderby('img_upload_date desc');
$query = $this->db->get('tq_images');
if($query->num_rows() > 0)
{
return $query->result_array();
} else {
return NULL;
}
}
This will return either one or many rows of the database, ordered by the date
the image was added, descending. If you send -1 as $img_id
it will return
all images; if you send it a number it will return a specific row if it’s a
valid ID. If the function can’t find any rows based on what you sent it, it
will return NULL. If you need more information about codeigniter or model
functions see http://codeigniter.com/user_guide/.
Now you will make a controller function that is actually accessible via a browser or AJAX call. Open a CI controller and insert the following function:
function ajaxer()
{
//pull in data from javascript AJAX call (not used for now)
$img = $_POST['img'];
$stamp = $_POST['stamp'];
//call your model function
$img = $this->Content->get_images(-1);
//create a the return string. This is the structure for your jCarousel list.
$lst = "<ul id='mycarousel' class='jcarousel-skin-tango'>\n";
//loop through the images record set. This should be a list of all the images you want to display.
foreach($img as $i)
{
//call a custom function in another model to format the date...you probly don't need this
$date = $this->Page->get_date($i['img_upload_date']);
//Build a list item for each image in the database. Insert values as needed.
//This will produce an unordered list with the prototype specified in jquery.imager.js
$lst .= "<li><img src='". base_url() . "upload/" . $i['img_file_name'] ."' width='68' height='60' alt='" . $i['img_alt'] . " - added " . $date . "' title='" . $i['img_name'] . "'/></li>\n"; //$i['img_string']
}
//close the list
$lst .= "</ul>\n";
//return the list to the dialog
echo $lst;
}
And that’s pretty much it. When you are setting up your AJAX call, its URL
attribute should be pointed at this controller function. ajaxer()
will call
the model function outlined above then process the returned recordset into an
HTML list. Echoing out the list will return it to your Javascript code as the
msg
variable I mentioned above. You should already have code in place in
jquery.imager.js to handle the incoming data and inject it where it needs to
go. In that same script you have already specified click behaviors for each
thumbnail in the list. If you need more information about codeigniter or
controller functions see http://codeigniter.com/user_guide/.
Now upload everything and refresh WYMEditor. If you set everything up correctly, you should see your jCarousel load in underneath the dialog form. If you click on an image, you should see its values pop into the input boxes above the carousel. Once you have the correct information in the boxes, hit “submit” on the dialog as usual and WYMEditor should plop it into your piece. Repeat as needed.
If you have any questions about specific technologies used above, see their respective websites. If you have questions about my code, hit me up at rhinocerous at gmail dot com.
This page explains step by step how to integrate WYMeditor version 0.4 into sNews version 1.6. sNews is a light and open source Content Management System. All information about sNews is available on its official site.
Since sNews already includes an editor, some hacks are mandatory in order to replace the default editor with WYMeditor. These hacks are all listed below. Fortunately, in order to avoid you these tedious code modifications, a ready-to-run archive file has been prepared. Unarchive this file and install sNews according to readme.html before to run it. Hacks made inside sNews have all been enclosed between tags [WYMeditor Hack] for allowing you to easily retreive them.
Download archive file sNews16.zip
Unarchive sNews16.zip under your Web space and follow the install instructions contained into readme.html.
Download archive file wymeditor-0.4.tar.gz
Unarchive wymeditor-0.4.tar.gz into a temporary directory and move directories ‘jquery’ and ‘wymeditor’ into ‘sNews’ root directory
All the following code modifications must be made in snews.php.
Replace line 325
if ($_SESSION[db('website').'Logged_In'] == token()) {js();}
with
if ($_SESSION[db('website').'Logged_In'] == token()) {js();
echo
' <link rel="stylesheet" type="text/css" media="screen" href="wymeditor/skins/default/screen.css" />
<script type="text/javascript" src="jquery/jquery.js"></script>
<script type="text/javascript" src="wymeditor/jquery.wymeditor.js"></script>
<script type="text/javascript">
jQuery(function() {
jQuery(".wymeditor").wymeditor({
cssPath: "'.db('website').'wymeditor/skins/default/screen.css",
jQueryPath: "'.db('website').'jquery/jquery.js",
wymPath: "'.db('website').'wymeditor/jquery.wymeditor.js"
});
});
</script>';
}
This code loads WYMeditor with its default skin and initializes WYMeditor with correct paths.
Replace line 1048
case 'textarea': $output = '<p>'.$lbl.':<br /><textarea name="'.$name.'" rows="'.$rows.'" cols="'.$cols.'"'.$attribs.'>'.$value.'</textarea></p>'; break;
with
case 'textarea':
if ($_SESSION[db('website').'Logged_In'] == token()) {
$output = '<p>'.$lbl.':<br /><textarea class="wymeditor" name="'.$name.'" rows="'.$rows.'" cols="'.$cols.'"'.$attribs.'>'.$value.'</textarea></p>'; break;
} else {
$output = '<p>'.$lbl.':<br /><textarea name="'.$name.'" rows="'.$rows.'" cols="'.$cols.'"'.$attribs.'>'.$value.'</textarea></p>'; break;
}
break;
This code allows WYMeditor to replace the ‘textarea’ block with a WYMeditor instance.
Comment from line 1312
/* echo '<p>';
to line 1322
echo '</p>';*/[/code]
This code disables the default editor toolbar.
Replace line 1327
echo html_input('fieldset', '', '', '', '', '', '', '', '', '', '', '', '', '', '<a title="'.l('customize').'" onclick="toggle(\'preview\')" style="cursor: pointer;">'.l('preview').'</a>');
with
if ($_SESSION[db('website').'Logged_In'] == token()) {
echo html_input('fieldset', '', '', '', '', '', '', '', '', '', '', '', '', '', '<a title="'.l('customize').'" class="wymupdate" onclick="toggle(\'preview\')" style="cursor: pointer;">'.l('preview').'</a>');
} else {
echo html_input('fieldset', '', '', '', '', '', '', '', '', '', '', '', '', '', '<a title="'.l('customize').'" onclick="toggle(\'preview\')" style="cursor: pointer;">'.l('preview').'</a>');
}
This code allows the edited article to be correctly previewed by sNews each time the sNews preview button is clicked.
Replace line 1393
echo html_input('submit', $frm_task, $frm_task, $frm_submit, '', 'button', '', '', '', '', '', '', '', '', '');
with
echo html_input('submit', $frm_task, $frm_task, $frm_submit, '', 'wymupdate', '', '', '', '', '', '', '', '', '');
This code allows the edited article to be correctly saved when the sNews save button is clicked.
That’s all, sNews should now run WYMeditor instead of its default editor. If you experiment problems in running or using WYMeditor inside sNews, you are invited to read this topic on the WYMeditor forum or this one on the sNews forum. If you don’t find the answer to your problem, feel free to post a new message.
WYM Editor Helper is a plugin that makes it dead easy to incorporate the WYM editor into your Rails views.
Follow these steps to use:
From your project’s root, run:
$ ruby script/plugin install svn://zuurstof.openminds.be/home/kaizer/svn/rails_stuff/plugins/wym_editor_helper $ rake wym:install
Put
<%= wym_editor_initialize %>
in the view that will host the text editing form. Prefereably this goes into your html’s HEAD, to keep our html W3C valid. Use<% content_for :head do %> <%= wym_editor_initialize %> <% end %>
in the view that needs the editor, and<%= yield :head %>
in the layout. This means the editor will only load when it is truly called for.In your form, instead of i.e.
<%= text_area :article, :content %>
, use<%= wym_editor :article, :content %>
OR add awymeditor
class to thetextarea
.Add a
wymupdate
class to the submit button.
This plugin uses an svn:external
to automatically get the latest version of
WYMeditor. If for some reason the checked out version is not working, you can
install a different version like so:
$ svn export svn://svn.wymeditor.org/wymeditor/tags/0.4 vendor/plugins/wym_editor_helper/assets/wymeditor
To see what versions are available, check the repository browser at
http://files.wymeditor.org/wymeditor/. Keep in mind that if you check out a
newer version of WYM, you need to re-run the wym:install
rake command to
actually copy the wym files to the public dir. If you do so, be sure to first
back up your configuration file (/javascripts/boot_wym.js) if you made any
changes to it.
Updates on this plugin will appear on http://www.gorilla-webdesign.be/artikel/42-WYM+on+Rails
WYMeditor Plugins¶
Included Plugins with Download¶
Embed¶
Add description.
Fullscreen¶
Add description.
Hovertools¶
This plugin improves visual feedback by:
- displaying a tool’s title in the status bar while the mouse hovers over it.
- changing background color of elements which match a condition, e.g. on which a class can be applied.
List¶
Add description.
RDFA¶
Add description.
Resizable¶
Add description.
Table¶
Add description.
Tidy¶
Add description.
Structured Headings¶
This plugin modifies the styling and function of headings in the editor to make them easier to use to structure documents. Currently in development.
Features¶
- Simplifies the process of adding headings to a document. Instead of having to choose between a heading 1-6 in the containers panel, those options are replaced with one single “Heading” option that inserts a heading at the same level as the preceding heading in the document.
- Makes it easier to adjust heading levels properly. In order to change the level of a heading, the indent and outdent tools can be used to lower and raise the level of the heading respectively. The indent tool will also prevent a user from indenting a heading more than one level below its preceding heading.
- Automatically numbers headings to better outline a document.
- Provides an optional tool that can be used to automatically fix improper heading structure in a document (although it’s still in development).
- More features to come on further development.
Applying Structured Headings Styling outside of the Editor¶
The numbering added to headings in the editor is not parsed with the content of the document, but rather, it is added as styling using CSS.
To apply the heading numbering to a document outside of the editor, follow these steps:
- Get the CSS for the structured headings. If you are using IE8+, Chrome,
or Firefox, enter the
WYMeditor.printStructuredHeadingsCSS()
command in the browser console on the page of the editor using the structured headings plugin to print the CSS to the console. - Either copy this CSS to an existing stylesheet or make a new stylesheet with this CSS.
- Apply the stylesheet with the CSS to all of the pages that contain documents that had heading numbering added to them in the editor.
Third Party Plugins¶
Modal Dialog (by samuelcole)¶
https://github.com/samuelcole/modal_dialog
Replaces the default dialog behavior (new window) with a modal dialog. Known bug in IE, more information here.
Alignment (by Patabugen)¶
https://bitbucket.org/Patabugen/wymeditor-plugins/src
Set Text Alignment with classes.
Site Links (by Patabugen)¶
https://bitbucket.org/Patabugen/wymeditor-plugins/src
A plugin to add a dropdown of links to the Links dialog, especially for making it easier to link to your own site (or any other predefined set).
Can also add a File Upload form to let you upload files right from the Link dialog.
Image Float (by Patabugen)¶
https://bitbucket.org/Patabugen/wymeditor-plugins/src
Float images with classes.
Image Upload (by Patabugen)¶
https://bitbucket.org/Patabugen/wymeditor-plugins/src
Adds an Image Upload form to the Insert Image dialog.
Catch Paste¶
Force automatic “Paste From Word” usage so that all pasted content is properly cleaned.
Plugins¶
The WYMeditor includes a simple plugin which demonstrates how easy it is to write plugins for WYMeditor.
Note
Next versions will use a more advanced events handling architecture which will allow a better interaction between plugins and the editor.
Using a Plugin¶
To use a plugin you need to include its script file using a <script>
tag
and then initialize it (passing an instance of wym) in the postInit
function, passed as an option when you call $().wymeditor()
.
postInit: function(wym) {
//activate the 'tidy' plugin, which cleans up the HTML
//'wym' is the WYMeditor instance
var wymtidy = wym.tidy();
// You may also need to run some init functions on the plugin, however this depends on the plugin.
wymtidy.init(wym);
}
Writing Plugins¶
Writing a plugin for WYMeditor is quite easy, if you have a basic knowledge of jQuery and Object Oriented Programming in Javascript.
Once you decide the name for your plugin you should create a folder of that
name in the plugins folder and then a file called
jquery.wymeditor.__plugin_name__.js. You need to include this file in your HTML
using a <script>
tag.
For details on interacting with the editor, including the selection, see API.
Example Plugin¶
// wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js
// Extend WYM
Wymeditor.prototype.hovertools = function() {
var wym = this;
//bind events on buttons
$j(this._box).find(this._options.toolSelector).hover(
function() {
wym.status($j(this).html());
},
function() {
wym.status(' ');
}
);
};
This example extends WYMeditor with a new method hovertools
, and uses
jQuery to execute a function while the mouse hovers over WYMeditor tools.
this._box
is the WYMeditor container, this._options.toolSelector
is the
jQuery selector, wym.status()
displays a message in the status bar.
Adding a Button to the Tool Bar¶
// Find the editor box
var wym = this,
$box = jQuery(this._box);
//construct the button's html
var html = '' +
"<li class='wym_tools_fullscreen'>" +
"<a name='Fullscreen' href='#' " +
"style='background-image: url(" +
wym._options.basePath +
"plugins/fullscreen/icon_fullscreen.gif)'>" +
"Fullscreen" +
"</a>" +
"</li>";
//add the button to the tools box
$box.find(wym._options.toolsSelector + wym._options.toolsListSelector)
.append(html);
(work in progress)
Available Plugins¶
See WYMeditor Plugins for a listing and descriptions of the plugins included in the download and available third party plugins.
Writing WYMeditor Plugins¶
API¶
Note
In the code examples below, wym
is a variable which refers to the
WYMeditor instance, and which must be initialized.
Core¶
html(html)
¶
Get or set the editor’s HTML value.
When the html argument is provided, that HTML will be parsed and loaded into the editor. The document will be ready for editing.
When called without an argument, it will provide an HTML representation of the document that is in the editor.
Example:
wym.html("<p>Hello, World.</p>");
wym.html();// "<p>Hello, World.</p>"
rawHtml(html)
¶
Get or set raw HTML value.
Value is not parsed.
After HTML insertion, document is not prepared for editing
(prepareDocForEditing()
is not called).
If you are not sure which one to use, html()
will most likely be the answer.
update()
¶
Update the value of the element replaced by WYMeditor and the value of the HTML source textarea.
getCurrentState()
¶
Returns an object with the current state of the editor.
The state includes:
html
- The return value of
editor.rawHtml()
. savedSelection
- A Rangy saved selection, if anything is selected.
The
win
and thedoc
properties are deleted, instead of referencing the window object and the document object, respectively. In order to provide this as an argument to Rangy’srestoreSelection
, these must be reassigned.
It may include more things in the future.
WYMeditor.instances
¶
Array consisting of WYMeditor instances. When a WYMeditor is initialized it is appended to this array.
WYMeditor.version
¶
WYMeditor’s version string.
iframeInitialized
¶
A boolean. After an editor’s iframe
initialization, this is set to
true
.
During the execution of postInit, for example, this can be
expected to be true
, if the editor initialized succesfully.
vanish()
¶
Removes the WYMeditor instance from existence and replaces the ‘data-wym-initialized’ attribute of its textarea with ‘data-wym-vanished’.
wym.vanish();
jQuery.getWymeditorByTextarea()
¶
Get the WYMeditor instance of a textarea element. If an editor is not initialized for the textarea, returns false.
var myWym,
myDocument;
myWym = jQuery.getWymeditorByTextarea(jQuery('textarea#myDocument'));
if (myWym) {
myDocument = myWym.html();
}
element
¶
The editor’s textarea element.
EVENTS
¶
An object, containing event names, which are triggered by the editor in various circumstances. jQuery can be used to add handlers to these events.
Available events:
postBlockMaybeCreated
- Triggered after a block type element may have been created.
postIframeInitialization
- Triggered after the editor’s Iframe has been initialized.
postModification
- Triggered after a change was registered with
registerModification()
. postUndo
- Triggered after undo.
postRedo
- Triggered after redo.
Example of adding a handler to one of the events:
jQuery(wym.element).bind(
WYMeditor.EVENTS.postBlockMaybeCreated,
myHandlerFunction
);
documentStructureManager.setDefaultRootContainer(tagName)
¶
Sets the default root container to tagName
.
Example: .. code-block:: javascript
wym.documentStructureManager.setDefaultRootContainer(“div”);
Selection Setting and Getting¶
Note
For selection setting and selection getting, WYMeditor uses the Rangy library internally.
The Rangy library doesn’t seem to provide a consistent interface for selection getting. Instead, the selection could be in many cases described differently in different browsers.
Additionally, erroneous selections are performed by some browsers under certain conditions.
In light of this, an effort has been made to provide reliable methods in WYMeditor for selection setting an getting.
Core contributors, as well as plugin authors, are encouraged to use these methods and to avoid using the Rangy API directly.
If you find these methods lack a feature that you require, then please file an issue describing your requirement so that we could look into answering it in a consistent and reliable way.
Pull requests regarding this or any other issue are warmly welcomed. For detailed pull request recommendations, please see our documentation on Contributing.
hasSelection()
¶
Returns true
if there is any selection in the document.
Returns false
otherwise.
deselect()
¶
Removes selection.
nodeAfterSel()
¶
Get the node that is immediately after the selection, whether it is collapsed or not.
doesElementContainSelection(element)
¶
Returns true
if the provided element contains at least part of the
selection.
Otherwise returns false
.
selectedContainer()
¶
Get the selected container.
- If no selection, returns
false
. - If selection starts and ends in the same element, returns that element.
- If an element that contains one end of the selection is ancestor to the element that contains the other end, return that ancestor element.
- Otherwise, returns
false
.
For example (|
marks selection ends):
.. code-block:: html
<p>|Foo <i>bar|</i></p>
The p
is returned.
<p>Foo <i>|bar|</i></p>
The i
is returned.
getRootContainer()
¶
Returns the root container, in which the selection is entirely in.
Example: get the selected root container.
wym.status(wym.mainContainer().tagName);
getSelectedImage()
¶
If selection encompasses exactly a single image, returns that image.
Otherwise returns false
.
canSetCaretBefore(node)
¶
Check whether it is possible to set a collapsed selection immediately before provided node.
For an example see the test named ‘selection: Set and get collapsed selection’.
Returns true if yes and false if no.
setCaretBefore(node)
¶
This sets a collapsed selection before the specified node.
Note
Due to browser and/or Rangy bugs it has been decided that node
could be
either a text node or a br
element and if it is a br
element it must
either have no previousSibling
or its previousSibling
must be a text
node, a br
element or any block element.
It checks whether this is possible, before doing so, using
canSetCaretBefore
.
canSetCaretIn(node)
¶
Check whether it is possible to set a collapsed selection at the start inside
a provided node. This is useful for the same reason as canSetCaretBefore
.
setCaretIn(element)
¶
Sets a collapsed selection at the start inside a provided element.
Note
Due to what seems like browser bugs, setting the caret inside an inline element results in a selection across the contents of that element.
For this reason it might not be useful for implementation of features.
It can, however, be useful in tests.
It checks whether this is possible, before doing so, using
canSetCaretIn
.
restoreSelectionAfterManipulation(manipulationFunc)
¶
A helper function to ensure that the selection is restored to the same location after a potentially complicated DOM manipulation is performed. This also handles the case where the DOM manipulation throws an error by cleaning up any selection markers that were added to the DOM.
manipulationFunc
is a function that takes no arguments and performs the
manipulation. It should return true if changes were made that could have
potentially destroyed the selection.
selection()
¶
Returns the Rangy selection.
Content Manipulation¶
body()
¶
Returns the document’s body
element.
Example; get the root-level nodes in the document:
var rootNodes = wym.body().childNodes;
$body()
¶
Returns a jQuery object of the document’s body element.
Example; find first paragraph in the document:
var $firstP = wym.$body().children('p').first();
exec(cmd)
¶
Execute a command. Supported cmd
values:
- Italic
- set/unset
em
on the selection. - Superscript
- set/unset
sup
on the selection. - Subscript
- set/unset
sub
on the selection. - InsertOrderedList
- create/remove an ordered list, based on the selection.
- InsertUnorderedList
- create/remove an unordered list, based on the selection.
- Indent
- indent the list element.
- Outdent
- outdent the list element.
- Undo
- undo an action.
- Redo
- redo an action.
- Unlink
- remove a link, based on the selection.
- ToggleHtml
- show/hide the HTML value.
link(attrs)
¶
Turns the selected text into an a
element with the provided attributes.
If an a
element is already selected, modifies its attributes.
Attributes are provided as key-value pairs, in attrs
.
Example:
// Perform some selection and then:
wym.link({
href: "http://example.com",
title: "Example"
});
insertImage(attrs)
¶
Inserts an img
element with the provided attributes.
Attributes are provided as key-value pairs, in attrs
.
Example:
// Perform some selection and then:
wym.insertImage({
src: "example.jpg",
alt: "Example"
});
insert(data)
¶
Parameters
- data: XHTML string
Description
Insert XHTML string at the cursor position. If there’s a selection, it is replaced bydata
.
Example:
wym.insert('<strong>Hello, World.</strong>');
setRootContainer(sType)
¶
Set the root container in which the selection is entirely in.
A root container is a root element in the document. For example, a paragraph or a ‘div’. It is only allowed inside the root of the document and inside a blockquote element.
Example: switch the root container to Heading 1.
wym.mainContainer('H1');
registerModification()
¶
Registers a change in the document. This should be called after changes are made in the document.
Triggers the postModification
event afterwards.
switchTo(node, sType, stripAttrs)
¶
Switch the type of the given node
to type sType
.
If stripAttrs
is true, the attributes of node will not be included in the new
type. If stripAttrs
is false (or undefined), the attributes of node will be
preserved through the switch.
toggleClass(sClass, jqexpr)
¶
Set or remove the class sClass
on the selected container/parent
matching the jQuery expression jqexpr
.
Example: set the class my-class
on the selected paragraph with the
class my-other-class
.
wym.toggleClass('.my-class', 'P.my-other-class')
isBlockNode(node)
¶
Returns true if the provided node is a block type element.
isForbiddenRootContainer(tagName)
¶
Returns true if provided tagName
is disallowed as a root container.
Returns false if it is allowed.
isInlineNode(node)
¶
Returns true if the provided node
is an inline type node. Otherwise
returns false.
keyCanCreateBlockElement(keyCode)
¶
Determines whether the key represented by the passed keyCode
can create a
block element within the editor when pressed. Returns true if the key can
create a block element when pressed, and returns false if otherwise.
prepareDocForEditing()
¶
Makes some editor-only modifications to the body of the document, which are necessary
for the user interface. For example, inserts br
elements in certain places.
These modifications will not show up in the HTML output.
findUp(node, filter)
¶
Return the closest parent or self container, based on its type.
filter
is a string or an array of strings on which to filter the container.
unwrapIfMeaninglessSpan(node)
¶
If the given node is a span with no useful attributes, unwrap it.
For certain editing actions (mostly list indent/outdent), it’s necessary to wrap content in a span element to retain grouping because it’s not obvious that the content will stay together without grouping. This method detects that specific situation and then unwraps the content if the span is in fact not necessary.
Undo/Redo¶
undoRedo.undo()
¶
Undoes the last change.
Triggers the postUndo
event afterwards.
example: wym.undoRedo.undo();
undoRedo.redo()
¶
Redoes the last undone change.
Triggers the postRedo
event afterwards.
example: wym.undoRedo.redo();
List manipulation¶
isListNode(node)
¶
Returns true if the provided node is a list element. Otherwise returns false.
indent()
¶
Indent the selected list items. Only list items that have a common list will be indented.
outdent()
¶
Outdent the selected list items.
insertList(listType)
¶
This either manipulates existing lists or creates a new one.
The action that will be performed depends on the contents of the selection and their context.
This can result in one of:
- Changing the type of lists.
- Removing items from list.
- Creating a list.
- Nothing.
If existing list items are selected this means either changing list type or de-listing. Changing list type occurs when selected list items all share a list of a different type than the requested. Removing items from lists occurs when selected list items are all of the same type as the requested.
If no list items are selected, then, if possible, a list will be created. If not possible, no change is made.
Returns true if a change was made, false otherwise.
changeListType(list, listType)
¶
Changes the type of a provided list
element to the desired listType
.
convertToList(blockElement, listType)
¶
Converts the provided blockElement
into a list of listType
. Returns the
list.
User Interface¶
status(sMessage)
¶
Update the HTML value of WYMeditor’ status bar.
Example:
wym.status("This is the status bar.");
toggleHtml()
¶
Show/hide the HTML source.
keyboard.combokeys
¶
For keyboard shortcuts, the Combokeys library is used.
For each instance of the editor, a Combokeys instance is instantiated, attached
to the document and assigned as wym.keyboard.combokeys
.
This Combokeys instance can be used to attach keyboard shortcuts.
Please refer to Combokeys‘ documentation.
focusOnDocument()
¶
Set the browser’s focus on the document.
This may be useful for returning focus to the document, for a smooth user experience, after some UI interaction.
For example, you may want to bind it as a handler for a dialog’s window
beforeunload
event. For example:
jQuery(window).bind('beforeunload', function () {
wym.focusOnDocument();
});
get$Buttons()
¶
Returns a jQuery object, containing all the UI buttons.
Example:
var $buttons = wym.get$Buttons();
Dialogs¶
dialog(dialogObject)
¶
Opens a dialog.
dialogObject
is a plain object.
The included dialogs are in the WYMeditor.DIALOGS
object, under the
property names:
CreateLink
- Link insertion and editing
InsertImage
- Image insertion and editing
InsertTable
- Table insertion
Paste
- Paste HTML. HTML will be cleaned up before insertion.
Preview
- Provides a preview of the document
Example:
wym.dialog(WYMeditor.DIALOGS.Link);
You can provide your own dialog object. It looks like this:
- String
title`
- Dialog window title.
- Function
shouldOpen
- Its return value determines whether the dialog should be opened or not.
Is called with the editor as
this
. - Function
getBodyHtml
- Used to provide the dialog’s body’s HTML. Is called with the editor as
this
. - Function
getBodyClass
- Optional. Returns a class that will be added to the body of the dialog window’s document.
- Function
getWindowFeatures
- Optional. Used to provide the dialog’s window features, for passing to
window.open
. Is called with the editor asthis
. - function
initialize
- Optional. Can be used to initialize the dialog (e.g. prepopulate input
fields). Is called with the editor as
this
and receives a single argument-the dialog window. - function
SubmitHandler
- Optional. Handles a submit button press in the dialog. Is called with
the editor instance as
this
. Receives a single argument-the dialog window.
Helpers¶
WYMeditor.console
¶
A wrapper for the various browser consoles. Use it instead of
window.console
, console
, etc.. Handles the situation where in some IEs
the console doesn’t always exist.
wym.uniqueStamp()
¶
Returns a globally unique string.
jQuery.fn.nextContentsUntil()
and jQuery.fn.prevContentsUntil()
¶
Acts like .nextUntil()
but includes text nodes and comments and only
works on the first element in the given jQuery collection.
jQuery.fn.nextAllContents()
and jQuery.fn.prevAllContents()
¶
Acts like .nextAll()
but includes text nodes and comments and only
works on the first element in the given jQuery collection.
jQuery.fn.parentsOrSelf()
¶
Returns the parents or the node itself, according to jQuery selector.
example:
var parentLis = $someNode.parentsOrSelf("li")
jQuery.fn.isPhantomNode()
and WYMeditor.isPhantomNode()
¶
Returns true if the node is a text node with whitespaces only. The jQuery extension checks the first node.
WYMeditor.isPhantomString()
¶
Returns true if the provided string consists only of whitespaces.
WYMeditor.arrayContains(array, thing)
¶
Returns true if array
contains thing
. Uses ===
for comparison of
provided thing
with contents of provided array
.
WYMeditor.replaceAllInStr(str, old, new)
¶
Returns a string based on str
, where all instances of old
were replaced
by new
.
WYMeditor.editor.get$CommonParent(one, two)
¶
Returns a jQuery of the common parent of two document elements.
Constants¶
Elements¶
BLOCKING_ELEMENT_SPACER_CLASS
- Class for marking
br
elements used to space apart blocking elements in the editor. BLOCKING_ELEMENTS
- The subset of the
ROOT_CONTAINERS
that prevent the user from using up/down/enter/backspace from moving above or below them. They effectively block the creation of new blocks. BLOCKS
- All blocks (as opposed to inline) tags.
EDITOR_ONLY_CLASS
- Class used to flag an element for removal by the xhtml parser so that the element is removed from the output and only shows up internally within the editor.
FORBIDDEN_ROOT_CONTAINERS
- Containers that we explicitly do not allow at the root of the document. These containers must be wrapped in a valid root container.
HEADING_ELEMENTS
h1
throughh6
.INLINE_ELEMENTS
- Inline elements.
LIST_TYPE_ELEMENTS
ol
andul
.ROOT_CONTAINERS
- Containers that we allow at the root of the document (as direct children of the body tag).
Key codes¶
The following are all under WYMeditor.KEY_CODE
. For example,
WYMeditor.KEY_CODE.ENTER
is 13
.
B
BACKSPACE
COMMAND
CTRL
CURSOR
DELETE
DOWN
END
ENTER
HOME
I
LEFT
R
RIGHT
TAB
UP
Node types¶
As in https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType.
WYMeditor.NODE_TYPE.ATTRIBUTE
WYMeditor.NODE_TYPE.ELEMENT
WYMeditor.NODE_TYPE.TEXT
Internationalization¶
replaceStrings(sVal)
¶
Localize the strings included in sVal
.
encloseString(sVal)
¶
Enclose a string in string delimiters.
Utilities¶
box
¶
The WYMeditor container.
jQuery.wymeditors(i)
¶
Returns the WYMeditor instance with index i (zero-based).
Example:
jQuery.wymeditors(0).toggleHtml();
jQuery.copyPropsFromObjectToObject(origin, target, props)
¶
General helper function that copies specified list of properties from a specified origin object to a specified target object.
Example:
var foo = {A: 'a', B: 'b', C: 'c'},
bar = {Y: 'y'};
jQuery.copyPropsFromObjectToObject(foo, bar, ['A', 'B']);
bar
will then be {A: 'a', B: 'b', Y: 'y'}
.
isInlineNode(node)
¶
Returns true if the provided node is an inline type node. False, otherwise.
WYMeditor.isInternetExplorer*()
¶
WYMeditor.isInternetExplorerPre11()
and
WYMeditor.isInternetExplorer11OrNewer()
.
Internet Explorer’s engine, Trident, had changed considerably in version 7, which is the version that IE11 has, and now behaves very similarly to Mozilla.
These two functions help detect whether the running browser is IE before 11 or IE11-or-newer, by returning a boolean.
Custom Events¶
WYMeditor allows plugin authors to hook in to various editor internals through a system of custom events. By registering a handler on a specific event, your plugin will be notified when something occurs so that you can modify the default behavior.
All events are triggered against the textarea
element
to which the wymeditor instance is bound.
On an editor object wym
,
this element is available as wym._element
.
Available Events¶
All events are present
as a member of WYMeditor.EVENTS
.
postBlockMaybeCreated
¶
This event is fired when some user input occurred that might have created a new block-level element. Things that qualify include pressing the Enter key, pasting content, inserting tables, etc.
This event fires after WYMeditor’s default handling occurs.
postIframeInitialization
¶
This event is fired directly after
the appropriate browser-specific editor subclass’s
initIframe
method finishes.
Hook in to this event if you need the iframe to exist,
which usually occurs after the editor’s postInit
fires.
Plugin-Writing Mini Guide¶
1. Create an example in examples/
¶
Make sure and provide a description on the example page and ideally, initialize the editor with content that makes playing with your plugin easy.
Note
Please avoid adding a file in test
other than for unit tests.
The .html
files in test
rot quickly
(other than unit tests)
because users don’t see them.
Serving Examples¶
You can load your example via:
$ grunt server
$ google-chrome http://localhost:9000/examples/
Serving Examples from dist/
¶
To make sure your examples
also works from the built distribution,
we can tell grunt
to build first:
$ grunt server:dist
$ google-chrome http://localhost:9000/examples/
2. Create your plugin folder¶
- Your folder will live at
src/wymeditor/plugins/<pluginName>
. - Your main javascript file should be
jquery.wymeditor.<pluginName>.js
. - Any images or CSS should live in that folder.
3. Build your awesome thing¶
Now get cracking!
See the Example Contribution Process guide for general contribution advice.
If you get stuck,
join us in the #wymeditor
IRC channel
on freenode.
4. Document your awesome plugin¶
Create a docs/wymeditor_plugins/included_plugins/<your_plugin>.rst
file
and tell your future users:
- What your plugin does.
- How to enable it (a link to the example is good).
- How to customize it, if you have customization options.
- Anything else they need to know about browser compatibility, etc.
Plugin Do’s and Don’ts¶
Don’t enable your plugin on file load¶
Your plugin should not activate itself
just by loading your plugin’s javascript.
To use your plugin,
the user must add some kind of initialization
in the wymeditor postInit
.
For example:
jQuery('.wymeditor').wymeditor({
postInit: function(wym) {
wym.yourPlugin = new YourPlugin({optionFoo: 'bar'}, wym);
}
});
Note
The embed
plugin currently violates this,
which is a bug.
Mark your dialog-opening buttons¶
If your plugin includes buttons that open dialog windows, mark the list item
with the class wym_opens_dialog
. This will prevent your dialog window from
opening in the background.
Handle focus¶
When UI interactions steal focus from the document, consider using
editor.focusOnDocument
.
For example, right before a dialog window closes.
WYMeditor Development¶
Contributing¶
We <3 Contributions¶
We love your contributions. Anything, whitespace cleanup, spelling corrections, translations, jslint cleanup, etc is very welcome.
The general idea is that you fork WYMeditor, make your changes in a branch, add appropriate unit tests, hack until you’re done, make sure the tests still pass, and then send a pull request. If you have questions on how to do any of this, please stop by #wymeditor on freenode IRC and ask. We’re happy to help!
Example Contribution Process¶
- Fork wymeditor to your personal GitHub account.
- Clone it (
git clone <your personal repo url>
) and add the official repo as a remote so that you easily can keep up new changes (git remote add upstream https://github.com/wymeditor/wymeditor.git
). - Create a new branch and check it out (
git checkout -b my-cool-new-feature
). - Make your changes, making sure to follow the
Coding Standard.
If possible,
also include a unit test in
src/test/unit/test.js
. - Add the changed files to your staging area
(
$ git add <modified files>
) and commit your changes with a meaningful message ($ git commit -m "Describe your changes"
). - Repeat steps 4-5 until you’re done.
- Add yourself to the AUTHORS file!
- Make sure unit tests pass in as many browsers as you can. If you don’t have access to some of the supported browsers, be sure and note that in your pull request message so we can test them.
- Make sure your code is up to date (see below) and if everything is fine push
your changes to GitHub (
git push origin <your branch>
) and send a Pull Request.
Staying up to Date¶
If your fork or local branch falls behind the official upstream repository
please do a git fetch
and then merge
or rebase
to make sure your
changes will apply cleanly – otherwise your pull request will not be accepted.
See the GitHub help section for further details.
Configuring Your Development Environment¶
WYMeditor uses the standard modern javascript development toolchain:
git
and whatever tools you need to build from source. eg.sudo apt-get install build-essential
- Node.JS and NPM.
- grunt-cli
Then you just need to
$ npm install
Example Installation¶
We think that nvm is a really cool way to manage multiple node.js versions (or even just one), so we’ll use that for our example install. If you already know your way around node, feel free to use whatever you’d like.
$ curl https://raw.githubusercontent.com/creationix/nvm/v0.23.3/install.sh | bash
$ source ~/.profile
$ nvm install 0.10
$ npm install
$ npm install -g grunt-cli
$ grunt build
$ grunt test
If grunt build
succeeds,
you’re in good shape.
If grunt test
fails,
it’s probably because of a busted PhantomJS install.
Refer to the troubleshoot-phantoms section
for tips.
Troubleshooting¶
You probably need to install the libraries required for building from source on your OS. In Debian/Ubuntu, that means:
.. code-block:: shell-session
$ sudo apt-get install build-essential libfontconfig1 fontconfig libfontconfig1-dev libfreetype6-dev $ npm install
If you’re not using Ubuntu, you should google around for a tutorial or check the PhantomJS Build Page.
Front-end dependencies with Bower¶
Our front-end dependencies are pulled in by Bower.
Grunt orchestrates this automatically so you don’t have to think about it.
If you changed bower.json
and want those changes to take affect, just
restart the server or run grunt bower
.
Enabling Automatic Livereload for Development¶
The grant
, server
, and server:dist
tasks
both support “Live Reload” functionality.
That means that if you have a proper browser extension installed,
changing a file will automatically trigger a reload event
in your browser.
If this sounds nifty, simply install the proper extension.
Contributing Documentation¶
Documentation is published at http://wymeditor.readthedocs.org/
.
WYMeditor’s documentation is written for the Sphinx documentation generator. In order to build the documentation, it must be installed.
Building and Opening the Documentation¶
Building the documentation is as easy as:
$ grunt docsMake
Opening the documentation in the default browser:
$ grunt docsOpen
Building and opening, in succession, in one command:
$ grunt docs
Documentation Source Files¶
The source files of the documentation are at docs
.
The only exception is
the readme file, README.rst
,
which is at the root of the repository.
It is included in the build
and serves as about_wymeditor
.
The Sphinx configuration file, conf.py
,
and the Makefile
are there, as well.
Version Agnostic Documentation¶
Version agnostic documentation are pages which do not correspond with any specific release.
For example, API documentation are version specific. Upgrading to v1 is version agnostic.
Version agnostic documentation is most useful at its latest version. Therefore, such documentation pages should at least announce that fact.
Include the following at the top of version agnostic documentation pages:
.. include:: /attention_version_agnostic.txt
Building WYMeditor¶
We use grunt
to build WYMeditor.
Assuming you’ve already followed the instructions for
Configuring Your Development Environment,
it’s pretty straight forward.
$ grunt
That command runs the tests before the build. For just a plain build:
$ grunt build
The resulting compressed distribution will appear in your dist
directory.
Testing WYMeditor¶
Unit tests vs. Selenium tests¶
For testing, two different systems are used; Selenium and QUnit.
The vast majority of code is covered by the QUnit unit test suite.
Some functionality can’t be tested by QUnit, and thus is tested using Selenium.
Since QUnit is faster than Selenium, it is the preferred test platform. In absence of compelling reason otherwise, new tests should be written in QUnit.
All tests should pass in all supported browsers.
If ever any tests fail, please file a bug.
All of the following assumes you’ve completed the process of Configuring Your Development Environment
QUnit test suite¶
Running in real browsers¶
Start a server using
$ grunt server
After the server is running, by default,
it serves on port 9000
so point a browser at
http://localhost:9000/test/unit/index.html.
Note
If testing in a virtual machine, for better performance, disabling virtual memory may help.
Running in a headless browser¶
In addition to running the QUnit test suite in real browsers, running it from the command line in a headless PhantomJS browser is also possible.
This is how the travis-ci
test suite runs.
$ grunt test
If tests fail, an error will be output.
Running with various versions of jQuery¶
The QUnit test suite can be run with various versions of jQuery.
Instructions for doing that are provided in the following sections.
The default version of jQuery that is used is the lowest supported version.
Testing with jQuery versions other than the default involves the automatic fetching of the desired jQuery version from Google’s CDN.
To do this when running tests in a browser,
append the URL parameter ?jquery=<version>
to the test suite URL.
For example,
for jQuery version 1.7.0
use
http://localhost:9000/test/unit/?jquery=1.7.0
.
To do this when running tests
from the command line,
include the parameter
--jquery=<version>
when running the test
task.
For example,
for jQuery 1.6.0
use
$ grunt test --jquery=1.6.0
Selenium tests¶
QUnit operates within the confines of JavaScript.
Some functionality can’t be tested with JavaScript, exclusively.
This includes browsers’ reaction to entered input like keyboard, mouse and
touch.
It also includes focus–related activity like window switching.
It also includes the execCommand
funcionality.
For testing these areas, we rely on Selenium. It controls real browsers in ways that JavaScript can’t.
Since QUnit is faster than Selenium, it is the preferred test platform. In absence of compelling reason otherwise, new tests should be written in QUnit.
WYMeditor’s Selenium test suite is written in Python, using Python bindings for Selenium.
Running Selenium tests¶
Install the Selenium 2 Python bindings, roughly following these instructions.
The specific version of the python Selenium bindings and the nose testing framework we require are defined in a pip requirements file located at wym_selenium/requirements.txt. To install these, we recommend that you first create an isolated python virtualenv.
$ mkdir -p ~/.virtualenvs $ virtualenv ~/.virtualenvs/wym
Then use pip to install the requirements:
(wym)$ cd /path/to/wymeditor (wym)$ pip install -r selenium_requirements.txt
To run the Selenium tests, you’ll first need to serve the
src
directory with a web server. If you have Python installed, then you can simply open a terminal and run:$ cd /path/to/wymeditor $ make testserver
You’ll need to keep this terminal open when running the tests.
Then you can use make once again (in another terminal) to actually run the tests:
$ source ~/.virtualenvs/wym/bin/activate (wym)$ cd /path/to/wymeditor (wym)$ make selenium
Coding Standard¶
The goal of this document is to define a set of general rules regarding the formatting and structure of the WYMeditor code as well as defining some best practices. It should also serve as a good starting point for developers that want to contribute code to the WYMeditor project.
jshint¶
All Javascript source should pass the version of
jshint
that’s defined via grunt-contrib-jshint
in our packages.json
with the options defined in our .jshintrc
.
Ideally, you are running this in your text editor on every file save. At the very least, you can perform this check via Grunt:
$ grunt jshint
Making Files Pass¶
jshint
against master should always pass,
all the time.
That means that before sending a pull request,
a feature branch should pass.
Generally,
there are a few techniques to make this happen.
Most of the time, the errors are useful and changing the code to pass results in cleaner, easier to read, more-consistent code. If you’re confused about an error, a quick google usually results in a Stack Overflow question with a good solution.
global
and exported
¶The combination of the unused and undef options is very useful for eliminating global leakage and dead code. It does have a cost in that we must tell jshint about which files rely on other files and which files are used as libraries by other files.
We do this using Inline configuration.
Do not use single-line exceptions to tell jshint to ignore global/exported problems.
global
¶If you get a W117
warning
about a variable not being defined
because it comes from another file,
you need to define a global.
For example,
the ok
method comes from QUnit
so test files need the following at the top:
/* global ok */
exported
¶For variables and functions defined in one file
but used in another,
you’ll get a W098
warning
about the variable being defined but never used.
You should fix this with an exported
Inline configuration
at the top of the file.
For example,
if the file defines a usefulUtiityFunction
,
you would add the following
at the top of the file:
/* exported usefulUtilityFunction */
This should be used as little as possible. There’s almost always a way to fix the code to match. When WYMeditor started enforcing a passing jshint, we added exceptions liberally just to get to a stable starting point. New code should almost never come with new exceptions.
This JSHint options documentation explains how to suppress a warning and then re-enable the warning. Always re-enable the warning after you disable it, even if there’s currently no code after the block. Otherwise, new code added might accidentally not be subject to the check.
For example,
if you’re doing a for in
loop that you know to be safe,
you could do:
var y = Object.create(null);
/* jshint -W089 */
for (var prop in y) {
// ...
}
/* jshint +W089 */
Any file-wide jshint exception is considered a bug, but in the interests of getting a passing jshint, some were used initially. Do not add these, despite the fact that some files currently use them. Pull requests that remove a file-wide exception and fix the resulting lint problems are greatly appreciated.
Current vs Ideal¶
Some of our current jshint options are a result of legacy code and not an indication of where we’d like to be. In the spirit of getting a 100% passing jshint run, we made some initial sacrifices. The goal is to get all of the code on the same standard.
Additionally,
there are several files listed in .jshintignore
because they don’t yet conform to our standards.
We would really all of our code to conform,
which means removing the ignores
for everything but the 3rd-party code.
Crockford Javascript Code Conventions¶
Please refer to the Crockford Javascript Code Conventions for our default code conventions. The Formatting and Style section describes some areas where we are more-strict.
Changes to Crockford Conventions¶
We also have some choices that contradict Crockford’s conventions.
- We use one
var
statement per scope, versus anothervar
for each variable. - We still use
eval()
in a couple of places. It is evil, though, and it’s considered an implementation bug.
Formatting and Style¶
Naming Conventions¶
- Give variables and function meaningful names. Naming is very important!
- Use mixedCase (lower CamelCase) for names spanning several words.
- Constants should be in all CAPITAL_LETTERS with underscores to separate words.
- Avoid the use of Hungarian Notation, instead make sure to type your variables by assigning default values and/or using comments.
- Use one
var
statement per scope, declaring all of your variables there on separate lines.
Example:
var elements = [],
somethingElse = '',
VERSION = 0.6;
function parseHtml () {};
Constructors should be named using PascalCase (upper CamelCase) for easier differentiation.
Example:
function MyObject () {}
MyObject.prototype = {
function myMethod () {}
}
Namespacing¶
All code should be placed under the WYMeditor namespace to avoid creating any unnecessary global variables. If you’re extending and/or modifying WYM, place you code where you see fit (most likely WYMeditor.plugins).
WYMeditor.core contains the Editor object and the SAPI as well as HTML, CSS and DOM parsers which make out the core parts of WYMeditor.
WYMeditor.ui contains the UI parts of WYM (i.e. the default Toolbar and Dialogue objects).
WYMeditor.util contains any utility methods or objects, see Leave the Natives Alone.
WYMeditor.plugins – place your plug-ins here.
Multi-Line Strings¶
Choosing among syntaxes for multi-line strings is rough, because they mostly all suck. We’ve settled on this as the least-bad:
var bigString = [""
, wym._options.containersSelector
, wym._options.classesSelector
].join('');
Advantages:
- Passes
jshint
- Leading commas allows re-ordering without comma juggling
- A one-line addition is a one-line diff
- Can use other join characters like
, `` or ``\n
for flexibility - Can indent lines in source to avoid >79 character lines
- Can indent lines in source to display HTML nesting for readability
Building HTML strings also kind of sucks.
Eventually,
we hope to using something like JSX.
For now,
just build a multi-line string with proper HTML indentation
and using '
as the quote character
(so that it’s easy to use proper "
to quote HTML attributes).
var iframeHtml = [""
, '<div class="wym_iframe wym_section">'
, '<iframe src="' + WYMeditor.IFRAME_BASE_PATH + 'wymiframe.html" '
, 'frameborder="0" '
, 'scrolling="no" '
, 'onload="this.contentWindow.parent.WYMeditor.INSTANCES['
, WYMeditor.INDEX + '].initIframe(this)"'
, '>'
, '</iframe>'
, '</div>'
].join(""),
Inheritance and “Classes”¶
There’s a lot of different ways of doing inheritance in JavaScript. There have been attempts to emulate Classes and several patterns trying enhance, hide or modify the prototypal nature of JavaScript – some more successful than others. But in order to keep things familiar for as many JavaScript developers as possible we’re sticking with the “Pseudo Classical” model (constructors and prototypes).
It’s not that the different variations of the “Pseudo Classical” model out there are all bad, but there is no other “standard” way of doing inheritance.
Other Rules and Best Practices¶
WYMeditor is used by a lot of people in a lot of different environments thus modifying the prototypes for native objects (such as Array or String) can result in unwanted and complicated conflicts.
The solution is simple – simply leave them alone. Place any kind of general helper methods under WYMeditor.util.
This is a basic one – but there’s still a lot of developers that use the Array and Object constructors.
http://yuiblog.com/blog/2006/11/13/javascript-we-hardly-new-ya/
which
Property of jQuery Event Objects¶When watching for keyboard key input, use the event.which
property to find
the inputted key instead of event.keyCode
or event.charCode
. This
should be done for consistency across the project because the event.which
property normalizes event.keyCode
and event.charCode
in jQuery. Using
event.which
is also the recommended method by jQuery for watching keyboard key input.
Wherever possible, comments should read like a sentence. Sentences evolved because they’re good at conveying information. Fragments are often ambiguous to those who need the comment most. They should also mostly answer the question “why?” instead of what/how.
When tempted to write a comment that describes what a block of code does, instead, write a function with a good name. The exception is one-liners that are conceptually dense, although those are usually the sign of a need for a refactor or utility function.
function MyPlugin(options, wym) {
var defaults = {
'optionFoo1': 'bar'
};
this._options = jQuery.extend(defaults, options);
this._wym = wym;
this.init();
}
MyPlugin.prototype.init = function() {
var wym = this._wym,
buttonFoo1,
buttonFoo2,
buttonsHtml,
box = jQuery(wym._box);
//construct the buttons' html
buttonFoo1 = [""
, "<li class='wym_tools_foo1'>"
, "<a name='foo1' title='Foo 1' href='#'"
, "{foo1}"
, "</a>"
, "</li>"
].join('');
buttonFoo2 = [""
, "<li class='wym_tools_foo2'>"
, "<a name='foo2' title='Foo 2' href='#'"
, "{foo2}"
, "</a>"
, "</li>"
].join('');
buttonsHtml = buttonFoo1 + buttonFoo2;
//add the button to the tools box
box.find(wym._options.toolsSelector + wym._options.toolsListSelector)
.append(buttonsHtml);
//bind listeners
box.find('li.wym_tools_foo1 a').click(function () {
// Do foo1 things
});
box.find('li.wym_tools_foo2 a').click(function () {
// Do foo2 things
});
};
function MyPlugin(options, wym) {
var defaults = {
'optionFoo1': 'bar'
};
this._options = jQuery.extend(defaults, options);
this._wym = wym;
this.init();
}
MyPlugin.prototype.init = function() {
var wym = this._wym,
buttonsHtml,
box = jQuery(wym._box);
buttonsHtml = this._buildButtonsHtml();
// Add the button to the tools box.
// TODO: There should probably be a WYMeditor utility function for
// doing this.
box.find(wym._options.toolsSelector + wym._options.toolsListSelector)
.append(buttonsHtml);
this._bindEventListeners(box);
};
MyPlugin.prototype._buildButtonsHtml = function () {
var buttonFoo1 = '',
buttonFoo2 = '';
buttonFoo1 = [""
, "<li class='wym_tools_foo1'>"
, "<a name='foo1' title='Foo 1' href='#'"
, "{foo1}"
, "</a>"
, "</li>"
].join('');
buttonFoo2 = [""
, "<li class='wym_tools_foo2'>"
, "<a name='foo2' title='Foo 2' href='#'"
, "{foo2}"
, "</a>"
, "</li>"
].join('');
return buttonFoo1 + buttonFoo2;
};
MyPlugin.prototype._bindEventListeners = function (box) {
var myPlugin = this;
box.find('li.wym_tools_foo1 a').click(function () {
myPlugin._doFoo1Things();
});
box.find('li.wym_tools_foo2 a').click(function () {
myPlugin._doFoo2Things();
});
};
MyPlugin.prototype._doFoo1Things = function () {
// Do foo1 things
};
MyPlugin.prototype._doFoo2Things = function () {
// Do foo2 things
};
Got any other links that you think can be of help for new WYM developers? Share them here!
WYMeditor Architecture¶
At a high level, WYMeditor is a jQuery plugin that replaces a textarea with a
designMode iframe surrounded by an editor skin and editor controls/buttons.
When you call $('.myEditorClass').wymeditor()
, several things happen:
- WYMeditor uses your configuration/settings to build out HTML templates for all of the various components.
- Your textarea is hidden and WYMeditor uses the generated HTML to insert the editor buttons and interface in the same spot.
- An iframe with
designMode=true
is created and inserted as the area you actually type in. - WYMeditor detects your browser and uses a form of prototype inheritance to instantiate the browser-specific version of the editor. The editor is the collection of event listeners, browser-specific DOM manipulations and interface hacks needed to provide the same behavior across IE, Firefox, Chrome, Safari and Opera.
Code Structure¶
WYMeditor’s source is organized in to several modules corresponding to large
pieces of functionality and all of the actual code lives in src/wymeditor/
.
The build/
directory contains build tools and is the default location where
bundled/packed/minified/archived versions of the project can be built (to
build/build/
) using the Makefile
.
Inside src/
you also have:
jquery/
– Includes the bundled version of jQuery and jQuery UI.test/
– Includes both manual and unit tests. The manual tests in the .html files have various editor configurations and combinations of plugins and options.unit/
– Here lives the automated test suite which makes it possible to make a change without breaking 90 other things. The unit test suite is based on QUnit.
core.js¶
This is the core of the project including the definition of the WYMeditor namespace, project constants, some generic helpers and the definition of the jQuery plugin function.
The most important object in core is WYMeditor.editor()
which is the
object/function that jquery.fn.wymeditor()
farms out to for all of the work
of actually building the editor. WYMeditor.editor()
does the basic job of
building the options
object (which for most cases is 99% the defaults),
locating your JavaScript files and calling init()
(which is defined in
editor/base.js
for the heavy lifting.)
parser.js¶
Here be dragons. parser.js
is fundamentally responsible for taking in
whatever HTML and CSS the browser or user throws at it, parsing it, and then
spitting out 100% compliant, semantic xHTML and CSS. This also includes some
work to correct invalid HTML and guess what the user/browser probably actually
meant (unclosed li tags, improperly nested lists, etc.) It uses several
components to do its job.
- A lexer powered by parallel regular expression object
- A base parser and an xHTML-specific parser
- An XhtmlValidator to drop nonsense tags and attributes
- A SAX-style listener to clean up the xHTML as it’s parsed (this does most of the magic for guessing how to fix broken HTML)
- A CSS-specific lexer and parser. This is mostly just used to take in CSS configuration options.
editor/¶
This folder is where most of the magic happens. It includes base.js
for doing
most of the heavy lifting and anything that can be done in a cross-browser
manner. It takes the options you passed to the jQuery plugin (defined in
core.js
) and actually creates all of the UI and event listeners that drive
the editor.
In the init()
it also performs browser detection and loads the appropriate browser-specific editor extensions that handle all of the fun browser-specific quirks. The extensions are also located in this folder.
iframe/¶
This folder contains the HTML and CSS that’s used to create the actual editor
iframe (which is created by the init()
method in the WYMeditor.editor
object defined primarily in editor/base.js
.) By switching the iframe source
configuration, you choose which iframe to use (default is of course,
default
.)
Of special interest is the wymiframe.css
file inside your chosen iframe
(eg. src/wymeditor/iframe/default/wymiframe.css. ) This file defines the
signature blue background with white boxes around block-level elements and with
the little “P, H2, CODE” images in the upper left.
skins/¶
The skins/
folder is structured like the iframe/
folder, with folders
corresponding to different available skins and a default of default
. An
editor skin allows customization of the editor controls and UI, separate from
the editable area that where a user actually types. The skin generally contains
CSS, JS and icons and hooks in to the bare HTML that’s produced by
WYMeditor.editor
using defined class names that correspond to different
controls. The best way to understand and create/edit a skin is to look at the
HTML constants defined in WYMeditor
(inside core.js
) and compare them
to the CSS/JS defined in an existing skin (e.g.
src/wymeditor/skins/default
).
lang/¶
This is where translations to other languages live. Each file defines a
specific WYMeditor.STRINGS.<language code>
object with mappings from the
English constant to the translated word.
plugins/¶
This is where all plugins bundled with WYMeditor live. There’s currently quite
a lot of variance between the ways plugins are created an organized, but
they’re at least organized with a top-level folder in the plugins directory
with the plugin’s name. In general, the name of the main plugin file has the
format jquery.wymeditor.<plugin_name>.js
.
A set of plugin system hooks is on the roadmap, but for now most plugins modify things in different ways and are relying on APIs that are not guaranteed. A future release will provide those guaranteed APIs. See Plugin System Architecture.
Expected Cross-browser behavior of the Cursor in Reaction to Keystrokes¶
For usability, it’s very important that the common navigation keys (up/down/enter/backspace) should behave as a the user expects. This means that whenever possible, any cross-browser behavioral differences should be corrected.
General Guidelines¶
- The Enter and Backspace keys should be compliments with regards to navigation. Doing one and then the other should return to the same state.
- The Up and Down keys should be compliments.
- The Up and Down keys should not create new blocks. Only move the cursor between them.
- When navigating to “blue space” (areas without blocks) via the nav keys or the mouse, a paragraph block should be created on first content keystroke.
- Backspace at the start of a block joins the contents of the current block with the contents of the previous.
- Enter when in a block creates a new block after the current, with the content after the cursor in the new block.
- Tables, Images, Blockquotes and Pre areas are “special”.
Planning For Future Enhancements¶
Roadmap for WYMeditor 2.0 (Draft)¶
Version 2.0 of WYMeditor will be a complete rewrite fixing a lot of the issues with the current stable version.
Version 2.0 can be grouped in to four major components: the Selection API or SAPI, the Editor core, the HTML, DOM and CSS parsers and the UI. These four components also make up the four steps we’ll need to complete before we release version 2.0. Of course these steps overlap somewhat, and it’s possible to work on several of these things in parallel.
General Goals¶
- Separating the different areas of WYMeditor from each other, making it more modular
- Implementing a solid event system
- Documenting the WYMeditor source more thoroughly
- A more consistent source code (through the Coding Standard)
The Roadmap¶
In the current stable version there’s a HTML parser and a CSS parser. In 2.0, we’ll also be introducing a DOM parser, as we can not rely on the innerHtml property due to a couple of issues and lack of flexibility.
Goals
- Document the HTML and CSS parsers
- Get rid of the innerHtml dependency
New Features
- Inline controls
- Place holders for forms/flash/dynamic content
Issues to Fix
- innerHtml: Problems with links and urls (#69)
- innerHtml: Insertion of script elements doesn’t work
Resources
The SAPI in the current stable version is rather incomplete. To make things easier, the excellent IERange library will be used for Internet Explorer compatibility.
The SAPI could also be released as a standalone jQuery plug-in, as it could be useful to a lot of other people and projects.
Goals
- Define a general jQuery plugin for selections and ranges, and integrate the plugin with WYMeditor
- Creating a consistent API that can be used for manipulating content
New Features
- Save and restore selections, allowing support for modal dialogues and the like
- A more solid API
Resources
In 2.0 we’ll be moving away from designMode in favour of contentEditable. This will not only reduce the complexity a lot, it will also open up a lot of new interesting possibilities. Through easier interaction with external scripts things such as drag an drop, placeholders and the like are a lot easier to achieve.
Goals
- Move away from designMode in favour of contentEditable
- Solid event handling
- New features
- onChange/isDirty functionality (#138)
Issues to Fix
- Support for different block level elements (#152, #178)
The goal is to create a new clean looking, extendable and skinnable UI that’s completely separated the WYMeditor core. This would allow the developer to completely replace the default UI with another one, opening up a lot of different integration possibilities.
Goals
- Create a new clean looking and consistent UI
- Separate the UI from the editor core
- Make it extendable and skinnable
- Make it more user friendly through on-screen help and well thought out layout, features and (visual) feedback
New features
- One toolbar for several editor instances (#97)
- Modal dialogues (#63)
Issues to Fix
- Link editing (#69)
- Block level elements inside list items (#135)
Proposed Build Stack¶
100% Javascript¶
For WYMeditor 1.0, we’re moving to an all-Javascript build stack from the
Node.js community. The rationale behind this is simple – use tools that make
sense to JS developers. Any person with node.js
and npm
installed
should be able to run a few npm
commands and then do development or run a
build.
The goal will be to combine asset-management best practices like concatenation and minification for CDNs with automatic file generation for easy development. Editors like TinyMCE are often criticized for poor HTTP performance and there are posts strewn across the interwebs from people attempting to hack together solutions to this problem. We should solve it for everyone by default and even make it easy for folks to make custom builds with just the plugins that they require.
Components¶
There are always miscellaneous tasks and helpers that developers need, from building documentation to bumping version numbers and tagging a new release. It’s always nice for new users if there is a consistent way of performing these actions.
A port of Rake. We should be able to lint, run tests, build documentation, create a full build or create customized builds all from jake.
We ultimately want the ability to produce a trio of one minified javascript file, one minified CSS file and one image file with all requirements. This will include the editor, its skin and any plugins the developer wants to enable (including 3rd-party plugins) along with all of their assets.
Either node-ams or local-cdn. Both provide a file-watching development server via node.js to allow easy development. Both let you statically define and then generate files on demand for releases.
What we really need is Sphinx ported to Javascript, but until something like that emerges in the node community, the standard solution is inline documentation plus stand-alone statically-generated HTML documentation.
Allows writing inline docs in pure Markdown. Not as restricting as most other solutions since it isn’t modeled after programming paradigms foreign to JavaScript. Also good for consistency across different platforms (GitHub wiki, issues and comments, the new forum, etc.)
Inline documentation doesn’t cover tutorials, API documentation and reference docs. The Django documentation is a good example of what we’re going for. For now, it seems most projects are rolling their own combination of statically-generated HTML sites powered by MarkDown. To start, rip off the documentation from a project like express and get started.
- QUnit for the test framework.
- phantomJS and qunit-tap to run the tests from the command line for quick webkit tests.
- Jenkins for CI to automatically package builds, run static analysis, run phantomJS tests and then use Sauce On Demand to spin up supported browsers for cross-browser testing.
- TestSwarm for coordinating cross-browser tests in all supported browsers.
- node-jshint for static analysis and linting.
Plugin System Architecture¶
A proper plugin system should provide one obvious way to perform all of the common things that plugins do. It should still be possible, however, to use plain Javascript to do uncommon things.
Goals¶
Plugins should read such that someone who understands Javascript should be able to follow along without knowing all of the details of the plugin API. This means that a little more “boilerplate” is better than magic and explicit is better than implicit. An example of explicit:
function TableEditor(name, wymeditor, options) { this.name = name; this.wymeditor = wymeditor; this.options = options; } TableEditor.prototype.init = function() { this.wymeditor.buttons.addButton({'name': 'AddRow', 'title': 'Add Row', 'cssClass': 'wym_tools_addrow'}); } TableEditor.prototype.bindEvents = function() { var tableEditor = this; var wym = this.wymeditor; wym.buttons.getButton('AddRow').find().click(function(evt) { return tableEditor.handleAddRowClick(evt); }); }
An implicit way to do things would be to set up some magically-named class attribute that is automatically used by WYMeditor at some point during initialization to create these things.
Very little method replacing and attribute mangling of the wymeditor object itself should be necessary.
Most/all of a plugins UI actions (creating dialogs, adding buttons etc) should be done through API calls, allowing editor-wide standardization and theming.
Plugins should have strong hooks into actions so that they’re able to clean up the DOM to fix cross-browser problems.
All currently-core WYMeditor actions should be migrated to being stand-alone plugins.
The plugin API shouldn’t be responsible for automatically locating Javascript files. The only way to efficiently handle that is on the server side and in the HTML file itself.
Enabling a Plugin¶
All well-behaving plugins should be explicitly enabled through the plugins
configuration option on editor initialization. Plugins that alter WYMeditor
behavior without being explicitly enabled (like the 0.5.x embed plugin) are
considered misbehaving.
The WYMeditor
plugins
configuration option is where plugin
configuration occurs. This object is an array of objects with the plugin’s
name
, func
, and options
. For example:
jQuery('.wymeditor').wymeditor({
plugins: [
{
name: 'table',
func: TableEditor,
options: {enableCellTabbing: false}
}
]
});
API¶
WYMeditor.plugins.addPlugin(<pluginName>, <pluginFunction>, <configurationObject>);
WYMeditor.plugins.getPlugin(<pluginName>);
WYMeditor.buttons.addButton(<options>);
WYMeditor.buttons.getButton(<buttonName>);
WYMeditor.buttons.removeButton(<buttonName>);
WYMeditor.dialogs.createDialog(<dialogName>, <options>, <callback>);
WYMeditor.dialogs.destroyDialog(<dialogName>, <options>, <callback>);
WYMeditor.addXhtmlCleanup(<cleanupName>, <cleanupFunction>);
WYMeditor.removeXhtmlCleanup(<cleanupName>);
WYMeditor.addDomCleanup(<cleanupName>, <cleanupFunction>);
WYMeditor.removeDomCleanup(<cleanupName>);
Note
The following methods already exist:
WYMeditor.editor.findUp(); WYMeditor.editor.container(); WYMeditor.editor.update(); WYMeditor.editor.html(); WYMeditor.editor.switchTo(); WYMeditor.editor.wrap(); WYMeditor.editor.unwrap(); WYMeditor.editor.setFocusToNode(); WYMeditor.editor.exec(<commandName>);
Selection API¶
The Selection API will be used to ease the interaction with selected text and
the current cursor position. If you want to handle an event yourself (and stop
the default one), it’s important to know where the cursor currently is. But
often you only need to know the position relative to a container element and
the container itself. There some problems that arise, such as nested tags. In
this document, |
will be used to indicate the current cursor position:
<p>Some <em>nice|</em> text</p>
Of course you don’t want <em>
returned as container element, but the
<p>
.
Here’s my proposal of some basic functionality. That was all I needed for my prototype. The following code is Javascript pseudocode:
selection = {
// properties
// the original DOM Selection object
//(http://developer.mozilla.org/en/docs/DOM:Selection)
original:
// the node the selection starts. "start" is the most left position of the
// selection, not where the user started to select (the user couls also
// select from right to left)
startNode:
// the node the selection ends
endNode:
// the offset the cursor/beginning of the selection has to it's parent nodenode
startOffset:
// the offset the cursor/end of the selection has to it's parent nodenode
endOffset:
// whether the selection is collapsed ot not
isCollapsed:
// That one don't need to be implemented at the moment. It's diffcult as
// one need to define what length is in this context. Is it the number of
// selected characters? Or the number of nodes? I'd say it's the number of
// selected characters of the normalized (no whitespaces at end or beginning,
// multiple inner whitepsaces collapsed to a single one) selected text.
length:
// methods
// Returns true if selection starts at the beginning of a (nested) tag
// @param String jqexpr The container the check should be performed in
// @example element = <p><b>|here</b></p>, element.isAtStart("p") would
// return true
isAtStart: function(jqexpr)
// Returns true if selection ends at the end of a (nested) tag
// @param String jqexpr The container the check should be performed in
// @example element = <p><b>here|</b></p>, element.isAtEnd("p") would
// return true
isAtEnd: function(jqexpr)
// set the cursor to the first character (it can be nested),
// @param String jqexpr The cursor will be set to the first character
// of this element
// @example elements = <p><b>test</b></p>, element.cursorToStart("p")
// will set the cursor in front of test: <p><b>|test</b></p>
cursorToStart: function(jqexpr)
// set the cursor to the last character (it can be nested),
// @param String jqexpr The cursor will be set to the last character
// of this element
// @example elements = <p><b>test</b></p>, element.cursorToEnd("p")
// will set the cursor behind test: <p><b>test|</b></p>
cursorToEnd: function(jqexpr)
// removes the current selection from document tree if the cursor isn't
// collapsed.
// NOTE Use the native function from DOM Selection API:
// http://developer.mozilla.org/en/docs/DOM:Selection:deleteFromDocument
// NOTE First I had also "deleteFromDocument()" in the Selection API, but
// it isn't needed as a "sel.deleteIfExpanded()" would do exactly the same
// as "sel.deleteFromDocument()" with the only difference that this one
// has a return value
// @returns true: if selection was expanded and therefore deleted
// false: if selection was already collapsed
// @example // do what delete key normally does
// if (!sel.isCollapsed)
// {
// sel.original.deleteFromDocument();
// return true;
// }
// will be now:
// if (sel.deleteIfExpanded())
// return true;
deleteIfExpanded: function()
};
Todo
- save selection (and current cursor position)
Plan for Insertion Functions¶
Current plan for a clean API around inserting inline and block elements and text.
Deprecate insert
and alias it to insertInline
.
insertInline
¶
Basically the same as the current insert
function with a few exceptions.
- Inserts the contents where the current selector is inside the current block element.
- If the selector is in the body, it creates a paragraph to wrap the contents inside, if needed.
- If
insertInline
is called with HTML representing a non-nestable block level element, an exception is raised instead of attempting to guess the proper behavior.
insertBlockAfter
¶
(This is @samuelcole’s insert_next, effectively)
Used to insert block-level elements after the currently-selected block-level element.
- If given a string representing a text node, wraps the text in the appropriate
container (
p
orli
tag, depending). - No matter the current selection, does not split the current block.
insertBlock
¶
Used to insert a block-level element and split the current block-level element on the selection boundary.
- If given a string representing a text node, wraps the text in the appropriate
container (
p
orli
tag, depending)
Inserting <p>inserted</p>
with |
representing current cursor.
Middle of Node
<p>before| after</p>
<p>before</p><p>inserted</p><p>| after</p>
Start of Node
<p>|before after</p>
<p>|before after</p><p>inserted</p>
End of Node
<p>before after|</p>
<p>before after|</p><p>inserted</p>
Dev Process Improvements¶
This page is where we brain-dump ideas for improving the development/testing/packaging/documentation/etc process because making Issues for speculation feels dirty.
Build Process¶
HTTP requests are silly.
Put all of the plugins in the bundled version, changing any of them so that just including the code doesn’t activate them.
Documentation¶
Current planned ideal:
index
getting_started/
- index
- setup (mention existing_integrations)
- philosophy (or should this be on the website?)
- getting_help (link to contributing)
- customizing_wymeditor/
- index (general overview of architecture and explanation of customization methods)
- configuration_options
- using_plugins (content from using_wymeditor/using_plugins and plugins/index)
- using_skins
- using_content_layouts (better name than iframe)
- howto/ (instead of customizing_wymeditor/examples)
- toolbar_items ?
- etc
- plugins/
- index
- core_plugins/
- bidi
- list
- table
- etc
- third_party_plugins
- skins/
- index
- core_skins/
- silver
- legacy
- minimal
- third_party_skins
- content_layouts
- index
- core_content_layouts/
- pretty
- legacy
- third_party_content_layouts
- resources/
- index
- wymeditor_end_user_guide
- writing_plugins/
- index
- writing_skins/
- index
- writing_content_layouts/
- index
- existing_integrations
- upgrade_guide
- changelog
- wymeditor_development/ (as it already exists)
Screenshots are worth a thousand words. Use grunt-autoshoot to take screenshots of the examples and update the docs to embed them.
Code Cleanup¶
Only things we don’t control
should be in .jshintignore
.
eval()
¶It’s not necessary.
We should include Rangy via bower.
Testing¶
There are some bugs that can only be tested with native events, so we wrote Selenium tests for them. This kind of sucks, though, since it’s a very separate concern.
Instead, use one of the Java Applet-based real user event simulators.
Testling-CI seems like the way to go for running our unit tests across our supported browsers. It won’t work for our Selenium tests, but it will at least make it easy to catch regressions and the like when lazy developers *cough*me*cough* don’t test in all of the IE’s.
Have travis load all of the examples and tests using phantomjs and verify that WYMeditor is at least finishing initialization.
Making WYMeditor Releases¶
WYMeditor’s release procedure aims to produce high–quality and easy–to–use releases.
In order to ensure its consistency, the release procedure is described here.
Prerequesites for a release¶
Subjects under this are prerequesites for making a release.
No Regression Bugs¶
We shouldn’t break things that used to work between releases. That undermines user confidence and makes it hard to build momentum of positive improvement since it becomes an equation with both positive and negatives which will be weighted differently for different people. That doesn’t mean we can’t make backwards-incompatible changes, but it does mean that anything that might be backwards incompatible needs to be very explicitly planned for.
This can generally be verified by running through the issue tracker and making sure no new bugs have been reported that didn’t exist in prior versions. If there has been, that issue needs to be addressed before a new release should be cut. Bugs that are a result of running WYM in a new browser (new Chrome or Firefox release, for example) don’t count as regression bugs for this purpose, but should be given high priority otherwise.
This report should be empty.
Passing Tests¶
All releases should have 100% passing unit tests in 100% of supported browsers for all of the supported versions of jQuery.
The person doing the release is responsible for running the unit tests in all supported browsers.
Documentation is up–to–date¶
It’s important that users can be confident that the documentation distributed with WYMeditor is up to date so that it can be trusted.
Please be aware that this prerequesite is not completely met, currently.
Change–log is up–to–date¶
Change–log entries for changes should be merged in with the changes themselves or immediately following them.
Since we’re probably still human, some change–log entries might not have been written.
We should ensure that noteworthy changes since the last version are included
in the CHANGELOG.md
file.
An overlook on the change–log may inspire rephrasing some of the entries. This is an opportunity for that.
The format of this file includes various sections for each release.
It should be easy to mimic this same format for the release being made.
The change log is intended for users of WYMeditor.
Any change that may affect them should be included.
Read–me is up–to–date¶
Similar to the change–log, the contents of the README.rst
are supposed to be
up–to–date already.
A good review of it, especially after reviewing the change–log, may inspire some updates.
Website is up–to–date¶
What is written regarding the read–me above is applicable regarding the website, as well.
Currently, the website’s content is generated from the README.rst
, mostly.
Making the release¶
After the above prerequesites are met, the following actions are in order.
Prepare¶
- Make sure your working directory is clean.
- Checkout
master
. - Pull from origin.
Update version strings¶
WYMeditor uses Semantic Versioning 2.0
.
The project’s version string is duplicated in several places, across four different files:
package.json
bower.json
docs/conf.py
in two linesCHANGELOG.md
(the latest entry)
According to Semantic Versioning, come up with a version for this release and update the version strings.
Build the web–site¶
Instructions for building the web–site are in the web–site documentation page.
Build WYMeditor¶
Note
Users should be able to make their own archive by following the instructions in the read–me. Nonetheless, including an archive in a known place is important, for ease of use.
-
This will make the checked–in build at
dist/
representitive of the source code. Check that examples work when served from
dist/
by usinggrunt server:dist
.
Commit the changes¶
- Set the date for the release.
- Commit all the changes you’ve made
,plus the changes in
dist/
to branchmaster
and push.
Ship it!¶
- Look joyously at the current releases.
- Publish a new release from the master branch with:
- The tag is the version string with
v
prefixed. - The title is the version string as is.
- The description is the change–log section for this release , excluding its title.
- The WYMeditor build,
wymeditor-<version>.tag.gz
, as an attached binary
- The tag is the version string with
- Activate the new version in Read the Docs and set it as the default version.
- Publish the website.
- Drench yourself in a feeling of attainment.
- Tweet.
Prepare for the next release¶
You may be unsure what the next version is going to be, but you must change the version string. So bump the patch version by one.
- Bump the version.
- Create a new entry in the change–log.
WYMeditor Website¶
The website at http://wymeditor.github.io/wymeditor/
is served via GitHub Pages.
Jekyll is used for building it. GitHub has an integrated Jekyll feature. That is not used. Jekyll is used in the development environment and the built website is checked in to the repository.
In GitHub Pages, websites for projects are stored in a separate
gh-pages
branch.
WYMeditor’s website is developed in the master
branch
and is built and published to the gh-pages
branch
as part of each release.
This promotes consistency. It also allows us to serve the demos/examples via GitHub Pages.
Website files¶
The website’s only page is the README.rst
file, converted to HTML.
This occurs automatically in builds.
The theme is a ported version of a GitHub Pages layout.
The Jekyll layout is at src/jekyll/_layouts/home.html
.
Its media and styles are located in src/jekyll/website-media
.
Environment for development of the website¶
There is no Jekyll configuration file, _config.yml
.
Instead, Grunt orchestrates building and serving.
Configuration is within the Gruntfile.js
.
The following will install Jekyll. In this example, RVM is used.
$ rvm install 2.2
$ bundle install
Docutils is also required. Docutils is probably available in your Linux
distribution as python3-docutils
or python-docutils
. In particular, it
is required that rst2html
be globally available.
Previewing the website locally¶
The website is served in development by running:
$ grunt server
It is then available at http://localhost:9000/website/
.
While this is running, changes to any of the contents of the website’s directory result in automatic rebuilding of the website. LiveReload is implemented.
Building the website¶
The website is built as part of the entire project build:
$ grunt build
Or, exclusively:
$ grunt jekyllDist
It gets built to dist/website
, which should then be committed.
Publishing the website¶
Publishing the website is a matter of pushing to the gh-pages
branch:
$ git push origin master:gh-pages
This should be done during the process of making a release. Doing it between releases will result in broken download links in the website.
Troubleshooting¶
Error Running $ grunt server
¶
On some linux systems, (eg. Ubuntu 10.04), you see something like:
$ grunt server
Running "express:all" (express) task
Running "open:all" (open) task
Web server started on port:9000, hostname: 0.0.0.0 [pid: 29903]
Running "watch" task
Waiting...Fatal error: watch ENOSPC
That ENOSPC
thing is related to your inotify
watchers.
Basically,
you’re trying to watch more files than are allowed.
Just Fix It¶
To just up the number of inotify
watchers allowed,
run:
$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
More Details¶
For a more detailed explanation, see the guard/listen wiki.
Resources¶
Using WYMeditor¶
Introduction¶
These guidelines will teach you why to use certain techniques when creating content and how to work with them in WYMeditor. Following these will keep the structure and meaning of your content correct - making it more flexible to technologies like SEO1 (Search Engine Optimization), screen readers2, mobile phone technologies, printers etc. Following these guidelines will also make your content accessible to a wider range of people, with or without disabilities
Guidelines¶
1. Use elements as intended
HTML is a so called markup language. It’s purpose is to give the content meaning and structure, not visual effects. Therefore, it’s important to use elements as intended to not break meaning, structure and compatibility to other technologies. If you want to style your content you’ve created with WYMeditor, you shall learn to use CSS (Cascading Style Sheets).
1.1. Headings
Using a logical and proper heading structure is among one of the most important SEO techniques. Always use a correct hierarchy. Start with Heading 1, followed by Heading 2, Heading 3... etc. A proper heading structure will also help users to find what they are looking for, faster.
To create a heading you simply place your marker where you want the heading to go and then select “Containers > Heading 1” for example.
1.2. Tables
Creating lean and accessible tables is a craft but WYMeditor makes it easy. Only use the Table element to arrange different kinds of data - time tables, statistics, member listings etc. Use the Table Header container to mark up headers properly. Doing this wrong will confuse users with screen readers when accessing tables, as headers aren’t part of the data.
To create a Table Header you simply place your marker in a table cell and select “Containers > Table Header”.
It’s also good practice to give users the ability to do a quick overlook of the table’s content by filling in a short description in the Caption field. You can do that in the dialog appearing when creating the table.
1.3. Lists
It’s important to mark up lists of items the right way, using Ordered list or Unordered list. Doing this wrong makes it harder for users to overlook the content and will also confuse users with screen readers. Creating a list using Shift+Enter to separate items is therefore highly depreciated as both structure and meaning will break in the document.
To create a list you simply use one of the two list buttons in WYMeditor’s toolbar. Hit enter twice to “jump out of” a list.
1.4. Indent and outdent
The Indent and Outdent icons are only intended to create nested lists of list items (as described in 1.3). Place the marker on a list item and hit the Indent icon to make it go sub level. To undo this move or to make it go back one level, simply hit the Outdent icon.1.5. Blockquotes
Mark up your quoted text by using the Blockquote container. Doing this the right way will help search engines and users with screen readers to understand your text and give it relevant meaning.
2. Provide alternatives
2.1. Alternative text
An alternative text string shall be accurate and equivalent in presenting the same content and function as the image. This also helps to identify the relevance and meaning of your image which improves SEO and accessibility for user with screen readers.
You can enter the Alternative text in the image dialog, appearing when adding or editing an image. The only exception to leave the field empty is when your image is for decoration purpose only.
2.2. Title attribute
To provide additional information of a link or and image you can provide a Title text. This helps several other technologies to identify the relevance and meaning of your elements which improves SEO and accessibility for user with screen readers.
You can enter the Title text in the image/link dialog, appearing when adding or editing an element.
3. Write understandable
Writing understandable is among one of the most important checkpoints in creating accessible content. Unfortunately computers can’t do anything about this, yet. But with WYMeditor you have got the tools to make it down the right way.
3.1. Descriptive headings
A heading element shall briefly describe the topic of the section it introduces making it easier to overlook both for all users. This will also improve the SEO aspects of your content.3.2. Descriptive link texts
A link text shall be completely descriptive of its location, which makes it a lot easier to overlook and skim-read the content for all users. This will also improve the SEO aspects and the “link indexing” procedure for users with screen readers. “Click here” links are therefor highly depreciated.
Word References¶
- SEO (Search Engine Optimization)
- “Search engine optimization” is a conception for everything that makes your content rank higher when using search engines like Google.
- Screen reader
- A screen reader is a text-only web browser that dictates webpages’ content to visually disabled peoples. In this text “screen readers” refers to all sorts of assistive and adoptive web technologies.