#21240: Layouts/DynamicForms: switch to Angular "inline array" DI annotation

Work Item: 21240
This commit is contained in:
Kegan Maher
2015-03-06 09:03:04 +01:00
committed by Sipke Schoorstra
parent 8101542d9e
commit 6e7d82bd29
19 changed files with 1733 additions and 1682 deletions

View File

@@ -1,28 +1,30 @@
angular
.module("LayoutEditor")
.directive("orcLayoutForm", 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";
$scope.edit = function () {
$scope.$root.editElement($scope.element).then(function (args) {
if (args.cancel)
return;
$scope.element.data = decodeURIComponent(args.element.data);
$scope.element.name = args.elementEditorModel.name;
$scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType;
$scope.$apply();
});
};
},
templateUrl: environment.templateUrl("Form"),
replace: true
};
});
.directive("orcLayoutForm", ["$compile", "scopeConfigurator", "environment",
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";
$scope.edit = function () {
$scope.$root.editElement($scope.element).then(function (args) {
if (args.cancel)
return;
$scope.element.data = decodeURIComponent(args.element.data);
$scope.element.name = args.elementEditorModel.name;
$scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType;
$scope.$apply();
});
};
},
templateUrl: environment.templateUrl("Form"),
replace: true
};
}
]);
var LayoutEditor;
(function ($, LayoutEditor) {

View File

@@ -4,7 +4,7 @@
<!--Determines if the bundle file should be automatically optimized after creation/update.-->
<minify>true</minify>
<!--Determine whether to generate/re-generate this bundle on building the solution.-->
<runOnBuild>false</runOnBuild>
<runOnBuild>true</runOnBuild>
<!--Specifies a custom subfolder to save files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
<outputDirectory />
</settings>

View File

@@ -1 +1 @@
angular.module("LayoutEditor").directive("orcLayoutForm",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y";n.edit=function(){n.$root.editElement(n.element).then(function(t){t.cancel||(n.element.data=decodeURIComponent(t.element.data),n.element.name=t.elementEditorModel.name,n.element.formBindingContentType=t.elementEditorModel.formBindingContentType,n.$apply())})}},templateUrl:i.templateUrl("Form"),replace:!0}});var LayoutEditor;(function(n,t){t.Form=function(i,r,u,f,e,o,s,h,c,l,a,v){var y,p;t.Element.call(this,"Form",i,r,u,f,e);t.Container.call(this,["Grid","Content"],v);y=this;this.isContainable=!0;this.dropTargetClass="layout-common-holder";this.contentType=h;this.contentTypeLabel=c;this.contentTypeClass=l;this.name=o||"Untitled";this.formBindingContentType=s;this.hasEditor=a;this.toObject=function(){var n=this.elementToObject();return n.name=this.name,n.formBindingContentType=this.formBindingContentType,n.children=this.childrenToObject(),n};p=this.getEditorObject;this.getEditorObject=function(){var t=p();return n.extend(t,{FormName:this.name,FormBindingContentType:this.formBindingContentType})};this.setChildren=function(n){this.children=n;_(this.children).each(function(n){n.parent=y;y.linkChild(n)})};this.linkChild=function(t){var i=t.getEditorObject;t.getEditorObject=function(){var t=i();return n.extend(t,{FormBindingContentType:y.formBindingContentType})}};this.setChildren(v)};t.Form.from=function(n){return new t.Form(n.data,n.htmlId,n.htmlClass,n.htmlStyle,n.isTemplated,n.name,n.formBindingContentType,n.contentType,n.contentTypeLabel,n.contentTypeClass,n.hasEditor,t.childrenFrom(n.children))};t.registerFactory("Form",function(n){return t.Form.from(n)})})(jQuery,LayoutEditor||(LayoutEditor={}));
angular.module("LayoutEditor").directive("orcLayoutForm",["$compile","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y";n.edit=function(){n.$root.editElement(n.element).then(function(t){t.cancel||(n.element.data=decodeURIComponent(t.element.data),n.element.name=t.elementEditorModel.name,n.element.formBindingContentType=t.elementEditorModel.formBindingContentType,n.$apply())})}},templateUrl:i.templateUrl("Form"),replace:!0}}]);var LayoutEditor;(function(n,t){t.Form=function(i,r,u,f,e,o,s,h,c,l,a,v){var y,p;t.Element.call(this,"Form",i,r,u,f,e);t.Container.call(this,["Grid","Content"],v);y=this;this.isContainable=!0;this.dropTargetClass="layout-common-holder";this.contentType=h;this.contentTypeLabel=c;this.contentTypeClass=l;this.name=o||"Untitled";this.formBindingContentType=s;this.hasEditor=a;this.toObject=function(){var n=this.elementToObject();return n.name=this.name,n.formBindingContentType=this.formBindingContentType,n.children=this.childrenToObject(),n};p=this.getEditorObject;this.getEditorObject=function(){var t=p();return n.extend(t,{FormName:this.name,FormBindingContentType:this.formBindingContentType})};this.setChildren=function(n){this.children=n;_(this.children).each(function(n){n.parent=y;y.linkChild(n)})};this.linkChild=function(t){var i=t.getEditorObject;t.getEditorObject=function(){var t=i();return n.extend(t,{FormBindingContentType:y.formBindingContentType})}};this.setChildren(v)};t.Form.from=function(n){return new t.Form(n.data,n.htmlId,n.htmlClass,n.htmlStyle,n.isTemplated,n.name,n.formBindingContentType,n.contentType,n.contentTypeLabel,n.contentTypeClass,n.hasEditor,t.childrenFrom(n.children))};t.registerFactory("Form",function(n){return t.Form.from(n)})})(jQuery,LayoutEditor||(LayoutEditor={}));

