1 /**
  2  * @author Lukas Fryc
  3  */
  4 
  5 (function($, rf) {
  6     rf.ui = rf.ui || {};
  7 
  8     /**
  9      * Default component configuration
 10      */
 11     var defaultOptions = {
 12         toolbar : 'Basic',
 13         skin: 'moono',
 14         readonly : false,
 15         style : '',
 16         styleClass : '',
 17         editorStyle : '',
 18         editorClass : '',
 19         width : '100%',
 20         height : '200px'
 21     };
 22     
 23     var eventsForDirty = [ "key", "paste", "undo", "redo" ];
 24 
 25     rf.ui.Editor = function(componentId, options, config) {
 26         $super.constructor.call(this, componentId);
 27         this.options = $.extend({}, defaultOptions, options);
 28 
 29         this.componentId = componentId;
 30         this.textareaId = componentId + ':inp';
 31         this.editorElementId = 'cke_' + this.textareaId;
 32         this.valueChanged = false;
 33         this.dirtyState = false;
 34         this.config = $.extend({}, config);
 35 
 36         this.attachToDom(this.componentId);
 37 
 38         $(document).ready($.proxy(this.__initializationHandler, this));
 39         rf.Event.bindById(this.__getTextarea(), 'init', this.options.oninit, this);
 40         rf.Event.bindById(this.__getTextarea(), 'dirty', this.options.ondirty, this);
 41     };
 42 
 43     rf.BaseComponent.extend(rf.ui.Editor);
 44 
 45     var $super = rf.ui.Editor.$super;
 46 
 47     $.extend(rf.ui.Editor.prototype, {
 48 
 49         name : "Editor",
 50 
 51         __initializationHandler : function() {
 52             this.ckeditor = CKEDITOR.replace(this.textareaId, this.__getConfiguration());
 53 
 54             // register event handlers
 55             if (this.__getForm()) {
 56                 this.__updateTextareaHandlerWrapper = rf.Event.bind(this.__getForm(), 'ajaxsubmit', $.proxy(this.__updateTextareaHandler, this));
 57             }
 58             this.ckeditor.on('instanceReady', $.proxy(this.__instanceReadyHandler, this));
 59             this.ckeditor.on('blur', $.proxy(this.__blurHandler, this));
 60             this.ckeditor.on('focus', $.proxy(this.__focusHandler, this));
 61             // register handlers for 'dirty' event
 62             for (var i in eventsForDirty) {
 63                 this.ckeditor.on(eventsForDirty[i], $.proxy(this.__checkDirtyHandlerWithDelay, this));
 64             }
 65             // interval for dirty checking
 66             this.dirtyCheckingInterval = window.setInterval($.proxy(this.__checkDirtyHandler, this), 100);
 67         },
 68         
 69         __checkDirtyHandlerWithDelay : function() {
 70             window.setTimeout($.proxy(this.__checkDirtyHandler, this), 0);
 71         },
 72         
 73         __checkDirtyHandler : function() {
 74             if (this.ckeditor.checkDirty()) {
 75                 this.dirtyState = true;
 76                 this.valueChanged = true;
 77                 this.ckeditor.resetDirty();
 78                 this.__dirtyHandler();
 79             }
 80         },
 81         
 82         __dirtyHandler : function() {
 83             this.invokeEvent.call(this, "dirty", document.getElementById(this.textareaId));
 84         },
 85         
 86         __updateTextareaHandler : function() {
 87             this.ckeditor.updateElement();
 88         },
 89 
 90         __instanceReadyHandler : function(e) {
 91             this.__setupStyling();
 92             this.__setupPassThroughAttributes();
 93 
 94             this.invokeEvent.call(this, "init", document.getElementById(this.textareaId), e);
 95         },
 96 
 97         __blurHandler : function(e) {
 98             this.invokeEvent.call(this, "blur", document.getElementById(this.textareaId), e);
 99             if (this.isDirty()) {
100                 this.valueChanged = true;
101                 this.__changeHandler();
102             }
103             this.dirtyState = false;
104         },
105 
106         __focusHandler : function(e) {
107             this.invokeEvent.call(this, "focus", document.getElementById(this.textareaId), e);
108         },
109 
110         __changeHandler : function(e) {
111             this.invokeEvent.call(this, "change", document.getElementById(this.textareaId), e);
112         },
113 
114         __getTextarea : function() {
115             return $(document.getElementById(this.textareaId));
116         },
117 
118         /**
119          * Returns the form where this editor component is placed
120          */
121         __getForm : function() {
122             return $('form').has(this.__getTextarea()).get(0);
123         },
124 
125         __getConfiguration : function() {
126             var textarea = this.__getTextarea();
127             return $.extend({
128                 skin : this.options.skin,
129                 toolbar : this.__getToolbar(),
130                 readOnly : textarea.attr('readonly') || this.options.readonly,
131                 width : this.__resolveUnits(this.options.width),
132                 height : this.__resolveUnits(this.options.height),
133                 bodyClass : 'rf-ed-b',
134                 defaultLanguage : this.options.lang,
135                 contentsLanguage : this.options.lang
136             }, this.config);
137         },
138 
139         __setupStyling : function() {
140             var span = $(document.getElementById(this.editorElementId));
141             if (!span.hasClass('rf-ed')) {
142                 span.addClass('rf-ed');
143             }
144             var styleClass = $.trim(this.options.styleClass + ' ' + this.options.editorClass);
145             if (this.initialStyle == undefined) {
146                 this.initialStyle = span.attr('style');
147             }
148             var style = this.__concatStyles(this.initialStyle, this.options.style, this.options.editorStyle);
149             if (this.oldStyleClass !== styleClass) {
150                 if (this.oldStyleClass) {
151                     span.removeClass(this.oldStyleClass);
152                 }
153                 span.addClass(styleClass);
154                 this.oldStyleClass = styleClass;
155             }
156             if (this.oldStyle !== style) {
157                 span.attr('style', style);
158                 this.oldStyle = style;
159             }
160         },
161 
162         __setupPassThroughAttributes : function() {
163             var textarea = this.__getTextarea();
164             var span = $(document.getElementById(this.editorElementId));
165 
166             // title
167             span.attr('title', textarea.attr('title'));
168         },
169 
170         __concatStyles : function() {
171             var result = "";
172             for ( var i = 0; i < arguments.length; i++) {
173                 var style = $.trim(arguments[i]);
174                 if (style) {
175                     result = result + style + "; ";
176                 }
177             }
178             return result;
179         },
180 
181         __getToolbar : function() {
182             var toolbar = this.options.toolbar;
183 
184             var lowercase = toolbar.toLowerCase();
185             if (lowercase === 'basic') {
186                 return 'Basic';
187             }
188             if (lowercase === 'full') {
189                 return 'Full';
190             }
191 
192             return toolbar;
193         },
194 
195         __setOptions : function(options) {
196             this.options = $.extend({}, defaultOptions, options);
197         },
198 
199         __resolveUnits : function(dimension) {
200             var dimension = $.trim(dimension);
201             if (dimension.match(/^[0-9]+$/)) {
202                 return dimension + 'px';
203             } else {
204                 return dimension;
205             }
206         },
207 
208         getEditor : function() {
209             return this.ckeditor;
210         },
211 
212         setValue : function(newValue) {
213             this.ckeditor.setData(newValue, $.proxy(function() {
214                 this.valueChanged = false;
215                 this.dirtyState = false;
216                 this.ckeditor.resetDirty();
217             }, this));
218         },
219 
220         getValue : function() {
221             return this.ckeditor.getData();
222         },
223 
224         getInput : function() {
225             return document.getElementById(this.textareaId);
226         },
227 
228         focus : function() {
229             this.ckeditor.focus();
230         },
231 
232         blur : function() {
233             this.ckeditor.focusManager.blur(true);
234         },
235 
236         isFocused : function() {
237             return this.ckeditor.focusManager.hasFocus;
238         },
239 
240         isDirty : function() {
241             return this.dirtyState || this.ckeditor.checkDirty();
242         },
243 
244         isValueChanged : function() {
245             return this.valueChanged || this.isDirty();
246         },
247 
248         setReadOnly : function(readOnly) {
249             this.ckeditor.setReadOnly(readOnly !== false);
250         },
251 
252         isReadOnly : function() {
253             return this.ckeditor.readOnly;
254         },
255 
256         destroy : function() {
257             window.clearInterval(this.dirtyCheckingInterval);
258             
259             if (this.__getForm()) {
260                 rf.Event.unbind(this.__getForm(), 'ajaxsubmit', this.__updateTextareaHandlerWrapper);
261             }
262 
263             if (this.ckeditor) {
264                 this.ckeditor.destroy();
265                 this.ckeditor = null;
266             }
267 
268             this.__getTextarea().show();
269 
270             $super.destroy.call(this);
271         }
272     });
273 })(RichFaces.jQuery, RichFaces);
274