/** * Editing of documents composed of different parts * * @constructor * @fires hui.ui.Editor#editPart * @fires hui.ui.Editor#cancelPart * @fires hui.ui.Editor#savePart * @fires hui.ui.Editor#addPart */ hui.ui.Editor = function() { this.name = 'huiEditor'; this.options = {rowClass:'row',columnClass:'column',partClass:'part'}; this.parts = []; this.rows = []; this.partControllers = []; this.activePart = null; this.active = false; this.live = true; this.enableStructure = true; hui.ui.extend(this); }; /** * Get the one and only editor instance * @return {hui.ui.Editor} */ hui.ui.Editor.get = function() { if (!hui.ui.Editor.instance) { hui.ui.Editor.instance = new hui.ui.Editor(); } return hui.ui.Editor.instance; }; hui.ui.Editor.prototype = { /** Start the editor */ ignite : function() { //hui.listen(window,'keydown',this._onKeyDown.bind(this)); //hui.listen(window,'keyup',this._onKeyUp.bind(this)); this.reload(); }, /* _onKeyDown : function(e) { e = hui.event(e); //this.live = e.altKey; }, _onKeyUp : function(e) { //this.live = false; },*/ addPartController : function(key,title,controller) { this.partControllers.push({key:key,'title':title,'controller':controller}); }, setOptions : function(options) { hui.override(this.options,options); }, _getPartController : function(key) { var ctrl = null; hui.each(this.partControllers,function(item) { if (item.key==key) {ctrl=item;} }); return ctrl; }, reload : function() { if (this.partControls) { this.partControls.hide(); } var self = this; this.parts = []; var rows = hui.get.byClass(document.body,this.options.rowClass); hui.each(rows,function(row,i) { var columns = hui.get.byClass(row,self.options.columnClass); self._reloadColumns(columns,i); hui.each(columns,function(column,j) { var parts = hui.get.byClass(column,self.options.partClass); self._reloadParts(parts,i,j); }); }); /* var parts = hui.get.byClass(document.body,this.options.partClass); hui.each(this.parts,function(part) { var i = parts.indexOf(part.element); if (i!=-1) { delete(parts[i]); } }); this._reloadParts(parts,-1,-1); */ }, _reloadColumns : function(columns,rowIndex) { var self = this; hui.each(columns,function(column,columnIndex) { hui.listen(column,'mouseover',function() { self._onHoverColumn(column); }); hui.listen(column,'mouseout',function(e) { self._onBlurColumn(e); }); if (self.enableStructure) hui.listen(column,'contextmenu',function(e) { self.contextColumn(column,rowIndex,columnIndex,e); }); }); }, _reloadParts : function(parts,row,column) { var self = this; var reg = new RegExp(this.options.partClass+"_([\\w]+)","i"); hui.each(parts,function(element,partIndex) { if (!element) return; var match = element.className.match(reg); if (match && match[1]) { var handler = self._getPartController(match[1]); if (handler) { var part = new handler.controller({element:element}); part.type = match[1]; hui.listen(element,'click',function(e) { e = hui.event(e); if (!e.findByTag('a') && e.altKey) { self._editPart(part); } }); hui.listen(element,'mouseover',function(e) { self.hoverPart(part); }); hui.listen(element,'mouseout',function(e) { self.blurPart(e); }); self.parts.push(part); } hui.listen(element,'mousedown',function(e) { self._startPartDrag({ element : element, event : e }); }); } }); }, activate : function() { this.active = true; }, deactivate : function() { this.active = false; if (this.activePart) { this._deactivatePart(this.activePart); } if (this.partControls) { this.partControls.hide(); } }, ///////////////////////// Columns //////////////////////// _onHoverColumn : function(column) { if (this.hoveredColumn) { hui.cls.remove(this.hoveredColumn,'hui_editor_column_hover'); } this.hoveredColumn = column; if (!this.active || this.activePart) { return; } hui.cls.add(column,'hui_editor_column_hover'); }, _onBlurColumn : function(e) { if (!this.active || !this.hoveredColumn || hui.ui.isWithin(e,this.hoveredColumn)) return; hui.cls.remove(this.hoveredColumn,'hui_editor_column_hover'); }, contextColumn : function(column,rowIndex,columnIndex,e) { if (!this.active || this.activePart) return; if (!this.columnMenu) { var menu = hui.ui.Menu.create({name:'huiEditorColumnMenu'}); menu.addItem({title:'Edit part',value:'editSection'}); menu.addDivider(); menu.addItem({title:'Edit column',value:'editColumn'}); menu.addItem({title:'Remove column',value:'removeColumn'}); menu.addItem({title:'Add column',value:'addColumn'}); menu.addDivider(); menu.addItem({title:'Edit row',value:'editRow'}); menu.addDivider(); for (var i=0; i < this.partControllers.length; i++) { var ctrl = this.partControllers[i]; menu.addItem({title:ctrl.title,value:ctrl.key}); } this.columnMenu = menu; menu.listen(this); } this.hoveredRow=rowIndex; this.hoveredColumnIndex=columnIndex; this.columnMenu.showAtPointer(e); }, /** @private */ $select$huiEditorColumnMenu : function(value) { if (value=='editSection') { this._editPart(this.hoveredPart); } else if (value=='removeColumn') { this.fire('removeColumn',{'row':this.hoveredRow,'column':this.hoveredColumnIndex}); } else if (value=='editColumn') { this.editColumn(this.hoveredRow,this.hoveredColumnIndex); } else if (value=='addColumn') { this.fire('addColumn',{'row':this.hoveredRow,'position':this.hoveredColumnIndex+1}); } else if (value=='editRow') { this.editRow(this.hoveredRow); } else { this.fire('addPart',{'row':this.hoveredRow,'column':this.hoveredColumnIndex,'position':0,type:value}); } }, ///////////////////// Rows ////////////////////// _editedRow : null, editRow : function(rowIndex) { this.stopRowEditing(); var node = hui.get.byClass(this.element,this.options.rowClass)[rowIndex]; hui.cls.add(node,'hui_editor_row_edit'); this._editedRow = node; var self = this; this.fire('editRow',{'index' : rowIndex, node: node}); }, stopRowEditing : function() { if (this._editedRow) { hui.cls.remove(this._editedRow,'hui_editor_row_edit'); this._editedRow = null; } }, ///////////////////// Columns ////////////////////// _editedColumn : null, editColumn : function(rowIndex,columnIndex) { this.stopColumnEditing(); var row = hui.get.byClass(this.element,this.options.rowClass)[rowIndex]; if (row) { var node = hui.get.byClass(row,this.options.columnClass)[columnIndex]; if (node) { this._editedColumn = node; hui.cls.add(node,'hui_editor_column_edit'); this.fire('editColumn',{'rowIndex' : rowIndex, 'index' : columnIndex, node: node}); } } }, stopColumnEditing : function() { if (this._editedColumn) { hui.cls.remove(this._editedColumn,'hui_editor_column_edit'); this._editedColumn = null; } }, ///////////////////////// Parts ////////////////////////// hoverPart : function(part,event) { if (!this.active || this.activePart || !this.live || this.dragging || this.busy) { return; } this.hoveredPart = part; hui.cls.add(part.element,'hui_editor_part_hover'); var self = this; this.partControlTimer = window.setTimeout(function() {self.showPartControls();},200); }, blurPart : function(e) { window.clearTimeout(this.partControlTimer); if (hui.ui.isWithin(e,this.hoveredPart.element)) { return; } if (!this.active) return; if (this.partControls && !hui.ui.isWithin(e,this.partControls.element)) { this._hidePartControls(); hui.cls.remove(this.hoveredPart.element,'hui_editor_part_hover'); } if (!this.partControls && this.hoveredPart) { hui.cls.remove(this.hoveredPart.element,'hui_editor_part_hover'); } }, showPartEditControls : function() { if (!this.partEditControls) { this.partEditControls = hui.ui.Overlay.create({name:'huiEditorPartEditActions',variant:'light',zIndex:100}); this.partEditControls.element.style.zIndex = '99999999'; this.partEditControls.addIcon('save','common/ok'); this.partEditControls.addIcon('cancel','common/undo'); this.partEditControls.addIcon('info','common/info'); this.partEditControls.listen(this); var self = this; hui.on(window,'resize',function() { hui.onDraw(function() { self._positionEditControls(); }); }); } this._positionEditControls(); }, _positionEditControls : function() { if (this.activePart) { this.partEditControls.showAtElement(this.activePart.element,{'horizontal':'right','vertical':'topOutside'}); } }, showPartControls : function() { if (!this.partControls) { this.partControls = hui.ui.Overlay.create({name:'huiEditorPartActions',variant:'light'}); this.partControls.addIcon('edit','common/edit'); this.partControls.addIcon('new','common/new'); this.partControls.addIcon('delete','common/delete'); hui.listen(this.partControls.getElement(),'mouseout',this._blurControls.bind(this)); hui.listen(this.partControls.getElement(),'mouseover',this._hoverControls.bind(this)); this.partControls.listen(this); } if (this.hoveredPart.column==-1 || true) { this.partControls.hideIcons(['new','delete']); } else { this.partControls.showIcons(['new','delete']); } this.partControls.showAtElement(this.hoveredPart.element,{'horizontal':'right'}); }, _hoverControls : function(e) { hui.cls.add(this.hoveredPart.element,'hui_editor_part_hover'); }, _blurControls : function(e) { hui.cls.remove(this.hoveredPart.element,'hui_editor_part_hover'); if (!hui.ui.isWithin(e,this.hoveredPart.element)) { this._hidePartControls(); } }, /** @private */ $iconWasClicked$huiEditorPartActions : function(key,event) { if (key=='delete') { this.deletePart(this.hoveredPart); } else if (key=='new') { this.newPart(event); } else if (key=='edit') { this._editPart(this.hoveredPart); } }, /** @private */ $iconWasClicked$huiEditorPartEditActions : function(key,event) { if (key=='cancel') { this.cancelPart(this.activePart); } else if (key=='save') { this.savePart(this.activePart); } else if (key=='info') { this.fire('toggleInfo'); } }, _hidePartControls : function() { if (this.partControls) { this.partControls.hide(); } }, _hidePartEditControls : function() { if (this.partEditControls) { this.partEditControls.hide(); } }, _editPart : function(part) { if (!this.active || this.activePart) return; if (this.activePart) { this._deactivatePart(this.activePart); } if (this.hoveredPart) { hui.cls.remove(this.hoveredPart.element,'hui_editor_part_hover'); } this.activePart = part; this.showPartEditControls(); hui.cls.add(part.element,'hui_editor_part_active'); hui.ui.msg({text:{en:'Loading...',da:'Indlæser...'},delay:300,busy:true}); part.activate(function() { hui.ui.hideMessage(); }); window.clearTimeout(this.partControlTimer); this._hidePartControls(); if (this.hoveredColumn) { hui.cls.remove(this.hoveredColumn,'hui_editor_column_hover'); } this.fire('editPart',part); }, cancelPart : function(part) { part.cancel(); this._deactivatePart(this.activePart); this.activePart = null; this.fire('cancelPart',part); }, savePart : function(part) { this.busy = true; hui.ui.msg({text:{en:'Saving...',da:'Gemmer...'},delay:300,busy:true}); part.save({ callback : function() { hui.ui.hideMessage(); this.activePart = null; this.busy = false; this._deactivatePart(part); this.partChanged(part); this.fire('savePart',part); }.bind(this) }); }, getEditorForElement : function(element) { for (var i=0; i < this.parts.length; i++) { if (this.parts[i].element==element) { return this.parts[i]; } } return null; }, _deactivatePart : function(part) { part.deactivate(function() { this.partDidDeactivate(part); this.fire('deactivatePart',part); }.bind(this)); }, partDidDeactivate : function(part) { hui.cls.remove(part.element,'hui_editor_part_active'); this.activePart = null; this._hidePartEditControls(); }, partChanged : function(part) { hui.ui.callDelegates(part,'partChanged'); }, deletePart : function(part) { hui.ui.callDelegates(part,'deletePart'); this.partControls.hide(); }, newPart : function(e) { if (!this.newPartMenu) { var menu = hui.ui.Menu.create({name:'huiEditorNewPartMenu'}); hui.each(this.partControllers,function(item) { menu.addItem({title:item.title,value:item.key}); }); menu.listen(this); this.newPartMenu=menu; } this.newPartMenu.showAtPointer(e); }, $itemWasClicked$huiEditorNewPartMenu : function(value) { var info = {row:this.hoveredPart.row,column:this.hoveredPart.column,position:this.hoveredPart.position+1,type:value}; hui.ui.callDelegates(this,'addPart',info); }, /**** Dragging ****/ _dragInfo : null, _dropInfo : null, dragProxy : null, _startPartDrag : function(info) { if (!this.active || this.activePart || !this.live) { return true; } var e = hui.event(info.event), element = info.element; if (!e.altKey) { return; } e.stop(); if (!this.dragProxy) { this.dragProxy = hui.build('div',{'class':'hui_editor_dragproxy',parent:document.body,style:'display:none;'}); } var proxy = this.dragProxy; proxy.innerHTML = element.innerHTML; var pos = this._getPartPosition(element); if (!pos) { return; } this._dragInfo = { diffLeft : e.getLeft() - hui.position.getLeft(element), diffTop : e.getTop() - hui.position.getTop(element), draggedElement : element, partIndex : pos.partIndex, rowIndex : pos.rowIndex, columnIndex : pos.columnIndex, initialHeight : element.clientHeight }; hui.log('startDrag'); hui.drag.start({ element : proxy, onBeforeMove : this._onBeforeDrag.bind(this), onMove : this._onDrag.bind(this), onAfterMove : this._onAfterDrag.bind(this), onEnd : function() { } },e); }, _onBeforeDrag : function() { var dragged = this._dragInfo.draggedElement, proxy = this.dragProxy; this._insertDropPlaceholders(); hui.style.set(proxy,{ display : 'block', visibility : 'visible', height : dragged.clientHeight+'px', width : dragged.clientWidth+'px', transform : 'scale(1)', background : 'rgba(255,255,255,.5)', padding : '1px', opacity: 1 }); //hui.animate({node:this.dragProxy,css:{transform:'scale(1.1)'},duration:1000,ease:hui.ease.slowFastSlow}); hui.style.setOpacity(dragged,0.5); this._dragging = true; if (!this._dropMarker) { this._dropMarker = hui.build('div',{'class':'hui_editor_dropmarker',parent:document.body}); } }, _onDrag : function(e) { var left = e.getLeft(); var top = e.getTop(); this.dragProxy.style.left = (left - this._dragInfo.diffLeft) + 'px'; this.dragProxy.style.top = (top - this._dragInfo.diffTop) + 'px'; for (var i=0; i < this.dropTargets.length; i++) { var info = this.dropTargets[i]; if (info.left<left && info.right>left && info.top<top && info.bottom>top) { //if (info.placeholder!=this._activeDragPlaceholder) { var h = this._dragColumnHeights[info.rowIndex+'-'+info.columnIndex]; //hui.log(info.columnIndex+': '+h) //info.debug.style.borderColor='blue' //hui.animate({node:info.placeholder,css:{height : h+'px'},duration:500,ease:hui.ease.slowFastSlow}); if (this._latestProxyColumn!=info.columnIndex || this._latestProxyRow!=info.rowIndex) { hui.animate({node:this.dragProxy,css:{width:(info.right-info.left)+'px'},duration:300,ease:hui.ease.fastSlow}); this._latestProxyColumn = info.columnIndex; this._latestProxyrow = info.rowIndex; } //this._activeDragPlaceholder = info.placeholder; this._dropInfo = info; hui.style.set(this._dropMarker,{width:(info.right-info.left)+'px',left:info.left+'px',top:info.position+'px',display:'block'}); //} break; } } }, _onAfterDrag : function(e) { var proxy = this.dragProxy, dragInfo = this._dragInfo, dragged = this._dragInfo.draggedElement, dropInfo = this._dropInfo; if (dropInfo) { var newHeight = this._dragColumnHeights[dropInfo.rowIndex+'-'+dropInfo.columnIndex]; var top = dropInfo.position, left = dropInfo.left; if ((dragInfo.columnIndex == dropInfo.columnIndex && dragInfo.partIndex < dropInfo.partIndex) || dragInfo.rowIndex < dropInfo.rowIndex) { top = top - dragInfo.initialHeight; } //top+=3; left++; // Move the proxy to new position hui.animate({ node : proxy, css : { left : left+'px', top : top+'px', opacity : 0.5 }, duration : 500, ease : hui.ease.slowFastSlow }); var column = this._getColumn(dropInfo.rowIndex,dropInfo.columnIndex); var parts = hui.get.byClass(column,this.options.partClass); if (parts[dropInfo.partIndex] != dragged) { var partIndex = dropInfo.partIndex; if (dragInfo.columnIndex == dropInfo.columnIndex && dragInfo.partIndex < dropInfo.partIndex) { partIndex--; } this.fire('partWasMoved',{dragged:dragged,rowIndex : dropInfo.rowIndex,columnIndex : dropInfo.columnIndex,partIndex : partIndex, $success : function() { dragged.style.webkitTransformOrigin='0 0'; var dummy = hui.build('div'); if (dropInfo.partIndex>=parts.length) { hui.animate({node:dragged,css:{height:'0px'},duration:500,ease:hui.ease.slowFastSlow,onComplete:function() { hui.dom.remove(dragged); column.appendChild(dragged); hui.animate({node:dragged,css:{transform:'scale(1)',height:newHeight+'px'},duration:500,ease:hui.ease.slowFastSlow,onComplete:function() { dragged.style.height=''; }}); }}); } else { hui.dom.insertBefore(parts[dropInfo.partIndex],dummy); hui.animate({node:dummy,css:{height:newHeight+'px'},duration:600,ease:hui.ease.slowFastSlow,onComplete:function() { hui.dom.remove(dragged); hui.dom.replaceNode(dummy,dragged); hui.style.set(dragged,{transform:'scale(1)',opacity:0,height:''}); hui.animate({node:dragged,css:{opacity:1},duration:500,ease:hui.ease.slowFastSlow}); }}); hui.animate({node:dragged,css:{transform:'scale(0)',height:'0px'},duration:500,ease:hui.ease.slowFastSlow}); } this._cleanDrag(); }.bind(this), $failure : function() { this._cleanDrag(); }.bind(this) }); } else { this._cleanDrag(); } } }, _cleanDrag : function() { var proxy = this.dragProxy; hui.animate({node:proxy,css:{opacity:0},duration:500,delay:500,ease:hui.ease.slowFastSlow,onComplete:function() { proxy.style.display='none'; } }); hui.style.setOpacity(this._dragInfo.draggedElement,1); var p = hui.get.byClass(document.body,'hui_editor_drop_placeholder'); for (var i=0; i < p.length; i++) { hui.dom.remove(p[i]); } this.dropTargets = []; this._dragging = false; if (this._dropMarker) { this._dropMarker.style.display='none'; } }, _getColumn : function(rowIndex,columnIndex) { var rows = hui.get.byClass(document.body,this.options.rowClass); var row = rows[rowIndex]; var columns = hui.get.byClass(row,this.options.columnClass); return columns[columnIndex]; }, _getPartPosition : function(element) { var rows = hui.get.byClass(document.body,this.options.rowClass); for (var i=0; i < rows.length; i++) { var columns = hui.get.byClass(rows[i],this.options.columnClass); for (var j=0; j < columns.length; j++) { var parts = hui.get.byClass(columns[j],this.options.partClass); for (var k=0; k < parts.length; k++) { if (element===parts[k]) { return {rowIndex:i,columnIndex:j,partIndex:k}; } } } } return null; }, _activeDragPlaceholder : null, _dragColumnHeights : null, _insertDropPlaceholders : function() { var infos = this.dropTargets = []; var colHeights = this._dragColumnHeights = {}; var proxy = this.dragProxy; var draggedPart = this._dragInfo.draggedElement; var rows = hui.get.byClass(document.body,this.options.rowClass); for (var i=0; i < rows.length; i++) { var row = rows[i]; var columns = hui.get.byClass(row,this.options.columnClass); for (var j=0; j < columns.length; j++) { var column = columns[j]; hui.style.set(proxy,{ width : column.clientWidth+'px', height : '', visibility : 'hidden', display : 'block' }); var height = colHeights[i+'-'+j] = proxy.clientHeight; var parts = hui.get.byClass(column,this.options.partClass); var min = hui.position.getTop(column); var max = min+column.clientHeight; var current = min; var k = 0; var previous = null; var left, right, top; var part; for (; k < parts.length; k++) { part = parts[k]; var next = parts[k+1]; previous = parts[k-1]; left = hui.position.getLeft(part); right = left + part.clientWidth; top = previous ? hui.position.getTop(previous)+previous.clientHeight/2 : min; var bottom = hui.position.getTop(part)+part.clientHeight/2; var info = { rowIndex : i, columnIndex : j, partIndex : k, part : part, left : left, right : right, top : top, bottom : bottom, position : hui.position.getTop(part) }; current += part.clientHeight; infos.push(info); previous = part; } var last = parts.length>0 ? parts[parts.length-1] : null; var position; if (last) { top = hui.position.getTop(last)+last.clientHeight/2; position = hui.position.getTop(last)+last.clientHeight; } else { top = min; position = min; left = hui.position.getLeft(column); right = left + column.clientWidth; } if (part) { current += part.clientHeight; } infos.push({ rowIndex : i, columnIndex : j, partIndex : k, // TODO part : part, left : left, right : right, top : top, position : position, bottom : max-top > 20 ? max : top+20 }); } } } }; hui.ui.Editor.getPartId = function(element) { var data = hui.string.fromJSON(element.getAttribute('data')); return data ? data.id : null; }; ////////////////////////////////// Header editor //////////////////////////////// /** * @constructor */ hui.ui.Editor.Header = function(options) { this.element = hui.get(options.element); this.id = hui.ui.Editor.getPartId(this.element); this.header = hui.get.firstByTag(this.element,'*'); this.field = null; }; hui.ui.Editor.Header.prototype = { activate : function(callback) { this.value = this.header.innerHTML; this.field = hui.build('textarea',{className:'hui_editor_header'}); this.field.value = this.value; this.header.style.visibility='hidden'; this.updateFieldStyle(); this.element.insertBefore(this.field,this.header); this.field.focus(); this.field.select(); hui.listen(this.field,'keydown',function(e) { if (e.keyCode==Event.KEY_RETURN) { this.save(); } }.bind(this)); callback(); }, save : function(options) { var value = this.field.value; this.header.innerHTML = value; if (value!=this.value) { this.value = value; } options.callback(); }, cancel : function() { }, deactivate : function(callback) { this.header.style.visibility=''; this.element.removeChild(this.field); callback(); }, updateFieldStyle : function() { hui.style.set(this.field,{width:this.header.clientWidth+'px',height:this.header.clientHeight+'px'}); hui.style.copy(this.header,this.field,['font-size','line-height','margin-top','font-weight','font-family','text-align','color','font-style']); }, getValue : function() { return this.value; } }; ////////////////////////////////// Html editor //////////////////////////////// /** * @constructor */ hui.ui.Editor.Html = function(options) { this.element = hui.get(options.element); this.id = hui.ui.Editor.getPartId(this.element); this.field = null; }; hui.ui.Editor.Html.prototype = { activate : function(callback) { this.value = this.element.innerHTML; this.element.innerHTML=''; var style = this.buildStyle(); this.editor = hui.ui.MarkupEditor.create({autoHideToolbar:false,style:style}); this.element.appendChild(this.editor.getElement()); this.editor.listen(this); this.editor.setValue(this.value); this.editor.focus(); callback(); }, buildStyle : function() { return { 'textAlign' : hui.style.get(this.element,'text-align'), 'fontFamily' : hui.style.get(this.element,'font-family'), 'fontSize' : hui.style.get(this.element,'font-size'), 'fontWeight' : hui.style.get(this.element,'font-weight'), 'color' : hui.style.get(this.element,'color') }; }, cancel : function() { this.element.innerHTML = this.value; }, save : function(options) { var value = this.editor.getValue(); if (value!=this.value) { this.value = value; } this.element.innerHTML = this.value; options.callback(); }, deactivate : function(callback) { if (this.editor) { this.editor.destroy(); this.element.innerHTML = this.value; } callback(); }, getValue : function() { return this.value; } };