View File

@@ -1,25 +1,27 @@
angular
.module("LayoutEditor")
.directive("orcLayoutForm", 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";
$scope.edit = function () {
$scope.$root.editElement($scope.element).then(function (args) {
if (args.cancel)
return;
$scope.element.data = decodeURIComponent(args.element.data);
$scope.element.name = args.elementEditorModel.name;
$scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType;
$scope.$apply();
});
};
},
templateUrl: environment.templateUrl("Form"),
replace: true
};
});
.directive("orcLayoutForm", ["$compile", "scopeConfigurator", "environment",
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";
$scope.edit = function () {
$scope.$root.editElement($scope.element).then(function (args) {
if (args.cancel)
return;
$scope.element.data = decodeURIComponent(args.element.data);
$scope.element.name = args.elementEditorModel.name;
$scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType;
$scope.$apply();
});
};
},
templateUrl: environment.templateUrl("Form"),
replace: true
};
}
]);

View File

@@ -9,9 +9,8 @@ namespace Orchard.Layouts {
manifest.DefineScript("AngularSanitize").SetUrl("Lib/angular-sanitize.js").SetDependencies("Angular");
manifest.DefineScript("AngularResource").SetUrl("Lib/angular-resource.js").SetDependencies("Angular");
manifest.DefineScript("Layouts.Models").SetUrl("Models.min.js", "Models.js").SetDependencies("jQuery", "Underscore");
// For some reason the minified version doesn't work and causes AngularJS to complain about some directive not being found, so we're only registering the debug version of LayoutEditor.js.
manifest.DefineScript("Layouts.LayoutEditor").SetUrl(/*"LayoutEditor.min.js",*/ "LayoutEditor.js").SetDependencies("Layouts.Models", "AngularResource", "AngularSanitize", "Angular", "jQuery", "Underscore");
manifest.DefineScript("Layouts.LayoutEditor").SetUrl("LayoutEditor.min.js", "LayoutEditor.js").SetDependencies("Layouts.Models", "AngularResource", "AngularSanitize", "Angular", "jQuery", "Underscore");
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,17 @@
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
};
});
.directive("orcLayoutCanvas", ["scopeConfigurator", "environment",
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
};
}
]);

View File

@@ -1,13 +1,15 @@
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);
}
};
});
.directive("orcLayoutChild", ["$compile",
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);
}
};
}
]);

View File

