Anonymous
Domino 2.0 Rich Internet Applications with IBM Lotus Notes/Domino
You are here: Today » Domino Workspace: Rich text editor beta
« My new XHTML page skeleton
Makig a calendar in LotusScript »

Domino Workspace: Rich text editor beta

This rich text editor is meant to be ultra compact for performance reasons. Since it will be used to structure the content rather that to style it, I only allow the most basic formatting. You can, however, paste entire Word documents in it and it will clean them up. It is implemented in non-obtrusive JavaScript so it builds itself automatically on page load. And it uses CSS sprites for the button images.

The required HTML

The JavaScript needs a very specific HTML for each textarea. Replace '(fieldName)' with the name of the field, '(fieldContent)' with the content of the field (HTML).

<div class="rich-text">
<div></div>
<textarea name="(fieldName)" rows="10" cols="80">(fieldContent)</textarea>
</div>

The stylesheet

/* def.css */
body{font-family:verdana,sans-serif;font-size:11px}
h1{font-size:14px;color:#666}
h2{font-size:12px;color:#c600}

/* rich text edit */
div.rich-text{border:solid 1px #666;margin-bottom:10px;padding:1px}
.rich-text textarea,.rich-text iframe{width:100%;border:0}
.rte-bar{height:20px;background:#ddd;padding:2px;border-bottom:solid 1px #666}
.rte-bar b,.rte-bar select{display:block;float:left}
.rte-bar b{width:20px;height:20px;cursor:pointer;background-image:url(SmarticonsR65.gif);margin-right:3px}
.rte-bar b:hover{background-color:#eee}
.bold{background-position:-280px -60px}
.italic{background-position:-300px 0}
.justifyleft{background-position:-260px -160px}
.justifycenter{background-position:-260px -120px}
.justifyright{background-position:-280px 0}
.insertunorderedlist{background-position:-280px -80px}
.insertorderedlist{background-position:-300px -40px}
.rte-bar select{font-size:11px;margin:0 5px}
.insert-link{background-position:-100px -40px}
.insert-image{background-position:-60px -20px}
.view-toggle{background-position:-240px -40px}

All the JavaScript

var G={w:window,d:document,gecko:navigator.product=='Gecko',
init:function(){for(var n in G)if(G[n].init)G[n].init()},
set:function(a,b){for(var o in b)a[o]=b[o];return a},
create:function(a,b,c,d){var o=(d||G.d).createElement(a);G.set(o,b);G.set(o.style,c);return o},
append:function(a,b,c,d){var o=b.tagName?b:G.create(b,c,d);a.appendChild(o);return o}
},$=function(a,b){return (b||G.d).getElementById(a)},$$=function(a,b){return (b||G.d).getElementsByTagName(a)}

G.edit={
blocks:' h1 h2 h3 p ol ul li div table tr th td ',
inline:' img span strong em br b i a ',
init:function(){
    var m=this,o=$$('div'),i
    m.all=m.blocks+m.inline
    m.style=G.d.styleSheets[0].href
    for(i=0;i<o.length;i++)if(o[i].className=="rich-text")m.prepare(o[i])
},
prepare:function(a){
    var m=this,tb=$$('div',a)[0],sr=$$('textarea',a)[0],o,od
    G.set(tb,{className:'rte-bar',innerHTML:m.toolbar()})
    o=G.append(a,'iframe',{frameBorder:0},{width:sr.offsetWidth+'px',height:sr.offsetHeight+'px'})
    sr.style.display='none'
    a.source=sr
    a.rte=o.contentWindow
    od=a.rte.document
    od.designMode='On'
    od.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">')
    od.write('<html><head><meta http-equiv="content-type" content="text/html;charset=UTF-8" />')
    od.write('<link rel="stylesheet" type="text/css" href="'+m.style+'" />')
    od.write('</head><body id="rte-body">'+sr.value+'</body></html>')
    od.close()
},
toolbar:function(){
    var m=this,f=m.button,x=m.option,v=
f('Bold'),f('Italic'),f('Left','justifyleft'),f('Center','justifycenter'),f('Right','justifyright'),
f('Unordered list','insertunorderedlist'),f('Ordered list','insertorderedlist'),
'<select onchange="G.edit.change(this)">',x('','[format]'),x('h2','Header 2'),x('h3','Header 3'),x('p','Normal'),'</select>',
f('Insert link','insert-link'),f('Insert image','insert-image'),f('Source','view-toggle')]
    return v.join('\n')
},
button:function(a,b){
    var v=b?b:a.toLowerCase()
    return '<b title="'+a+'" class="'+v+'" unselectable="on" onclick="G.edit.exec(this,\''+v+'\')"></b>'
},
option:function(a,b){
    return '<option value="'+a+'">'+b+'</option>'
},
exec:function(a,b,c){
    var m=this,op=a.parentNode.parentNode,o=op.rte,v,os,ob
    switch(b){
    case 'insert-link':
        v=prompt('Hyperlink:','http://')
        if(v && v!='' && v!='http://')o.document.execCommand('createlink',false,v)
        break
    case 'insert-image':
        v=prompt('Image URL:','')
        if(v && v!='')o.document.execCommand('insertimage',false,v)
        break
    case 'view-toggle':
        os=$$('textarea',op)[0]
        ob=(os.style.display=='none')
        if(ob){
            if(G.gecko)m.removeEmpty(o.document.body)
            os.value=m.clean(o.document.body)
        }
        else o.document.body.innerHTML=os.value
        os.style.display=ob?'block':'none'
        $$('iframe',op)[0].style.display=ob?'none':'block'
        break
    default:
        o.document.execCommand(b,false,null)
    }
    o.focus()
},
change:function(a){
    var o=a.parentNode.parentNode.rte,v=a.options[a.selectedIndex].value
    o.document.execCommand('formatblock',false,'<'+v+'>')
    a.selectedIndex=0
    o.focus()
},
removeEmpty:function(a){
    var i=0,o=a.childNodes
    while(i<o.length){if(o[i] && o[i].nodeType==3 && !o[i].data.search(/^\s+$/))a.removeChild(o[i]);i++}
},
clean:function(a,b){
    var m=this,v=[],o=a.childNodes,i,good=b?b:m.all
    for(i=0;i<o.length;i++)v.push(m.cleanNode(o[i],good))
    return v.join('')
},
cleanNode:function(a,b){
    var m=this,nn,q,bt,it
    if(a.nodeType==3)return a.data
    nn=a.nodeName.toLowerCase()
    it=m.inline.indexOf(' '+nn+' ')>-1
    if(b.indexOf(' '+nn+' ')==-1)return m.clean(a,b)
    switch(nn){
    case 'h1':return '<h2>'+m.clean(a,b)+'</h2>'
    case 'br':return '<br />'
    case 'img':return '<img src="'+a.src+'" alt="'+a.alt+'"'+(a.className?' class="'+a.className+'"':'')+' />'
    case 'a':return '<a href="'+a.href+'" title="'+a.title+'"'+(a.className?' class="'+a.className+'"':'')+'>'+m.clean(a,m.inline)+'</a>'
    case 'p':return '<p'+m.attributes(a)+'>'+m.clean(a,m.inline)+'</p>\n'
    default:return '<'+nn+m.attributes(a)+'>'+m.clean(a,it?m.inline:m.blocks)+'</'+nn+'>'
    }
},
attributes:function(a){
    var m=this,v=[],ac=a.className,as=m.allowedStyle(a)
    if(ac && !ac.indexOf('Mso')==0)v.push(' class="'+ac+'"')
    if(a.align=='center'||a.align=='right')v.push(' align="'+a.align+'"')
    if(as!='')v.push(' style="'+as+'"')
    return v.join('')
},
allowedStyle:function(a){
    var m=this,o=a.style,v=[],i,q
    if(!o)return ''
    if(o.textAlign=='center')v.push('text-align:center')
    if(o.textAlign=='right')v.push('text-align:right')
    if(o.fontWeight=='bold')v.push('font-weight:bold')
    if(o.fontStyle=='italic')v.push('font-style:italic')
    return v.join(';')
}
}
G.w.onload=G.init

The result

Try it out on the test page. You should be able to edit the text. You can even try to paste in text from other applications. Clicking on the 'Source' button cleans up the code and displays it. Clicking again brings you back to edit mode.

Next steps

In the next few days, I will integrate the rich text editor in Domino Workspace. I still have to even out a lot of browser differences, making an nice interface to insert images and links, putting the correct HTML back in the fields, etc. All comments are welcome.

Download

dws-edit.zip (19 kB)

Star rating

0%

Comments

  1. 06/09/2007 19:43:03, Jan Schulz

    Opera 8.23 displays the "toolbar" slightly wrong (the hyperlink button is over the paragraph dropdown).

    Do yo plan to add image uploading to this?

  2. 06/09/2007 20:45:33, Michel Van der Meiren

    Hey, fantastic. I didn't check with Opera yet. I'm very surprised that it even works in that browser. I will put the wrong toolbar display on my to do list. And, yes, image and file uploading is planned.

  3. 07/09/2007 05:35:45, Kevin S Pettitt

    Looks good so far :-)

  4. 07/09/2007 08:00:05, Erwin Heeren

    When will you stop embarrasing us, mere mortals? Very impressive and useful.

  5. 07/09/2007 11:07:01, Michel Van der Meiren

    Hallo Erwin. Don't forget that I have more than 10 years experience in building web applications with Domino. It is that experience that enables me to perform these hat tricks.

  6. 12/09/2007 14:06:16, Mike McP

    Very cool.

  7. 30/10/2007 22.58.08, Stefano Balocco

    Hi, you done a good job. Can I use it in a my project? If yes, what is the license?

  8. 11/29/2007 11:14:51 AM, Johnny Jiang

    Hi Michel, good job. I'm struggling with Domino's web rich text editor as well. One of the drawback of the built-in Notes rich text editor is the loss of embedded images while rich text is being edited with both Web client and Notes client. Do you have any thoughts to share with me about this issue? Thanks.

To add a comment, log in or register as new user. It's free and safe.