05 Oct 2010What good is building a really nice CMS with top-notch WYSIWYG editor if you handed it to clients that will blindly copy/paste Word documents into it ?
Well, it will just render a big ugly mess of proprietary and contradictory pseudo-css rules. You could even be blamed for it.
The solution
The latest versions of tinyMCE came bundled with an improved paste
plugin. It will automatically attempt to clean bad pasted text by operating some dark voodoo magic on it.
It does it quite well to be honest, removing almost all messy formatting. From the tests I tried, I was still getting useless crap in the resulting text (CSS comments, <span>
with overly long style of color and background-color definitions, etc).
I finally decided to take a more brutal approach. The paste
plugin contained a _insertPlainText
method that was supposed to be used in conjunction with a toolbar icon.
This method removes all formatting. Period.
As I didn't want to have to click on a toolbar icon before pasting my text (and none of my actual clients would ever think of doing that either), I came up with a very simple plugin to do the boring stuff for me.
The plugin
The only goal of this plugin is setting to true
the pasteAsPlainText
property of every editor. This property is defaulted to false
but can be swapped using the toolbar icon.
I also forced the paste_text_sticky
setting to true
, preventing the previous property to revert to false
after the first pasting.
Here's my plugin full code :
Be sure to include the paste
plugin in your plugin list, and insert my plugin *after *the paste plugin.
/**
* Will automatically convert pasted text so no junk code will be included.
* This plugin depends on the core paste plugin.
**/
(function() {
tinymce.create('tinymce.plugins.pasteAsPlainTextPlugin', {
init : function(editor, url) {
// We force the pasting to occur as plain text
editor.pasteAsPlainText = true;
// We also put it as sticky to allow for multiple pastings
editor.settings.paste_text_sticky = true;
// Adding some special post process
editor.settings.paste_postprocess = function(a, o) {
var text = o.content,
split = String.fromCharCode(13)+String.fromCharCode(10)
;
// If content is long text without HTML, We'll break it into <p>ieces
if (text.charAt(0)!='<' && text.indexOf(split)!=-1) {
// Adding <p> around each line
var node = document.createElement('div'),
sentences = text.split(split)
;
for(var i=0,max=sentences.length;i!=max;i++) {
node.innerHTML+='<p>'+sentences[i]+'</p>';
}
// Saving back in original content/node
o.node = node;
o.content = node.innerHTML;
}
return o;
}
}
});
// Register plugin
tinymce.PluginManager.add('pasteAsPlainText', tinymce.plugins[pasteAsPlainTextPlugin]);
})();
Update
I added a postprocess
calback after seing that the pasted text was a little too plain in Webkit. All my text was displayed on the same line, without the nice breaking into paragraph that Firefox showed.
It occured because tinyMCE used the event.clipboardData
property that Webkit browsers provides and allow for easy retrieving of clipboard data. Unfortunatly it returned a really plain text, and I had to apply a little loop to replace each new line with a paragraph.
21 Aug 2010I finally managed to fix something that was bugging me for a very long time. I'm talking about the jQuery UI tabs.
They are pretty useful actually, but I always hated that FOUC they produce. For one split second, you'll see all your tabs content, then they'll be neatly re-arranged in tabs.
What I want are my tabs displayed correctly right away, on first load.
First, the markup
Here's a typical tabs markup :
<div class="tabs">
<ul>
<li><a href="#firstTab">First tab</a></li>
<li><a href="#secondTab">Second tab</a></li>
</ul>
<div class="tabPanel" id="firstTab">
First tab content
</div>
<div class="tabPanel" id="secondTab">
Second tab content
</div>
</div>
CSS to hide all tabs if Javascript is disabled
If your Javascript is disabled, so will jQuery UI. We will then hide the <ul>
because it serves no purpose here. We will only show it if js is enabled
.tabs ul { display:none; }
.js .tabs ul { display:block; }
Applying jQuery UI tabs
By doing a$('.tabs').tabs();
jQuery UI will treat your <ul>
as your tab menu and all your .tabPanel
as their corresponding contents. It will hide all your panels, except for the first one. It does so by adding a.ui-tabs- panel
class to every .tabPanel
as well as a .ui-tabs-hide
to every panel it hides.
Right now, you should add another CSS rule to hide the unused panels :
.ui-tabs-hide { display: none; }
But if you look at your page now, you'll see all your tabs content before they get hidden. That is the FOUC I was talking about. The jQuery UI documentation indicate that to remove it, you should directly add the .ui-tabs-hide
class to panels you'll want to hide.
As also pointed in the doc, it will not gracefully degrade because users without Javascript won't even be able to see your other tabs. Also, it asks you to add server-side logic (HTML markup with jQuery specific classes) for something that should be handled entirely client-side.
Removing the FOUC while gracefully degrade
Ok, so what I did was writing two simple rules that will directly hide all unused panels while still displaying the active one, even before jQuery UI takes action.
.js .tabPanel + .tabPanel { display:none;}
That way, no FOUC, and users without Javascript still see all the content. Unfortunatly, if you now try clicking on your tabs, you'll see that nothing happens and you get stuck with your default panel always visible.
Fixing the jQuery UI tabs
As jQuery will add new classes to my elements, I'll just have to write more specific rules that use those classes. Here's the little piece of logic I came up with :
.js .tabPanel.ui-tabs-panel { display:block;}
.js .tabPanel.ui-tabs-panel.ui-tabs-hide { display:none;}
All the jQuery panels are shown, except for the one hidden by jQuery. All those rules being more and more specific, they will get applied once the tabs are activated but still override the previous display:
declarations.
Conclusion
This is how I fixed an issue that was bugging me for years. Unfortunatly the CSS rules being dependent on the markup used, I haven't yet been able to write them in a global form that could be added to the main jQuery UI css files.
*[FOUC]: Flash Of Unstyled Content
21 Aug 2010In a previous post I was wondering if my way of loading js-specific CSS rules was right. I was loading a specific css file for js overrides.
This allow for lighter stylesheets for users without Javascript (they won't load useless rules), but also resulted in slower rendering for other users because they needed to load two stylesheets instead of one.
After much pondering, I now think that loading all the rules in one stylesheet is the way to go.
In addition with the number of request mentionned, keeping all your rules in the same file is much more easier to maintain. Js-specific rules aren't that big either so the small additional bytes are usually better than a full http request.
I now prefix all my Javascript rules by .js
. I add a js
class to the html
element directly from the head
, this helps in preventing any FOUC that may occur because we add the class before the body
rendering.
<head>
<script>document.documentElement.className+=' js';</script>
</head>
19 Aug 2010I know only two _unperfect _ways of displaying an emboss text in CSS. As we can't use inset shadows with text-shadow
, unlike box-shadow
, we have to resort to other hacky ways.
Let me show you what can be done.
Simulate it with an optical illusion
Instead of creating a real inner shadow, you can just create an optical illusion by adding the correct shadow to make the viewer believe your text is embossed.
If you have dark text, just add a subtle white shadow at the bottom, while if you have light text, just add a subtle black one at the top.
<a class="emboss light">Lorem ipsum</a>
<a class="emboss dark">Lorem ipsum</a>
.emboss { font-size:1.2em; padding:50px; font-weight:bold; background:#0A539C; display:block; }
.light { color:#FFFFFF; text-shadow:0px -1px 0px rgba(0,0,0,0.9); }
.dark { color:#000000; text-shadow:0px 1px 0px rgba(255,255,255,0.1); }
I added an emboss
class to more easily spot the effect, but the important code is in the light
and dark
rules.
Add an almost transparent layer
For this second solution we will simply add a second layer with the exact same text above the first text. Just slightly moving it and making it partly transparent.
You could add twice your element in your markup, but that would be bad for accesibility as well as SEO, so we'll instead use the :before
pseudo-class.
We can set the :before
content using the content:
property, and we can also use theattr()
method to get the content of one of our element's attributes. We just need to put the exact same text in one of our attributes and we're good to go.
This can be easily done with a link and its title
attribute.
<a class="emboss lightAgain" title="Lorem ipsum">Lorem ipsum</a>
<a class="emboss darkAgain" title="Lorem ipsum">Lorem ipsum</a>
.lightAgain { color:#FFFFFF; position:relative; }
.lightAgain:before {
content: attr(title);
position:absolute;
color:rgba(0,0,0,0.1);
top:-1px;
left:-1px;
padding:50px;
}
.darkAgain { color:#000000; position:relative; }
.darkAgain:before {
content: attr(title);
position:absolute;
color:rgba(255,255,255,0.1);
top:1px;
left:1px;
padding:50px;
}
The effect is much better with this technique but it also has it share of drawback :
- You have to write your content twice, once in the element and once in an attribute
- You have to copy all your
padding
rules in the :before
element or it won't get correctly positionned - You can't select the text with this effect
Hope those two little techniques helped someone, anyway.
19 Aug 2010Sometimes you have a really great design with some really fancy Javascript goodness, like drag'n'drop and other shiny Ajaxy stuff.
But when you browse the website with Javascript disabled, none of that works and you end up with interface elements that should'nt be here because they do not work.
In those case, you'd rather display a nice message to your user, telling him that he can't use the feature withou Javascript enabled.
But how do you do that ?
Well, I myself load two different stylesheets. Remember that your website should be working without Javascript, this is just the last layer you add.
My default stylesheet will load all the rules for when Javascript is not enabled. No fancy cursor:move
here.
Then I load a second stylesheet using Javascript, using document.write()
in the <head>
. And that's in this one that I write rules that overload the previous one. I add every styling that deals with Javascript-enabled features here.
Limitations
I may be changing the way I load the JS stylesheet in the future. I don't really like relying on document.write
because it is evil. I also don't like the idea of getting one extra request.
I could add a js
class to my body element (like modernizr does with all its CSS3 properties) and then target elements by prepending .js
to the rule.
But it means adding rules in my main CSS file for users without JS that will still be downloading those extra useless bytes.
I haven't yet figured which way was the best (or should I say, the worst)
Convenient methodes
Whatever way you choose, one thing that really helped me was two real simple classes : jsOn
and jsOff
that I add to elements.
Elements with jsOn
will only be visible if Javascript is enabled and hidden otherwise, while element with jsOff
will do the opposite.
Assuming you mark your body element with a js
class if Javascript is enabled, here's how to do it :
.jsOn { display:none; }
.js .jsOn { display:block;
.jsOff { display:block;
.js .jsOff { display:none;
Hope all that helps at least someone.