@@ -1,64 +1,66 @@
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);
});
.directive("orcLayoutColumn", ["$compile", "scopeConfigurator", "environment",
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 (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);
});
}
}
}
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;
});
}
});
}
};
});
},
stop: function (e, ui) {
scope.$apply(function () {
scope.element.editor.isResizing = false;
});
}
});
}
};
}
]);

View File

@@ -1,44 +1,46 @@
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;
.directive("orcLayoutContent", ["$sce", "scopeConfigurator", "environment",
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.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(decodeURIComponent($scope.element.html.replace(/\+/g, "%20")));
},
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();
}
});
};
$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(decodeURIComponent($scope.element.html.replace(/\+/g, "%20")));
},
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();
}
});
}
};
});
}
};
}
]);

View File

@@ -1,172 +1,174 @@
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.");
.directive("orcLayoutEditor", ["environment",
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
$scope.click = function (canvas, e) {
if (!canvas.editor.isDragging)
canvas.setIsFocused();
e.stopPropagation();
};
// 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.getClasses = function (canvas) {
var result = ["layout-element", "layout-container", "layout-canvas"];
$scope.$root.editElement = function (element) {
var host = $scope.$root.layoutDesignerHost;
return host.editElement(element);
};
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");
$scope.$root.addElement = function (contentType) {
var host = $scope.$root.layoutDesignerHost;
return host.addElement(contentType);
};
return result;
};
$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;
}
};
// 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");
$(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;
}
});
$scope.$root.layoutDesignerHost = layoutDesignerHost;
// 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();
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);
}
});
}, 100);
}
else {
tinymce.remove("#layout-editor-" + $scope.$id + " .layout-content-markup");
$element.find(".layout-toolbar-container").hide();
$scope.element.inlineEditingIsActive = false;
}
};
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();
$(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();
}
})
// 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;
});
}
});
}
};
});
});
// 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;
});
}
});
}
};
}
]);

View File

@@ -1,15 +1,17 @@
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
};
});
.directive("orcLayoutGrid", ["$compile", "scopeConfigurator", "environment",
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
};
}
]);

View File

@@ -1,34 +1,36 @@
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.find("input").first().focus();
}
});
popup.click(function (e) {
e.stopPropagation();
});
parentElement.click(function (e) {
popup.hide();
});
popup.keydown(function (e) {
if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc
.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.find("input").first().focus();
}
});
popup.click(function (e) {
e.stopPropagation();
});
parentElement.click(function (e) {
popup.hide();
e.stopPropagation();
});
}
};
});
});
popup.keydown(function (e) {
if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc
popup.hide();
e.stopPropagation();
});
}
};
}
]);

View File

@@ -1,16 +1,18 @@
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
};
});
.directive("orcLayoutRow", ["$compile", "scopeConfigurator", "environment",
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
};
}
]);

View File

@@ -1,168 +1,170 @@
angular
.module("LayoutEditor")
.directive("orcLayoutToolbox", function ($compile, environment) {
return {
restrict: "E",
controller: function ($scope, $element) {
.directive("orcLayoutToolbox", ["$compile", "environment",
function ($compile, environment) {
return {
restrict: "E",
controller: function ($scope, $element) {
$scope.resetElements = function () {
$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.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.resetElements();
$scope.columnElements = [
LayoutEditor.Column.from({
toolboxIcon: "\uf0db",
toolboxLabel: "Column",
toolboxDescription: "Empty column.",
width: 1,
offset: 0,
children: []
})
];
$scope.getSortableOptions = function (type) {
var editorId = $element.closest(".layout-editor").attr("id");
var parentClasses;
var placeholderClasses;
var floating = false;
$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;
})
};
});
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);
});
},
}
};
$scope.resetElements();
var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed";
$scope.layoutIsCollapsed = $.cookie(layoutIsCollapsedCookieName) === "true";
$scope.getSortableOptions = function (type) {
var editorId = $element.closest(".layout-editor").attr("id");
var parentClasses;
var placeholderClasses;
var floating = false;
$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
};
});
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
};
}
]);

View File

