From 845dd4673c5f00d60d34e6eb0f43a4facb98f598 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 10 Jan 2017 15:15:56 +0100 Subject: [PATCH] Add client side file for partial view editor --- .../common/services/templatehelper.service.js | 57 ++++ .../views/common/overlays/insert/insert.html | 8 +- .../src/views/partialviews/edit.controller.js | 315 ++++++++++++++++++ .../src/views/partialviews/edit.html | 129 +++++++ .../src/views/templates/edit.controller.js | 43 ++- .../template-editor-controller.spec.js | 7 + 6 files changed, 532 insertions(+), 27 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/partialviews/edit.html diff --git a/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js new file mode 100644 index 0000000000..01e544c78d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js @@ -0,0 +1,57 @@ +(function() { + 'use strict'; + + function templateHelperService() { + + //crappy hack due to dictionary items not in umbracoNode table + function getInsertDictionarySnippet(nodeName) { + return "@Umbraco.GetDictionaryValue(\"" + nodeName + "\")"; + } + + function getInsertPartialSnippet(nodeName) { + return "@Html.Partial(\"" + nodeName + "\")"; + } + + function getQuerySnippet(queryExpression) { + var code = "\n@{\n" + "\tvar selection = " + queryExpression + ";\n}\n"; + code += "\n\n"; + return code; + } + + function getRenderBodySnippet() { + return "@RenderBody()"; + } + + function getRenderSectionSnippet(sectionName, mandatory) { + return "@RenderSection(\"" + sectionName + "\", " + mandatory + ")"; + } + + function getAddSectionSnippet(sectionName) { + return "@section " + sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n"; + } + + //////////// + + var service = { + getInsertDictionarySnippet: getInsertDictionarySnippet, + getInsertPartialSnippet: getInsertPartialSnippet, + getQuerySnippet: getQuerySnippet, + getRenderBodySnippet: getRenderBodySnippet, + getRenderSectionSnippet: getRenderSectionSnippet, + getAddSectionSnippet: getAddSectionSnippet + }; + + return service; + + } + + angular.module('umbraco.services').factory('templateHelper', templateHelperService); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html index e5ea8a3ddf..921058ea5a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/insert/insert.html @@ -2,19 +2,19 @@
-
+
Value
Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values.
-
+
Partial view
A partial view is a separate template file which can be rendered inside another template, it's great for reusing markup or for separating complex templates into separate files.
-
+
Macro
A Macro is a configurable component which is great for @@ -23,7 +23,7 @@
-
+
Dictionary
A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites.
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js new file mode 100644 index 0000000000..45065a8e89 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js @@ -0,0 +1,315 @@ +(function () { + "use strict"; + + function PartialViewsEditController($scope, $routeParams, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) { + + var vm = this; + var localizeSaving = localizationService.localize("general_saving"); + + vm.page = {}; + vm.page.loading = true; + vm.partialView = {}; + vm.partialViewPathPrefix = "Partials /"; + + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; + + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + + /* Functions bound to view model */ + + function save() { + + vm.page.saveButtonState = "busy"; + vm.partialView.content = vm.editor.getValue(); + + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: templateResource.save, + scope: $scope, + content: vm.partialView, + //We do not redirect on failure for stylesheets - this is because it is not possible to actually save the doc + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) {} + }).then(function (saved) { + + notificationsService.success("Partial View saved"); + vm.page.saveButtonState = "success"; + vm.partialView = saved; + + //sync state + editorState.set(vm.partialView); + + // normal tree sync + navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + + // clear $dirty state on form + setFormState("pristine"); + + }, function (err) { + + vm.page.saveButtonState = "error"; + + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + + }); + + } + + function openInsertOverlay() { + + vm.insertOverlay = { + view: "insert", + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: true + }, + hideSubmitButton: true, + show: true, + submit: function(model) { + + switch(model.insert.type) { + case "macro": + var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc"); + insert(macroObject.syntax); + break; + + case "dictionary": + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); + insert(code); + break; + + case "partial": + var code = templateHelper.getInsertPartialSnippet(model.insert.node.name); + insert(code); + break; + + case "umbracoField": + insert(model.insert.umbracoField); + break; + } + + vm.insertOverlay.show = false; + vm.insertOverlay = null; + + }, + close: function(oldModel) { + // close the dialog + vm.insertOverlay.show = false; + vm.insertOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + + } + + + function openMacroOverlay() { + + vm.macroPickerOverlay = { + view: "macropicker", + dialogData: {}, + show: true, + title: "Insert macro", + submit: function (model) { + + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + + }, + close: function(oldModel) { + // close the dialog + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + + + function openPageFieldOverlay() { + vm.pageFieldOverlay = { + submitButtonLabel: "Insert", + closeButtonlabel: "Cancel", + view: "insertfield", + show: true, + submit: function (model) { + insert(model.umbracoField); + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + }, + close: function (model) { + // close the dialog + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + + + function openDictionaryItemOverlay() { + vm.dictionaryItemOverlay = { + view: "treepicker", + section: "settings", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + show: true, + title: "Insert dictionary item", + select: function(node){ + + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + }, + close: function (model) { + // close dialog + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + + function openQueryBuilderOverlay() { + vm.queryBuilderOverlay = { + view: "querybuilder", + show: true, + title: "Query for content", + + submit: function (model) { + + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + }, + + close: function (model) { + // close dialog + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + + /* Local functions */ + + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css"); + if ($routeParams.create) { + templateResource.getScaffold().then(function (partialView) { + ready(partialView); + }); + } else { + templateResource.getById($routeParams.id).then(function (partialView) { + ready(partialView); + }); + } + } + + function ready(partialView) { + + vm.page.loading = false; + vm.partialView = partialView; + + //sync state + editorState.set(vm.partialView); + navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + + // ace configuration + vm.aceOption = { + mode: "razor", + theme: "chrome", + showPrintMargin: false, + advanced: { + fontSize: '14px' + }, + onLoad: function(_editor) { + vm.editor = _editor; + + // initial cursor placement + // Keep cursor in name field if we are create a new template + // else set the cursor at the bottom of the code editor + if(!$routeParams.create) { + $timeout(function(){ + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + + //change on blur, focus + vm.editor.on("blur", persistCurrentLocation); + vm.editor.on("focus", persistCurrentLocation); + } + } + + } + + function insert(str) { + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + vm.editor.focus(); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function setFormState(state) { + + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + + // set state + if(state === "dirty") { + currentForm.$setDirty(); + } else if(state === "pristine") { + currentForm.$setPristine(); + } + } + + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.PartialViews.EditController", PartialViewsEditController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.html new file mode 100644 index 0000000000..647844e3d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.html @@ -0,0 +1,129 @@ +
+ + + +
+ + + + + + + + +
+ +
+ + + + +
+ +
+ +
+
+ + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index f661b82e7b..f12c82fc3c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper) { + function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper) { var vm = this; var oldMasterTemplateAlias = null; @@ -169,26 +169,30 @@ vm.insertOverlay = { view: "insert", + allowedTypes: { + macro: true, + dictionary: true, + partial: true, + umbracoField: true + }, hideSubmitButton: true, show: true, submit: function(model) { switch(model.insert.type) { - case "macro": + case "macro": var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc"); insert(macroObject.syntax); break; case "dictionary": - //crappy hack due to dictionary items not in umbracoNode table - var code = "@Umbraco.GetDictionaryValue(\"" + model.insert.node.name + "\")"; + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); insert(code); break; case "partial": - //crappy hack due to dictionary items not in umbracoNode table - var code = "@Html.Partial(\"" + model.insert.node.name + "\")"; + var code = templateHelper.getInsertPartialSnippet(model.insert.node.name); insert(code); break; @@ -272,8 +276,7 @@ show: true, title: "Insert dictionary item", select: function(node){ - //crappy hack due to dictionary items not in umbracoNode table - var code = "@Umbraco.GetDictionaryValue(\"" + node.name + "\")"; + var code = templateHelper.getInsertDictionarySnippet(node.name); insert(code); vm.dictionaryItemOverlay.show = false; @@ -299,8 +302,8 @@ show: true, title: "Insert Partial view", select: function(node){ - //crappy hack due to dictionary items not in umbracoNode table - var code = "@Html.Partial(\"" + node.name + "\")"; + + var code = templateHelper.getInsertPartialSnippet(node.name); insert(code); vm.partialItemOverlay.show = false; @@ -321,18 +324,9 @@ view: "querybuilder", show: true, title: "Query for content", - submit: function (model) { - var code = "\n@{\n" + "\tvar selection = " + model.result.queryExpression + ";\n}\n"; - code += "
    \n" + - "\t@foreach(var item in selection){\n" + - "\t\t
  • \n" + - "\t\t\t@item.Name\n" + - "\t\t
  • \n" + - "\t}\n" + - "
\n\n"; - + var code = templateHelper.getQuerySnippet(model.result.queryExpression); insert(code); vm.queryBuilderOverlay.show = false; @@ -360,15 +354,18 @@ submit: function(model) { if (model.insertType === 'renderBody') { - insert("@RenderBody()"); + var code = templateHelper.getRenderBodySnippet(); + insert(code); } if (model.insertType === 'renderSection') { - insert("@RenderSection(\"" + model.renderSectionName + "\", " + model.mandatoryRenderSection + ")"); + var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); + insert(code); } if (model.insertType === 'addSection') { - wrap("@section " + model.sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n"); + var code = templateHelper.getAddSectionSnippet(model.sectionName); + wrap(code); } vm.sectionsOverlay.show = false; diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js index 8208999536..6af9d12d73 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js @@ -101,6 +101,13 @@ $setPristine: function() {} } } + }, + templateHelper: { + getInsertDictionary: function() { return ""; }, + getInsertPartialSnippet: function() { return ""; }, + getQuerySnippet: function() { return ""; }, + getRenderBodySnippet: function() { return ""; }, + getRenderSectionSnippet: function() { return ""; } } }); }