2015-02-19 22:14:55 +01:00
angular . module ( "LayoutEditor" , [ "ngSanitize" , "ngResource" , "ui.sortable" ] ) ;
var LayoutEditor ;
( function ( LayoutEditor ) {
var Clipboard = function ( ) {
var self = this ;
this . clipboardData = { } ;
this . setData = function ( contentType , data , realClipBoard ) {
self . clipboardData [ contentType ] = data ;
} ;
this . getData = function ( contentType , realClipBoard ) {
return self . clipboardData [ contentType ] ;
} ;
this . disable = function ( ) {
this . disabled = true ;
} ;
}
LayoutEditor . Clipboard = new Clipboard ( ) ;
angular
. module ( "LayoutEditor" )
. factory ( "clipboard" , function ( ) {
return {
setData : LayoutEditor . Clipboard . setData ,
getData : LayoutEditor . Clipboard . getData ,
disable : LayoutEditor . Clipboard . disable
} ;
} ) ;
} ) ( LayoutEditor || ( LayoutEditor = { } ) ) ;
angular
. module ( "LayoutEditor" )
. factory ( "scopeConfigurator" , function ( $timeout , clipboard ) {
return {
configureForElement : function ( $scope , $element ) {
$element . find ( ".layout-panel" ) . click ( function ( e ) {
e . stopPropagation ( ) ;
} ) ;
$element . parent ( ) . keydown ( function ( e ) {
var handled = false ;
var resetFocus = false ;
var element = $scope . element ;
if ( element . editor . isDragging || element . editor . inlineEditingIsActive )
return ;
// If the "real" clipboard works, then the pseudo-clipboard will have been disabled.
if ( ! clipboard . disabled ) {
var focusedElement = element . editor . focusedElement ;
if ( ! ! focusedElement ) {
// Pseudo clipboard handling for browsers not allowing real clipboard operations.
if ( e . ctrlKey ) {
switch ( e . which ) {
case 67 : // C
focusedElement . copy ( clipboard ) ;
break ;
case 88 : // X
focusedElement . cut ( clipboard ) ;
break ;
case 86 : // V
focusedElement . paste ( clipboard ) ;
break ;
}
}
}
}
if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 46 ) { // Del
$scope . delete ( element ) ;
handled = true ;
} else if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 32 ) { // Space
$element . find ( ".layout-panel-action-properties" ) . first ( ) . click ( ) ;
handled = true ;
}
if ( element . type == "Content" ) { // This is a content element.
if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 13 ) { // Enter
$element . find ( ".layout-panel-action-edit" ) . first ( ) . click ( ) ;
handled = true ;
}
}
if ( ! ! element . children ) { // This is a container.
if ( ! e . ctrlKey && ! e . shiftKey && e . altKey && e . which == 40 ) { // Alt+Down
if ( element . children . length > 0 )
element . children [ 0 ] . setIsFocused ( ) ;
handled = true ;
}
if ( element . type == "Column" ) { // This is a column.
var connectAdjacent = ! e . ctrlKey ;
if ( e . which == 37 ) { // Left
if ( e . altKey )
element . expandLeft ( connectAdjacent ) ;
if ( e . shiftKey )
element . contractRight ( connectAdjacent ) ;
handled = true ;
} else if ( e . which == 39 ) { // Right
if ( e . altKey )
element . contractLeft ( connectAdjacent ) ;
if ( e . shiftKey )
element . expandRight ( connectAdjacent ) ;
handled = true ;
}
}
}
if ( ! ! element . parent ) { // This is a child.
if ( e . altKey && e . which == 38 ) { // Alt+Up
element . parent . setIsFocused ( ) ;
handled = true ;
}
if ( element . parent . type == "Row" ) { // Parent is a horizontal container.
if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 37 ) { // Left
element . parent . moveFocusPrevChild ( element ) ;
handled = true ;
}
else if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 39 ) { // Right
element . parent . moveFocusNextChild ( element ) ;
handled = true ;
}
else if ( e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 37 ) { // Ctrl+Left
element . moveUp ( ) ;
resetFocus = true ;
handled = true ;
}
else if ( e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 39 ) { // Ctrl+Right
element . moveDown ( ) ;
handled = true ;
}
}
else { // Parent is a vertical container.
if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 38 ) { // Up
element . parent . moveFocusPrevChild ( element ) ;
handled = true ;
}
else if ( ! e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 40 ) { // Down
element . parent . moveFocusNextChild ( element ) ;
handled = true ;
}
else if ( e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 38 ) { // Ctrl+Up
element . moveUp ( ) ;
resetFocus = true ;
handled = true ;
}
else if ( e . ctrlKey && ! e . shiftKey && ! e . altKey && e . which == 40 ) { // Ctrl+Down
element . moveDown ( ) ;
handled = true ;
}
}
}
if ( handled ) {
e . preventDefault ( ) ;
}
e . stopPropagation ( ) ;
$scope . $apply ( ) ; // Event is not triggered by Angular directive but raw event handler on element.
// HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost.
if ( resetFocus ) {
window . setTimeout ( function ( ) {
$scope . $apply ( function ( ) {
element . editor . focusedElement . setIsFocused ( ) ;
} ) ;
} , 100 ) ;
}
} ) ;
$scope . element . setIsFocusedEventHandlers . push ( function ( ) {
$element . parent ( ) . focus ( ) ;
} ) ;
$scope . delete = function ( element ) {
element . delete ( ) ;
}
} ,
configureForContainer : function ( $scope , $element ) {
var element = $scope . element ;
//$scope.isReceiving = false; // True when container is receiving an external element via drag/drop.
$scope . getShowChildrenPlaceholder = function ( ) {
return $scope . element . children . length === 0 && ! $scope . element . getIsDropTarget ( ) ;
} ;
$scope . sortableOptions = {
cursor : "move" ,
delay : 150 ,
disabled : element . getIsSealed ( ) ,
distance : 5 ,
//handle: element.children.length < 2 ? ".imaginary-class" : false, // For some reason doesn't get re-evaluated after adding more children.
start : function ( e , ui ) {
$scope . $apply ( function ( ) {
element . setIsDropTarget ( true ) ;
element . editor . isDragging = true ;
} ) ;
// Make the drop target placeholder as high as the item being dragged.
ui . placeholder . height ( ui . item . height ( ) - 4 ) ;
ui . placeholder . css ( "min-height" , 0 ) ;
} ,
stop : function ( e , ui ) {
$scope . $apply ( function ( ) {
element . editor . isDragging = false ;
element . setIsDropTarget ( false ) ;
} ) ;
} ,
over : function ( e , ui ) {
if ( ! ! ui . sender && ! ! ui . sender [ 0 ] . isToolbox ) {
if ( ! ! ui . sender [ 0 ] . dropTargetTimeout ) {
$timeout . cancel ( ui . sender [ 0 ] . dropTargetTimeout ) ;
ui . sender [ 0 ] . dropTargetTimeout = null ;
}
$timeout ( function ( ) {
if ( element . type == "Row" ) {
// If there was a previous drop target and it was a row, roll back any pending column adds to it.
var previousDropTarget = element . editor . dropTargetElement ;
if ( ! ! previousDropTarget && previousDropTarget . type == "Row" )
previousDropTarget . rollbackAddColumn ( ) ;
}
element . setIsDropTarget ( false ) ;
} ) ;
ui . sender [ 0 ] . dropTargetTimeout = $timeout ( function ( ) {
if ( element . type == "Row" ) {
var receivedColumn = ui . item . sortable . model ;
var receivedColumnWidth = Math . floor ( 12 / ( element . children . length + 1 ) ) ;
receivedColumn . width = receivedColumnWidth ;
receivedColumn . offset = 0 ;
element . beginAddColumn ( receivedColumnWidth ) ;
// Make the drop target placeholder the correct width and as high as the highest existing column in the row.
var maxHeight = _ . max ( _ ( $element . find ( "> .layout-children > .layout-column:not(.ui-sortable-placeholder)" ) ) . map ( function ( e ) {
return $ ( e ) . height ( ) ;
} ) ) ;
for ( i = 1 ; i <= 12 ; i ++ )
ui . placeholder . removeClass ( "col-xs-" + i ) ;
ui . placeholder . addClass ( "col-xs-" + receivedColumn . width ) ;
if ( maxHeight > 0 ) {
ui . placeholder . height ( maxHeight ) ;
ui . placeholder . css ( "min-height" , 0 ) ;
}
else {
ui . placeholder . height ( 0 ) ;
ui . placeholder . css ( "min-height" , "" ) ;
}
}
element . setIsDropTarget ( true ) ;
} , 150 ) ;
}
} ,
receive : function ( e , ui ) {
if ( ! ! ui . sender && ! ! ui . sender [ 0 ] . isToolbox ) {
$scope . $apply ( function ( ) {
var receivedElement = ui . item . sortable . model ;
if ( ! ! receivedElement ) {
if ( element . type == "Row" )
element . commitAddColumn ( ) ;
// Should ideally call LayoutEditor.Container.addChild() instead, but since this handler
// is run *before* the ui-sortable directive's handler, if we try to add the child to the
// array that handler will get an exception when trying to do the same.
receivedElement . setEditor ( element . editor ) ;
receivedElement . parent = element ;
if ( receivedElement . type == "Content" && ! ! receivedElement . hasEditor ) {
$scope . $root . editElement ( receivedElement ) . then ( function ( args ) {
if ( ! args . cancel ) {
receivedElement . data = decodeURIComponent ( args . element . data ) ;
2015-02-23 16:01:54 +01:00
receivedElement . setHtml ( decodeURIComponent ( args . element . html . replace ( /\+/g , "%20" ) ) ) ;
2015-02-19 22:14:55 +01:00
}
$timeout ( function ( ) {
if ( ! ! args . cancel )
receivedElement . delete ( ) ;
else
receivedElement . setIsFocused ( ) ;
//$scope.isReceiving = false;
element . setIsDropTarget ( false ) ;
} ) ;
return ;
} ) ;
}
}
$timeout ( function ( ) {
//$scope.isReceiving = false;
element . setIsDropTarget ( false ) ;
if ( ! ! receivedElement )
receivedElement . setIsFocused ( ) ;
} ) ;
} ) ;
}
}
} ;
$scope . click = function ( child , e ) {
if ( ! child . editor . isDragging )
child . setIsFocused ( ) ;
e . stopPropagation ( ) ;
} ;
$scope . getClasses = function ( child ) {
var result = [ "layout-element" ] ;
if ( ! ! child . children ) {
result . push ( "layout-container" ) ;
if ( child . getIsSealed ( ) )
result . push ( "layout-container-sealed" ) ;
}
result . push ( "layout-" + child . type . toLowerCase ( ) ) ;
if ( ! ! child . dropTargetClass )
result . push ( child . dropTargetClass ) ;
// TODO: Move these to either the Column directive or the Column model class.
if ( child . type == "Row" ) {
result . push ( "row" ) ;
if ( ! child . canAddColumn ( ) )
result . push ( "layout-row-full" ) ;
}
if ( child . type == "Column" ) {
result . push ( "col-xs-" + child . width ) ;
result . push ( "col-xs-offset-" + child . offset ) ;
}
if ( child . type == "Content" )
result . push ( "layout-content-" + child . contentTypeClass ) ;
if ( child . getIsActive ( ) )
result . push ( "layout-element-active" ) ;
if ( child . getIsFocused ( ) )
result . push ( "layout-element-focused" ) ;
if ( child . getIsSelected ( ) )
result . push ( "layout-element-selected" ) ;
if ( child . getIsDropTarget ( ) )
result . push ( "layout-element-droptarget" ) ;
if ( child . isTemplated )
result . push ( "layout-element-templated" ) ;
return result ;
} ;
}
}
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutEditor" , function ( environment ) {
return {
restrict : "E" ,
scope : { } ,
controller : function ( $scope , $element , $attrs , $compile , clipboard ) {
if ( ! ! $attrs . model )
$scope . element = eval ( $attrs . model ) ;
else
throw new Error ( "The 'model' attribute must evaluate to a LayoutEditor.Editor object." ) ;
$scope . click = function ( canvas , e ) {
if ( ! canvas . editor . isDragging )
canvas . setIsFocused ( ) ;
e . stopPropagation ( ) ;
} ;
$scope . getClasses = function ( canvas ) {
var result = [ "layout-element" , "layout-container" , "layout-canvas" ] ;
if ( canvas . getIsActive ( ) )
result . push ( "layout-element-active" ) ;
if ( canvas . getIsFocused ( ) )
result . push ( "layout-element-focused" ) ;
if ( canvas . getIsSelected ( ) )
result . push ( "layout-element-selected" ) ;
if ( canvas . getIsDropTarget ( ) )
result . push ( "layout-element-droptarget" ) ;
if ( canvas . isTemplated )
result . push ( "layout-element-templated" ) ;
return result ;
} ;
// An unfortunate side-effect of the next hack on line 54 is that the created elements aren't added to the DOM yet, so we can't use it to get to the parent ".layout-desiger" element.
// Work around: access that element directly (which efectively turns multiple layout editors on a single page impossible).
// //var layoutDesignerHost = $element.closest(".layout-designer").data("layout-designer-host");
var layoutDesignerHost = $ ( ".layout-designer" ) . data ( "layout-designer-host" ) ;
$scope . $root . layoutDesignerHost = layoutDesignerHost ;
layoutDesignerHost . element . on ( "replacecanvas" , function ( e , args ) {
var editor = $scope . element ;
var canvasData = {
data : args . canvas . data ,
htmlId : args . canvas . htmlId ,
htmlClass : args . canvas . htmlClass ,
htmlStyle : args . canvas . htmlStyle ,
isTemplated : args . canvas . isTemplated ,
children : args . canvas . children
} ;
// HACK: Instead of simply updating the $scope.element with a new instance, we need to replace the entire orc-layout-editor markup
// in order for angular to rebind starting with the Canvas element. Otherwise, for some reason, it will rebind starting with the first child of Canvas.
// You can see this happening when setting a breakpoint in ScopeConfigurator where containers are initialized with drag & drop: on page load, the first element
// is a Canvas (good), but after having selected another template, the first element is (typically) a Grid (bad).
// Simply recompiling the orc-layout-editor directive will cause the entire thing to be generated, which works just fine as well (even though not is nice as simply leveraging model binding).
layoutDesignerHost . editor = window . layoutEditor = new LayoutEditor . Editor ( editor . config , canvasData ) ;
var template = "<orc-layout-editor" + " model='window.layoutEditor' />" ;
var html = $compile ( template ) ( $scope ) ;
$ ( ".layout-editor-holder" ) . html ( html ) ;
} ) ;
$scope . $root . editElement = function ( element ) {
var host = $scope . $root . layoutDesignerHost ;
return host . editElement ( element ) ;
} ;
$scope . $root . addElement = function ( contentType ) {
var host = $scope . $root . layoutDesignerHost ;
return host . addElement ( contentType ) ;
} ;
$scope . toggleInlineEditing = function ( ) {
if ( ! $scope . element . inlineEditingIsActive ) {
$scope . element . inlineEditingIsActive = true ;
$element . find ( ".layout-toolbar-container" ) . show ( ) ;
var selector = "#layout-editor-" + $scope . $id + " .layout-content-h-t-m-l .layout-content-markup[data-templated=false]" ;
var firstContentEditorId = $ ( selector ) . first ( ) . attr ( "id" ) ;
tinymce . init ( {
selector : selector ,
theme : "modern" ,
schema : "html5" ,
plugins : [
"advlist autolink lists link image charmap print preview hr anchor pagebreak" ,
"searchreplace wordcount visualblocks visualchars code fullscreen" ,
"insertdatetime media nonbreaking table contextmenu directionality" ,
"emoticons template paste textcolor colorpicker textpattern" ,
"fullscreen autoresize"
] ,
toolbar : "undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close" ,
convert _urls : false ,
valid _elements : "*[*]" ,
// Shouldn't be needed due to the valid_elements setting, but TinyMCE would strip script.src without it.
extended _valid _elements : "script[type|defer|src|language]" ,
statusbar : false ,
skin : "orchardlightgray" ,
inline : true ,
fixed _toolbar _container : "#layout-editor-" + $scope . $id + " .layout-toolbar-container" ,
init _instance _callback : function ( editor ) {
if ( editor . id == firstContentEditorId )
tinymce . execCommand ( "mceFocus" , false , editor . id ) ;
}
} ) ;
}
else {
tinymce . remove ( "#layout-editor-" + $scope . $id + " .layout-content-markup" ) ;
$element . find ( ".layout-toolbar-container" ) . hide ( ) ;
$scope . element . inlineEditingIsActive = false ;
}
} ;
$ ( document ) . on ( "cut copy paste" , function ( e ) {
// The real clipboard is supported, so disable the peudo clipboard.
clipboard . disable ( ) ;
var focusedElement = $scope . element . focusedElement ;
if ( ! ! focusedElement ) {
$scope . $apply ( function ( ) {
switch ( e . type ) {
case "copy" :
focusedElement . copy ( e . originalEvent . clipboardData ) ;
break ;
case "cut" :
focusedElement . cut ( e . originalEvent . clipboardData ) ;
break ;
case "paste" :
focusedElement . paste ( e . originalEvent . clipboardData ) ;
break ;
}
} ) ;
// HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost.
window . setTimeout ( function ( ) {
$scope . $apply ( function ( ) {
if ( ! ! $scope . element . focusedElement )
$scope . element . focusedElement . setIsFocused ( ) ;
} ) ;
} , 100 ) ;
e . preventDefault ( ) ;
}
} ) ;
} ,
templateUrl : environment . templateUrl ( "Editor" ) ,
replace : true ,
link : function ( scope , element ) {
// No clicks should propagate from the TinyMCE toolbars.
element . find ( ".layout-toolbar-container" ) . click ( function ( e ) {
e . stopPropagation ( ) ;
} ) ;
// Intercept mousedown on editor while in inline editing mode to
// prevent current editor from losing focus.
element . mousedown ( function ( e ) {
if ( scope . element . inlineEditingIsActive ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
}
} )
// Unfocus and unselect everything on click outside of canvas.
$ ( window ) . click ( function ( e ) {
// Except when in inline editing mode.
if ( ! scope . element . inlineEditingIsActive ) {
scope . $apply ( function ( ) {
scope . element . activeElement = null ;
scope . element . focusedElement = null ;
} ) ;
}
} ) ;
}
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutCanvas" , function ( scopeConfigurator , environment ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
controller : function ( $scope , $element , $attrs ) {
scopeConfigurator . configureForElement ( $scope , $element ) ;
scopeConfigurator . configureForContainer ( $scope , $element ) ;
$scope . sortableOptions [ "axis" ] = "y" ;
} ,
templateUrl : environment . templateUrl ( "Canvas" ) ,
replace : true
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutChild" , function ( $compile ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
link : function ( scope , element ) {
var template = "<orc-layout-" + scope . element . type . toLowerCase ( ) + " element='element' />" ;
var html = $compile ( template ) ( scope ) ;
$ ( element ) . replaceWith ( html ) ;
}
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutColumn" , function ( $compile , scopeConfigurator , environment ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
controller : function ( $scope , $element ) {
scopeConfigurator . configureForElement ( $scope , $element ) ;
scopeConfigurator . configureForContainer ( $scope , $element ) ;
$scope . sortableOptions [ "axis" ] = "y" ;
} ,
templateUrl : environment . templateUrl ( "Column" ) ,
replace : true ,
link : function ( scope , element , attrs ) {
element . find ( ".layout-column-resize-bar" ) . draggable ( {
axis : "x" ,
helper : "clone" ,
revert : true ,
start : function ( e , ui ) {
scope . $apply ( function ( ) {
scope . element . editor . isResizing = true ;
} ) ;
} ,
drag : function ( e , ui ) {
var columnElement = element . parent ( ) ;
var columnSize = columnElement . width ( ) / scope . element . width ;
var connectAdjacent = ! e . ctrlKey ;
if ( $ ( e . target ) . hasClass ( "layout-column-resize-bar-left" ) ) {
var delta = ui . offset . left - columnElement . offset ( ) . left ;
if ( delta < - columnSize && scope . element . canExpandLeft ( connectAdjacent ) ) {
scope . $apply ( function ( ) {
scope . element . expandLeft ( connectAdjacent ) ;
} ) ;
}
else if ( delta > columnSize && scope . element . canContractLeft ( connectAdjacent ) ) {
scope . $apply ( function ( ) {
scope . element . contractLeft ( connectAdjacent ) ;
} ) ;
}
}
else if ( $ ( e . target ) . hasClass ( "layout-column-resize-bar-right" ) ) {
var delta = ui . offset . left - columnElement . width ( ) - columnElement . offset ( ) . left ;
if ( delta > columnSize && scope . element . canExpandRight ( connectAdjacent ) ) {
scope . $apply ( function ( ) {
scope . element . expandRight ( connectAdjacent ) ;
} ) ;
}
else if ( delta < - columnSize && scope . element . canContractRight ( connectAdjacent ) ) {
scope . $apply ( function ( ) {
scope . element . contractRight ( connectAdjacent ) ;
} ) ;
}
}
} ,
stop : function ( e , ui ) {
scope . $apply ( function ( ) {
scope . element . editor . isResizing = false ;
} ) ;
}
} ) ;
}
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutContent" , function ( $sce , scopeConfigurator , environment ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
controller : function ( $scope , $element ) {
scopeConfigurator . configureForElement ( $scope , $element ) ;
$scope . edit = function ( ) {
$scope . $root . editElement ( $scope . element ) . then ( function ( args ) {
$scope . $apply ( function ( ) {
if ( args . cancel )
return ;
$scope . element . data = decodeURIComponent ( args . element . data ) ;
$scope . element . setHtml ( decodeURIComponent ( args . element . html . replace ( /\+/g , "%20" ) ) ) ;
} ) ;
} ) ;
} ;
$scope . updateContent = function ( e ) {
$scope . element . setHtml ( e . target . innerHTML ) ;
} ;
// Overwrite the setHtml function so that we can use the $sce service to trust the html (and not have the html binding strip certain tags).
$scope . element . setHtml = function ( html ) {
$scope . element . html = html ;
$scope . element . htmlUnsafe = $sce . trustAsHtml ( html ) ;
} ;
$scope . element . setHtml ( $scope . element . html ) ;
} ,
templateUrl : environment . templateUrl ( "Content" ) ,
replace : true ,
link : function ( scope , element ) {
// Mouse down events must not be intercepted by drag and drop while inline editing is active,
// otherwise clicks in inline editors will have no effect.
element . find ( ".layout-content-markup" ) . mousedown ( function ( e ) {
if ( scope . element . editor . inlineEditingIsActive ) {
e . stopPropagation ( ) ;
}
} ) ;
}
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutGrid" , function ( $compile , scopeConfigurator , environment ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
controller : function ( $scope , $element ) {
scopeConfigurator . configureForElement ( $scope , $element ) ;
scopeConfigurator . configureForContainer ( $scope , $element ) ;
$scope . sortableOptions [ "axis" ] = "y" ;
} ,
templateUrl : environment . templateUrl ( "Grid" ) ,
replace : true
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutRow" , function ( $compile , scopeConfigurator , environment ) {
return {
restrict : "E" ,
scope : { element : "=" } ,
controller : function ( $scope , $element ) {
scopeConfigurator . configureForElement ( $scope , $element ) ;
scopeConfigurator . configureForContainer ( $scope , $element ) ;
$scope . sortableOptions [ "axis" ] = "x" ;
$scope . sortableOptions [ "ui-floating" ] = true ;
} ,
templateUrl : environment . templateUrl ( "Row" ) ,
replace : true
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutPopup" , function ( ) {
return {
restrict : "A" ,
link : function ( scope , element , attrs ) {
var popup = $ ( element ) ;
var trigger = popup . closest ( ".layout-popup-trigger" ) ;
var parentElement = popup . closest ( ".layout-element" ) ;
trigger . click ( function ( ) {
popup . toggle ( ) ;
if ( popup . is ( ":visible" ) ) {
popup . position ( {
my : attrs . orcLayoutPopupMy || "left top" ,
at : attrs . orcLayoutPopupAt || "left bottom+4px" ,
of : trigger
} ) ;
}
} ) ;
popup . click ( function ( e ) {
e . stopPropagation ( ) ;
} ) ;
parentElement . click ( function ( e ) {
popup . hide ( ) ;
} ) ;
}
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutToolbox" , function ( $compile , environment ) {
return {
restrict : "E" ,
controller : function ( $scope , $element ) {
$scope . resetElements = function ( ) {
$scope . gridElements = [
LayoutEditor . Grid . from ( {
toolboxIcon : "\uf00a" ,
toolboxLabel : "Grid" ,
toolboxDescription : "Empty grid." ,
children : [ ]
} )
] ;
$scope . rowElements = [
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (1 column)" ,
toolboxDescription : "Row with 1 column." ,
children : LayoutEditor . Column . times ( 1 )
} ) ,
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (2 columns)" ,
toolboxDescription : "Row with 2 columns." ,
children : LayoutEditor . Column . times ( 2 )
} ) ,
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (3 columns)" ,
toolboxDescription : "Row with 3 columns." ,
children : LayoutEditor . Column . times ( 3 )
} ) ,
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (4 columns)" ,
toolboxDescription : "Row with 4 columns." ,
children : LayoutEditor . Column . times ( 4 )
} ) ,
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (6 columns)" ,
toolboxDescription : "Row with 6 columns." ,
children : LayoutEditor . Column . times ( 6 )
} ) ,
LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (12 columns)" ,
toolboxDescription : "Row with 12 columns." ,
children : LayoutEditor . Column . times ( 12 )
} ) , LayoutEditor . Row . from ( {
toolboxIcon : "\uf0c9" ,
toolboxLabel : "Row (empty)" ,
toolboxDescription : "Empty row." ,
children : [ ]
} )
] ;
$scope . columnElements = [
LayoutEditor . Column . from ( {
toolboxIcon : "\uf0db" ,
toolboxLabel : "Column" ,
toolboxDescription : "Empty column." ,
width : 1 ,
offset : 0 ,
children : [ ]
} )
] ;
$scope . contentElementCategories = _ ( $scope . element . config . categories ) . map ( function ( category ) {
return {
name : category . name ,
elements : _ ( category . contentTypes ) . map ( function ( contentType ) {
var type = contentType . type ;
var factory = LayoutEditor . factories [ type ] || LayoutEditor . factories [ "Content" ] ;
var item = {
isTemplated : false ,
contentType : contentType . id ,
contentTypeLabel : contentType . label ,
contentTypeClass : contentType . typeClass ,
data : null ,
hasEditor : contentType . hasEditor ,
html : contentType . html
} ;
var element = factory ( item ) ;
element . toolboxIcon = contentType . icon || "\uf1c9" ;
element . toolboxLabel = contentType . label ;
element . toolboxDescription = contentType . description ;
return element ;
} )
} ;
} ) ;
} ;
$scope . resetElements ( ) ;
$scope . getSortableOptions = function ( type ) {
var editorId = $element . closest ( ".layout-editor" ) . attr ( "id" ) ;
var parentClasses ;
var placeholderClasses ;
var floating = false ;
switch ( type ) {
case "Grid" :
parentClasses = [ ".layout-canvas" , ".layout-column" , ".layout-common-holder" ] ;
placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder" ;
break ;
case "Row" :
parentClasses = [ ".layout-grid" ] ;
placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder" ;
break ;
case "Column" :
parentClasses = [ ".layout-row:not(.layout-row-full)" ] ;
placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder" ;
floating = true ; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating
break ;
case "Content" :
parentClasses = [ ".layout-canvas" , ".layout-column" , ".layout-common-holder" ] ;
placeholderClasses = "layout-element layout-content ui-sortable-placeholder" ;
break ;
}
return {
cursor : "move" ,
connectWith : _ ( parentClasses ) . map ( function ( e ) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children" ; } ) . join ( ", " ) ,
placeholder : placeholderClasses ,
"ui-floating" : floating ,
create : function ( e , ui ) {
e . target . isToolbox = true ; // Will indicate to connected sortables that dropped items were sent from toolbox.
} ,
start : function ( e , ui ) {
$scope . $apply ( function ( ) {
$scope . element . isDragging = true ;
} ) ;
} ,
stop : function ( e , ui ) {
$scope . $apply ( function ( ) {
$scope . element . isDragging = false ;
$scope . resetElements ( ) ;
} ) ;
} ,
over : function ( e , ui ) {
$scope . $apply ( function ( ) {
$scope . element . canvas . setIsDropTarget ( false ) ;
} ) ;
} ,
}
} ;
var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed" ;
$scope . layoutIsCollapsed = $ . cookie ( layoutIsCollapsedCookieName ) === "true" ;
$scope . toggleLayoutIsCollapsed = function ( e ) {
$scope . layoutIsCollapsed = ! $scope . layoutIsCollapsed ;
$ . cookie ( layoutIsCollapsedCookieName , $scope . layoutIsCollapsed , { expires : 365 } ) ; // Remember collapsed state for a year.
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ;
} ,
templateUrl : environment . templateUrl ( "Toolbox" ) ,
replace : true
} ;
} ) ;
angular
. module ( "LayoutEditor" )
. directive ( "orcLayoutToolboxGroup" , function ( $compile , environment ) {
return {
restrict : "E" ,
scope : { category : "=" } ,
controller : function ( $scope , $element ) {
var isCollapsedCookieName = "layoutToolboxCategory_" + $scope . category . name + "_IsCollapsed" ;
$scope . isCollapsed = $ . cookie ( isCollapsedCookieName ) === "true" ;
$scope . toggleIsCollapsed = function ( e ) {
$scope . isCollapsed = ! $scope . isCollapsed ;
$ . cookie ( isCollapsedCookieName , $scope . isCollapsed , { expires : 365 } ) ; // Remember collapsed state for a year.
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ;
} ,
templateUrl : environment . templateUrl ( "ToolboxGroup" ) ,
replace : true
} ;
} ) ;