@@ -1,20 +1,22 @@
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
};
});
.directive("orcLayoutToolboxGroup", ["$compile", "environment",
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
};
}
]);

View File

@@ -20,11 +20,13 @@
angular
.module("LayoutEditor")
.factory("clipboard", function() {
return {
setData: LayoutEditor.Clipboard.setData,
getData: LayoutEditor.Clipboard.getData,
disable: LayoutEditor.Clipboard.disable
};
});
.factory("clipboard", [
function() {
return {
setData: LayoutEditor.Clipboard.setData,
getData: LayoutEditor.Clipboard.getData,
disable: LayoutEditor.Clipboard.disable
};
}
]);
})(LayoutEditor || (LayoutEditor = {}));

View File

@@ -1,317 +1,319 @@
angular
.module("LayoutEditor")
.factory("scopeConfigurator", function ($timeout, clipboard) {
return {
.factory("scopeConfigurator", ["$timeout", "clipboard",
function ($timeout, clipboard) {
return {
configureForElement: function ($scope, $element) {
configureForElement: function ($scope, $element) {
$element.find(".layout-panel").click(function (e) {
e.stopPropagation();
});
$element.find(".layout-panel").click(function (e) {
e.stopPropagation();
});
$element.parent().keydown(function (e) {
var handled = false;
var resetFocus = false;
var element = $scope.element;
$element.parent().keydown(function (e) {
var handled = false;
var resetFocus = false;
var element = $scope.element;
if (element.editor.isDragging || element.editor.inlineEditingIsActive)
return;
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 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 || e.which == 27)) { // Space or Esc
$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();
if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 46) { // Del
$scope.delete(element);
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();
} else if (!e.ctrlKey && !e.shiftKey && !e.altKey && (e.which == 32 || e.which == 27)) { // Space or Esc
$element.find(".layout-panel-action-properties").first().click();
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);
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();
}
},
if (!!element.parent) { // This is a child.
if (e.altKey && e.which == 38) { // Alt+Up
element.parent.setIsFocused();
handled = true;
}
configureForContainer: function ($scope, $element) {
var element = $scope.element;
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;
}
}
}
//$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();
};
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.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.editor.focusedElement.setIsFocused();
element.setIsDropTarget(true);
element.editor.isDragging = true;
});
}, 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();
}
// 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);
});
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.
// Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization.
receivedElement.setEditor(element.editor);
receivedElement.setParent(element);
if (receivedElement.type == "Content" && !!receivedElement.hasEditor) {
$scope.$root.editElement(receivedElement).then(function (args) {
if (!args.cancel) {
receivedElement.data = decodeURIComponent(args.element.data);
receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20")));
}
$timeout(function () {
if (!!args.cancel)
receivedElement.delete();
else
receivedElement.setIsFocused();
//$scope.isReceiving = false;
element.setIsDropTarget(false);
});
return;
});
}
},
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 () {
//$scope.isReceiving = false;
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);
if (!!receivedElement)
receivedElement.setIsFocused();
});
});
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.
// Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization.
receivedElement.setEditor(element.editor);
receivedElement.setParent(element);
if (receivedElement.type == "Content" && !!receivedElement.hasEditor) {
$scope.$root.editElement(receivedElement).then(function (args) {
if (!args.cancel) {
receivedElement.data = decodeURIComponent(args.element.data);
receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20")));
}
$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.click = function (child, e) {
if (!child.editor.isDragging)
child.setIsFocused();
e.stopPropagation();
};
$scope.getClasses = function (child) {
var result = ["layout-element"];
$scope.getClasses = function (child) {
var result = ["layout-element"];
if (!!child.children) {
result.push("layout-container");
if (child.getIsSealed())
result.push("layout-container-sealed");
}
if (!!child.children) {
result.push("layout-container");
if (child.getIsSealed())
result.push("layout-container-sealed");
}
result.push("layout-" + child.type.toLowerCase());
result.push("layout-" + child.type.toLowerCase());
if (!!child.dropTargetClass)
result.push(child.dropTargetClass);
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);
// 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");
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;
};
}
return result;
};
}
};
}
});
]);