├── .github └── workflows │ ├── ci.yml │ ├── release-preview.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── LICENSE ├── LICENSE.md ├── README.md ├── docs ├── README.md ├── developers-guide-v1.md ├── developers-guide-v2.md └── img │ ├── doc-type-grid-editor.png │ ├── doc-type-grid-editor.psd │ ├── screenshot-01.png │ ├── screenshot-02.png │ ├── screenshot-03.png │ ├── screenshot-03b.png │ ├── screenshot-04.png │ ├── screenshot-05.png │ └── screenshot-06.png └── src ├── Our.Umbraco.DocTypeGridEditor.sln └── Our.Umbraco.DocTypeGridEditor ├── App_Plugins └── DocTypeGridEditor │ └── Render │ ├── DocTypeGridEditor.cshtml │ └── DocTypeGridEditorPreviewer.cshtml ├── Config └── DocTypeGridEditorSettings.cs ├── Controllers ├── DocTypeGridEditorApiController.cs └── DocTypeGridEditorBlueprintApiController.cs ├── DocTypeGridEditorComposer.cs ├── DocTypeGridEditorManifestFilter.cs ├── Events ├── ContentTypeCacheRefreshHandler.cs └── DataTypeCacheRefreshHandler.cs ├── Helpers ├── DocTypeGridEditorHelper.cs ├── ServiceCollectionExtensions.cs └── UmbracoBuilderExtensions.cs ├── Models ├── ContentTypeContainer.cs ├── DetachedPublishedElement.cs ├── DetachedPublishedProperty.cs ├── DocTypeGridEditorValue.cs ├── PreviewData.cs ├── PreviewModel.cs ├── UnpublishedContent.cs └── UnpublishedProperty.cs ├── Our.Umbraco.DocTypeGridEditor.csproj ├── ValueProcessing ├── Collections │ ├── DocTypeGridEditorValueProcessorsCollection.cs │ └── DocTypeGridEditorValueProcessorsCollectionBuilder.cs ├── DocTypeGridEditorDataValueReference.cs ├── IDocTypeGridEditorValueProcessor.cs └── UmbracoTagsValueProcessor.cs ├── ViewComponents └── DocTypeGridEditorViewComponent.cs └── wwwroot ├── Css └── doctypegrideditor.css ├── Js ├── doctypegrideditor.controllers.js ├── doctypegrideditor.directives.js ├── doctypegrideditor.resources.js └── doctypegrideditor.services.js ├── Lang ├── da-DK.xml ├── en-GB.xml └── en-US.xml └── Views ├── DocTypeGridEditor.html └── doctypegrideditor.dialog.html /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | defaults: 12 | run: 13 | working-directory: ./src 14 | timeout-minutes: 5 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | - name: Build 19 | run: dotnet build --configuration Release 20 | -------------------------------------------------------------------------------- /.github/workflows/release-preview.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v[0-9]+.[0-9]+.[0-9]+-[a-z]*[0-9][0-9][0-9]" 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | defaults: 9 | run: 10 | working-directory: ./src 11 | timeout-minutes: 15 12 | env: 13 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Set VERSION variable from tag 18 | run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV 19 | - name: Pack 20 | run: dotnet pack --configuration Release /p:Version=${VERSION} --output . 21 | - name: Push 22 | run: dotnet nuget push Our.Umbraco.DocTypeGridEditor.${VERSION}.nupkg --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v[0-9]+.[0-9]+.[0-9]+" 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | defaults: 9 | run: 10 | working-directory: ./src 11 | timeout-minutes: 15 12 | env: 13 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Verify commit exists in origin/main 18 | run: | 19 | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* 20 | git branch --remote --contains | grep origin/main 21 | - name: Set VERSION variable from tag 22 | run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV 23 | - name: Build 24 | run: dotnet build --configuration Release /p:Version=${VERSION} 25 | - name: Pack 26 | run: dotnet pack --configuration Release /p:Version=${VERSION} --no-build --output . 27 | - name: Push 28 | run: dotnet nuget push Our.Umbraco.DocTypeGridEditor.${VERSION}.nupkg --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | Our.Umbraco.DocTypeGridEditor.Testsite 3 | bin 4 | obj 5 | src/.idea 6 | src/Our.Umbraco.DocTypeGridEditor/.idea 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 10.0.0 9 | 10 | - Upgraded to Umbraco 10 11 | - Changed to a Razor Class Library - which means static assets are now embedded. When upgrading, make sure to delete App_Plugins/DocTypeGridEditor from your project! 12 | 13 | ## 9.0.2 14 | 15 | - Fix datavalue reference so other editors than DTGE doesnt break when saving 16 | 17 | ## 1.2.9 18 | 19 | - Fix datavalue reference so other editors than DTGE doesnt break when saving 20 | 21 | ## 9.0.1 22 | 23 | - Fixes bug when saving a component which previously failed validation - thanks @inetzo 24 | - Fixes bug in DataValueReference for media tracking, where custom DTGEs were not recognized. 4db5cdd 25 | 26 | ## 1.2.8 27 | 28 | - Fixes bug when saving a component which previously failed validation - thanks @inetzo 29 | - Fixes bug in DataValueReference for media tracking, where custom DTGEs were not recognized. 4db5cdd 30 | 31 | ## 9.0.0 32 | 33 | - Updated to Umbraco 9.0.0 34 | - DocTypeGridEditorSurfaceController replaced by DocTypeGridEditorViewComponent **BREAKING** 35 | - Finding moved cheese 36 | - Changed DocTypeGridEditorHelper from a static class to being registered in the container for DI. **BREAKING** 37 | - Adds default DocTypeGridEditorViewComponent used for all not hijacked rendering. 38 | - Adds DocTypeGridEditorSettings object for configuring DocTypeGridEditor - currently only configures the default viewcomponent. 39 | 40 | ## 1.2.7 - 2020-03-28 41 | 42 | - Fix: Null error when validation fails 43 | - Fix: Resetting save button when validation fails 44 | 45 | ## 1.2.6 - 2020-12-07 46 | 47 | - Fix: Error saving temp blueprint 48 | - Fix: issue when previewing linked content items 49 | 50 | ## [1.2.5] - 2020-11-01 51 | 52 | - Resetting saveButtonState when validation contains errors #221 53 | - Handle null value when deserializing grid value in DataValueReference 50ba85aaeeb66ed305248d303d568e4698943094 54 | 55 | ## [1.2.4] - 2020-09-09 56 | 57 | - Clear earlier serverside validation errors #217 58 | - Null Pointer Exception on save when no grid layout selected #211 59 | - Unsaved Changes dialog fires when hitting submit on grid editors #205 60 | 61 | ## [1.2.3] - 2020-06-05 62 | 63 | - #199 2 Element Types in same Nested Content freeze the Add content button in the grid layout 64 | - #202 Contenttemplates are left behind when validation is not succesfull 65 | - #203 "activeVariant.language is null" - thanks @Matthew-Wise 66 | 67 | ## [1.2.2] - 2020-05-14 68 | 69 | - Infinite loading when doc type has a content template #192 - thanks for reporting @OlePc 70 | - GetPreviewMarkup null reference error when getting cultures #195 - thanks for reporting _and fixing_ @Matthew-Wise 71 | 72 | ## [1.2.1] - 2020-04-27 73 | 74 | - Disables notifications after each edit #190 75 | - Moves intrusive styling in previews to css file #188 76 | - Fixes JS error when no doctypes selected in multitype #186 77 | - Fixes bug where wrong editor gets removed when cancelling #185 78 | - Now works with custom backoffice URL #138 79 | 80 | ## [1.2.0] - 2020-04-12 81 | 82 | - #173 Added ValueProcessors and added processor for Umbraco.Tags 83 | - #176 Enables client side validation 84 | - #182 Enables media tracking in 8.6 85 | 86 | ## [1.1.0] - 2019-12-11 87 | 88 | - Changed the size attribute on grid editors to enable the new medium size in Umbraco 8.4 #167 89 | - Adds class to the preview container, so it's easier to target with custom css 47c8f5d 90 | - Check for culture variation when picking content types #154 f5453db c19a93f 91 | - Added implementation of Alias property #160 92 | 93 | ## [1.0.0] - 2019-08-20 94 | 95 | - Uses Infinite Editing (editorService) for editing DTGE content, instead of overlays. 96 | - Adds option to set dialog (infinite editor) size from grid editor config. 97 | - Adds Content Template aka Blueprints support for DTGE items. 98 | - Updates default placeholder for DTGEs without previews enabled. 99 | - Developers Guide updated 100 | - New minimum Umbraco version requirement: 8.1.0 - Doc Type Grid Editor will not work in lower versions! 101 | 102 | [unreleased]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.5...HEAD 103 | [1.2.5]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.4...1.2.5 104 | [1.2.4]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.3...1.2.4 105 | [1.2.3]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.2...1.2.3 106 | [1.2.2]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.1...1.2.2 107 | [1.2.1]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.2.0...1.2.1 108 | [1.2.0]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.1.0...1.2.0 109 | [1.1.0]: https://github.com/skttl/umbraco-doc-type-grid-editor/compare/1.0.0...1.1.0 110 | [1.0.0]: https://github.com/skttl/umbraco-doc-type-grid-editor/releases/tag/1.0.0 111 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue or assessing 9 | patches and features. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](#bugs), 15 | [features requests](#features) and [submitting pull 16 | requests](#pull-requests), but please respect the following restrictions: 17 | 18 | * Please **do not** use the issue tracker for personal support requests (use 19 | [Our Umbraco](https://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor/doc-type-grid-editor-feedback/) or Twitter). 20 | 21 | * Please **do not** derail or troll issues. Keep the discussion on topic and 22 | respect the opinions of others. 23 | 24 | 25 | 26 | ## Bug reports 27 | 28 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 29 | Good bug reports are extremely helpful - thank you! 30 | 31 | Guidelines for bug reports: 32 | 33 | 1. **Use the GitHub issue search** — check if the issue has already been 34 | reported. 35 | 36 | 2. **Check if the issue has been fixed** — try to reproduce it using the 37 | latest `master` or development branch in the repository. 38 | 39 | 3. **Isolate the problem** — create a reduced test case and a live example. 40 | 41 | A good bug report shouldn't leave others needing to chase you up for more 42 | information. Please try to be as detailed as possible in your report. What is 43 | your environment? What steps will reproduce the issue? What browser(s) and OS 44 | experience the problem? What would you expect to be the outcome? All these 45 | details will help people to fix any potential bugs. 46 | 47 | Example: 48 | 49 | > Short and descriptive example bug report title 50 | > 51 | > A summary of the issue and the browser/OS environment in which it occurs. If 52 | > suitable, include the steps required to reproduce the bug. 53 | > 54 | > 1. This is the first step 55 | > 2. This is the second step 56 | > 3. Further steps, etc. 57 | > 58 | > `` - a link to the reduced test case 59 | > 60 | > Any other information you want to share that is relevant to the issue being 61 | > reported. This might include the lines of code that you have identified as 62 | > causing the bug, and potential solutions (and your opinions on their 63 | > merits). 64 | 65 | 66 | 67 | ## Feature requests 68 | 69 | Feature requests are welcome. But take a moment to find out whether your idea 70 | fits with the scope and aims of the project. It's up to *you* to make a strong 71 | case to convince the project's developers of the merits of this feature. Please 72 | provide as much detail and context as possible. 73 | 74 | 75 | 76 | ## Pull requests 77 | 78 | Good pull requests - patches, improvements, new features - are a fantastic 79 | help. They should remain focused in scope and avoid containing unrelated 80 | commits. 81 | 82 | **Please ask first** before embarking on any significant pull request (e.g. 83 | implementing features, refactoring code, porting to a different language), 84 | otherwise you risk spending a lot of time working on something that the 85 | project's developers might not want to merge into the project. 86 | 87 | Please adhere to the coding conventions used throughout a project (indentation, 88 | accurate comments, etc.) and any other requirements (such as test coverage). 89 | 90 | Follow this process if you'd like your work considered for inclusion in the 91 | project: 92 | 93 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 94 | and configure the remotes: 95 | 96 | ```bash 97 | # Clone your fork of the repo into the current directory 98 | git clone https://github.com// 99 | # Navigate to the newly cloned directory 100 | cd 101 | # Assign the original repo to a remote called "upstream" 102 | git remote add upstream https://github.com// 103 | ``` 104 | 105 | 2. If you cloned a while ago, get the latest changes from upstream: 106 | 107 | ```bash 108 | git checkout develop 109 | git pull upstream develop 110 | ``` 111 | 112 | 3. Create a new topic branch (off the main project `develop` branch) to 113 | contain your feature, change, or fix: 114 | 115 | ```bash 116 | git checkout -b 117 | ``` 118 | 119 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 120 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 121 | or your code is unlikely be merged into the main project. Use Git's 122 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 123 | feature to tidy up your commits before making them public. 124 | 125 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 126 | 127 | ```bash 128 | git pull [--rebase] upstream develop 129 | ``` 130 | 131 | 6. Push your topic branch up to your fork: 132 | 133 | ```bash 134 | git push origin 135 | ``` 136 | 137 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 138 | with a clear title and description. 139 | 140 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 141 | license your work under the same license as that used by the project. -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [skttl] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Søren Kottal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Doc Type Grid Editor 2 | 3 | [![NuGet release](https://img.shields.io/nuget/v/Our.Umbraco.DocTypeGridEditor.svg)](https://www.nuget.org/packages/Our.Umbraco.DocTypeGridEditor) 4 | [![Our Umbraco project page](https://img.shields.io/badge/our-umbraco-orange.svg)](https://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor) 5 | 6 | A grid editor for Umbraco that allows you to use Doc Types as a blue print for grid-cell data. 7 | 8 | ## Getting Started 9 | 10 | ### Installation 11 | 12 | > _Note:_ Doc Type Grid Editor has been developed against Umbraco since v7 supports different versions of Umbraco: 13 | 14 | DTGE is compatible with the following Umbraco versions: 15 | Umbraco 7.1-7.5: DocTypeGridEditor 0.5.0 16 | Umbraco 7.6-7.x: DocTypeGridEditor 0.6.0 17 | Umbraco 8.1-8.5: DocTypeGridEditor 1.1.0 18 | Umbraco 8.5-8.x: DocTypeGridEditor 1.2.7 19 | Umbraco 9.0-9.x: DocTypeGridEditor 2.0.0 20 | Umbraco 10.0-10.x: DocTypeGridEditor 10.0.0 21 | 22 | Doc Type Grid Editor can be installed from either Our Umbraco package repository, or NuGet. From version 2.0.0 only NuGet can be used to install the package. 23 | 24 | #### Our Umbraco package repository 25 | 26 | To install from Our Umbraco, please download the package from: 27 | 28 | > [https://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor](https://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor) 29 | 30 | #### NuGet package repository 31 | 32 | To [install from NuGet](https://www.nuget.org/packages/Our.Umbraco.DocTypeGridEditor), you can run the following command from within Visual Studio: 33 | 34 | PM> Install-Package Our.Umbraco.DocTypeGridEditor 35 | 36 | --- 37 | 38 | ## Developers Guide 39 | 40 | For details on how to use the Doc Type Grid Editor package, please refer to our documentation. 41 | 42 | - [Doc Type Grid Editor - Developers Guide, v1.2.x](docs/developers-guide-v1.md) 43 | - [Doc Type Grid Editor - Developers Guide, v2.x.x & v10.x.x](docs/developers-guide-v2.md) 44 | 45 | --- 46 | 47 | ## Known Issues 48 | 49 | Please be aware that not all property-editors will work within Doc Type Grid Editor. The following Umbraco core property-editors are known to have compatibility issues: 50 | 51 | - Image Cropper 52 | - Macro Container 53 | - Tags 54 | - Upload 55 | 56 | --- 57 | 58 | ## Contributing to this project 59 | 60 | Anyone and everyone is welcome to contribute. Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md). 61 | 62 | - [Bug reports](CONTRIBUTING.md#bugs) 63 | - [Feature requests](CONTRIBUTING.md#features) 64 | - [Pull requests](CONTRIBUTING.md#pull-requests) 65 | 66 | ## Contact 67 | 68 | Have a question? 69 | 70 | - [Doc Type Grid Editor Forum](https://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor/doc-type-grid-editor-feedback/) on Our Umbraco 71 | - [Raise an issue](https://github.com/skttl/umbraco-doc-type-grid-editor/issues) on GitHub 72 | 73 | ## Dev Team 74 | 75 | - [Søren Kottal](https://github.com/skttl) 76 | 77 | ### Special thanks 78 | 79 | - Thanks to [Matt Brailsford](https://github.com/mattbrailsford) and [Lee Kelleher](https://github.com/leekelleher) for building this great package. 80 | - Thanks to [Jeavon Leopold](https://github.com/Jeavon) for being a rockstar and adding AppVeyor & NuGet support. 81 | - Thanks to [Dave Woestenborghs](https://github.com/dawoe) for helping solve showstopper issues. 82 | - Thanks to [Arnold Visser](https://github.com/ArnoldV) and [Bjarne Fyrstenborg](https://github.com/bjarnef) for help with porting the package to Umbraco 8. 83 | 84 | ## License 85 | 86 | Copyright © 2019 Søren Kottal, Our Umbraco and [other contributors](https://github.com/skttl/umbraco-doc-type-grid-editor/graphs/contributors) 87 | 88 | Copyright © 2017 UMCO, Our Umbraco and [other contributors](https://github.com/skttl/umbraco-doc-type-grid-editor/graphs/contributors) 89 | 90 | Copyright © 2014 Umbrella Inc 91 | 92 | Licensed under the [MIT License](LICENSE.md) 93 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Doc Type Grid Editor 2 | ## Documentation 3 | 4 | * [Doc Type Grid Editor - Developers Guide, v1.2.x](developers-guide-v1.md) 5 | * [Doc Type Grid Editor - Developers Guide, v2.x.x](developers-guide-v2.md) -------------------------------------------------------------------------------- /docs/developers-guide-v1.md: -------------------------------------------------------------------------------- 1 | # Doc Type Grid Editor 1 - Developers Guide 2 | 3 | ### Contents 4 | 5 | 1. [Introduction](#introduction) 6 | 2. [Getting Set Up](#getting-set-up) 7 | 1. [System Requirements](#system-requirements) 8 | 3. [Configuring The Doc Type Grid Editor](#configuring-the-doc-type-grid-editor) 9 | 4. [Hooking Up The Doc Type Grid Editor](#hooking-up-the-doc-type-grid-editor) 10 | 5. [Rendering a Doc Type Grid Editor](#rendering-a-doc-type-grid-editor) 11 | 1. [Rendering Alternative Preview Content](#rendering-alternative-preview-content) 12 | 2. [DocTypeGridEditorSurfaceController](#doctypegrideditorsurfacecontroller) 13 | 6. [Value Processors](#value-processors) 14 | 7. [Useful Links](#useful-links) 15 | 16 | --- 17 | 18 | ### Introduction 19 | 20 | **Doc Type Grid Editor** is an advanced grid editor for the Umbraco grid, offering similar functionality as the macro grid editor but using the full power of the Doc Type editor and data types. 21 | 22 | With the macro grid editor you are limited to only using the macro builder and thus the handful of parameter editors that are available. Of course you can create / config your own parameter editors, however this is cumbersome compared to how we can configure data types. 23 | 24 | With the **Doc Type Grid Editor** then, we bridge that gap, allowing you to reuse doc type definitions as blue prints for complex data to be rendered in a grid cell. 25 | 26 | --- 27 | 28 | ### Getting Set Up 29 | 30 | #### System Requirements 31 | 32 | Before you get started, there are a number of things you will need: 33 | 34 | 1. .NET 4.7.2+ 35 | 2. Umbraco 8.1.0+ 36 | 3. The **Doc Type Grid Editor** package installed 37 | 38 | --- 39 | 40 | ### Configuring The Doc Type Grid Editor 41 | 42 | The **Doc Type Grid Editor** is configured via the grid.editors.config.js config file located in the `~/Config` folder. A default configuration should be installed along with the package, but for details on the configuration options, please see below. 43 | 44 | You can also add your editors using a package.manifest file in a folder in the `~/App_Plugins` folder. 45 | 46 | #### Example 47 | 48 | ```javascript 49 | [ 50 | ... 51 | { 52 | "name": "Doc Type", 53 | "alias": "docType", 54 | "view": "/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.html", 55 | "render": "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml", 56 | "icon": "icon-item-arrangement", 57 | "config": { 58 | "allowedDocTypes": [...], 59 | "nameTemplate": "", 60 | "enablePreview": true, 61 | "overlaySize": "medium", 62 | "viewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/", 63 | "previewViewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/Previews/", 64 | "previewCssFilePath": "", 65 | "previewJsFilePath": "" 66 | } 67 | }, 68 | ... 69 | ] 70 | ``` 71 | 72 | For the main part, the root properties shouldn’t need to be modified, however the only properties that MUST not be changed are the **view** and **render** properties. 73 | 74 | | Member | Type | Description | 75 | |--------|--------|-------------| 76 | | Name | String | The name of the grid editor as it appears in the grid editor prevalue editor / selector screen. | 77 | | Alias | String | A unique alias for this grid editor. | 78 | | View | String | The path to the **Doc Type Grid Editor** editor view. **MUST NOT BE CHANGED**. | 79 | | Render | String | The path to the **Doc Type Grid Editor** render view. **MUST NOT BE CHANGED**. | 80 | | Icon | String | The icon class name to use for this grid editor (minus the '.') | 81 | | Config | Object | Config options for this grid editor. | 82 | 83 | The **Doc Type Grid Editor** supports 3 config options, all of which are optional. 84 | 85 | | Member | Type | Description | 86 | |-----------------|----------|-------------| 87 | | AllowedDocTypes | String[] | An array of doc type aliases of which should be allowed to be selected in the grid editor. By default Strings are REGEX patterns to allow matching groups of doc types in a single entry. e.g. "widgetAlias" will match all doc types with an alias starting in "widgetAlias". By adding "$" to the end of the string you can stop this behaviour e.g. "widgetAlias$" will only match a doc type with an alias of "widgetAlias". However if a single doc type is matched, (aka **Single Doc Type Mode**), then doc type selection stage (in the DTGE panel) will be skipped. Note, your document type must be an Element type, in order to be usable in DTGE. | 88 | | NameTemplate | String | Allows using any of the doctype's property values in the name/label: {{propertyAlias}} | 89 | | EnablePreview | Boolean | Enables rendering a preview of the grid cell in the grid editor. | 90 | | overlaySize | String | Define the size of the grid editor dialog. You can write `large`, `medium` or `small`. If no size is set, the grid editor dialog will be small. Note, the medium size requires a minimum Umbraco version of 8.3 | 91 | | LargeDialog | Boolean | (obsolete, use overlaySize instead) Makes the editing dialog larger. Especially useful for grid editors with complex property editors. | 92 | | size | string | (obsolete, use overlaySize instead) 93 | | ViewPath | String | Sets an alternative view path for where the **Doc Type Grid Editor** should look for views when rendering. Defaults to `~/Views/Partials/` | 94 | | PreviewViewPath | String | Sets an alternative view path for where the **Doc Type Grid Editor** should look for views when rendering previews in the backoffice | 95 | | ShowDocTypeSelectAsGrid | Boolean | Makes the content type selection dialog render a grid, in stead of the default list with descriptions | 96 | 97 | By default, a universal grid editor allowing all available element document types is added upon installation. 98 | 99 | Since Doc Type Grid Editor does not support culture variation, element document types allowing culture variations is not available for use in Doc Type Grid Editor. 100 | 101 | --- 102 | 103 | ### Hooking Up The Doc Type Grid Editor 104 | 105 | To hook up the **Doc Type Grid Editor**, within your grids prevalue, select the row configs you want to use the **Doc Type Grid Editor** in and for each cell config, check the **Doc Type** checkbox option to true. If you changed the name in the config, then select the item with the name you enter in the config. And, if you add your own editors either by package.manifest, or by editing grid.editors.config.js, you will need to select those too. 106 | 107 | ![Doc Type Grid Editor - Prevalue Editor](img/screenshot-01.png) 108 | 109 | With the Doc Type Grid Editor enabled, from within your grid editor, you should now have a new option in the **Choose type of content** dialog. 110 | 111 | ![Doc Type Grid Editor - Insert Control](img/screenshot-02.png) 112 | 113 | From there, simply click the **Doc Type** icon. If you have multiple document types matching the `AllowedDocTypes` setting in the selected grid editor (the default **Doc Type**, lets you pick between all available document types), you need to choose the document type you wish to render. 114 | 115 | ![Doc Type Grid Editor - Select Doc Type](img/screenshot-03b.png) 116 | 117 | If you have any Content Templates available for the selected document type, you need to choose which template to use for the content. 118 | 119 | ![Doc Type Grid Editor - Select Doc Type](img/screenshot-03.png) 120 | 121 | Then you should be presented with a form for all the fields in your document type. 122 | 123 | ![Doc Type Grid Editor - Doc Type Fields](img/screenshot-04.png) 124 | 125 | Fill in the fields and click Submit. You should then see the grid populated with a preview of your item. If you have disabled previews using the `EnablePreview` setting, an icon will be shown to represent your content block. 126 | 127 | ![Doc Type Grid Editor - Grid Preview](img/screenshot-05.png) 128 | 129 | Make sure save / save & publish the current page to persist your changes. 130 | 131 | --- 132 | 133 | ### Rendering a Doc Type Grid Editor 134 | 135 | The **Doc Type Grid Editor** uses standard ASP.NET MVC partials as the rendering mechanism. By default it will look for partial files in the `ViewPath` setting of your grid editor, or in the default partial view location (for exmaple `~/Views/Partials`). The rendering mechanism looks for a file with a name that matches the document type alias. For example, for the default setup, if your document type alias is `TestDocType`, the Doc Type Grid Editor will look for the partial file `~/Views/Partials/Grid/Editors/DocTypeGridEditor/TestDocType.cshtml`. 136 | 137 | To access the properties of your completed doc type, simply have your partial view inherit the standard `UmbracoViewPage` class, and you’ll be able to access them via the standard `Model` view property as a native `IPublishedElement` instance. 138 | 139 | ``` 140 | @inherits UmbracoViewPage 141 |

@Model.Name

142 | ``` 143 | 144 | Because we treat your data as a standard `IPublishedElement` entity, that means you can use all the property value converters you are used to using. 145 | 146 | ``` 147 | @inherits UmbracoViewPage 148 |

@Model.Name

149 | @Model.Value("bodyText") 150 | More 151 | ``` 152 | 153 | Doc Type Grid Editor also supports ModelsBuilder, so you can simplify your views like this: 154 | ``` 155 | @inherits UmbracoViewPage 156 |

@Model.Name

157 | @Model.BodyText 158 | More 159 | ``` 160 | 161 | ![Doc Type Grid Editor - Rendered Content](img/screenshot-06.png) 162 | 163 | 164 | #### Rendering Alternative Preview Content 165 | 166 | If your front end view is rather complex, you may decide that you want to feed the back office preview an alternative, less complex view. To do this, you can use the `PreviewViewPath` setting on your grid editor, and place a view named after your document type there. **Doc Type Grid Editor** will use the preview view file, when rendering previews in the backoffice. 167 | 168 | If you don't want to have a seperate preview view file, you can add preview logic within your Razor view/partial. Check for a querystring parameter `dtgePreview` being set to "1" to detect being in preview mode to provide an alternative view. 169 | 170 | ``` 171 | @inherits UmbracoViewPage 172 | @if (Request.QueryString["dtgePreview"] == "1") 173 | { 174 | // Render preview view 175 | } 176 | else 177 | { 178 | // Render front end view 179 | } 180 | ``` 181 | 182 | #### DocTypeGridEditorSurfaceController 183 | 184 | If you are not the type of developer that likes to put business logic in your views, then the ability to have a controller for you partial view is a must. To help with this, the **Doc Type Grid Editor** comes with a base surface controller you can used called `DocTypeGridEditorSurfaceController`. 185 | 186 | Simply create your controller inheriting from the above class, giving it a class name of `{DocTypeAlias}SurfaceController` and an action name of `{DocTypeAlias}` and the **Doc Type Grid Editor** will automatically wire it up for you and use it at render time. 187 | 188 | ```csharp 189 | public class TestDocTypeSurfaceController 190 | : DocTypeGridEditorSurfaceController 191 | { 192 | public ActionResult TestDocType() 193 | { 194 | // Do your thing... 195 | return CurrentPartialView(); 196 | } 197 | } 198 | ``` 199 | 200 | By inheriting from the `DocTypeGridEditorSurfaceController` base class, you'll also have instant access to the following helper properties / methods. 201 | 202 | | Member | Type | Description | 203 | |---------------------------------------------------|-------------------|-------------| 204 | | Model | IPublishedElement | The IPublishedElement instance of your cell's data. | 205 | | ViewPath | String | A reference to the currently configured ViewPath | 206 | | CurrentPartialView(object model = null) | Method | Helper method to return you to the default partial view for this cell. If no model is passed in, the standard Model will be passed down. | 207 | | PartialView(string viewName, object model = null) | Method | Helper method to return you to an alternative partial view for this cell. If no model is passed in, the standard Model will be passed down. | 208 | 209 | --- 210 | 211 | ### Value Processors 212 | Since Doc Type Grid Editor stores the data for each property as a JSON-blob, we're not processing the values in the same way as Umbraco-core before storing it. This also means that the values that comes back and are passed into a Property Value Converter might be in of a type/format that that Property Value Convert can't handle. 213 | 214 | We've added something that we call "ValueProcessors" and these can be used to modify the raw property value before we send it to the property value converter. One example of where this is needed is the Tags-editor. 215 | 216 | If you need to perform some processing of a value before it's sent to the property value converter you can add your own ValueProcessor. 217 | 218 | ```csharp 219 | public class UmbracoColorPickerValueProcessor : IDocTypeGridEditorValueProcessor 220 | { 221 | public bool IsProcessorFor(string propertyEditorAlias) 222 | => propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.ColorPicker); 223 | 224 | public object ProcessValue(object value) 225 | { 226 | // Do something with the value 227 | return value; 228 | } 229 | } 230 | ``` 231 | 232 | Then register this during composition withing IUserComposer, 233 | ```csharp 234 | public class MyCustomDocTypeGridEditorComposer : IUserComposer 235 | { 236 | public void Compose(Composition composition) 237 | { 238 | composition.DocTypeGridEditorValueProcessors() 239 | .Append(); 240 | } 241 | } 242 | ``` 243 | 244 | Dot Type Grid editor ships with a ValueProcessor for the Umbraco-Tags property. 245 | 246 | **Note:** When using a Tag-editor inside a DTGE this would not create any relationship between the current node and that tag, if you need to tag a node you should use the Tags-editor as a property directly on the document type. 247 | 248 | 249 | 250 | 251 | 252 | ### Useful Links 253 | 254 | * [Source Code](https://github.com/skttl/umbraco-doc-type-grid-editor) 255 | * [Our Umbraco Project Page](http://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor) 256 | -------------------------------------------------------------------------------- /docs/developers-guide-v2.md: -------------------------------------------------------------------------------- 1 | # Doc Type Grid Editor 2 - Developers Guide 2 | 3 | ### Contents 4 | 5 | 1. [Introduction](#introduction) 6 | 2. [Getting Set Up](#getting-set-up) 7 | 1. [System Requirements](#system-requirements) 8 | 3. [Configuring The Doc Type Grid Editor](#configuring-the-doc-type-grid-editor) 9 | 4. [Hooking Up The Doc Type Grid Editor](#hooking-up-the-doc-type-grid-editor) 10 | 5. [Rendering a Doc Type Grid Editor](#rendering-a-doc-type-grid-editor) 11 | 1. [Rendering Alternative Preview Content](#rendering-alternative-preview-content) 12 | 2. [DocTypeGridEditorSurfaceController](#doctypegrideditorsurfacecontroller) 13 | 6. [Value Processors](#value-processors) 14 | 7. [Useful Links](#useful-links) 15 | 16 | --- 17 | 18 | ### Introduction 19 | 20 | **Doc Type Grid Editor** is an advanced grid editor for the Umbraco grid, offering similar functionality as the macro grid editor but using the full power of the Doc Type editor and data types. 21 | 22 | With the macro grid editor you are limited to only using the macro builder and thus the handful of parameter editors that are available. Of course you can create / config your own parameter editors, however this is cumbersome compared to how we can configure data types. 23 | 24 | With the **Doc Type Grid Editor** then, we bridge that gap, allowing you to reuse doc type definitions as blue prints for complex data to be rendered in a grid cell. 25 | 26 | --- 27 | 28 | ### Getting Set Up 29 | 30 | #### System Requirements 31 | 32 | Before you get started, there are a number of things you will need: 33 | 34 | 1. .NET 5+ 35 | 2. Umbraco 9.0.0+ 36 | 3. The **Doc Type Grid Editor** package installed 37 | 38 | --- 39 | 40 | ### Configuring The Doc Type Grid Editor 41 | 42 | The **Doc Type Grid Editor** is configured via package.manifest files located in `~/App_Plugins/*`. A default configuration is installed along with the package in `~/App_Plugins/DocTypeGridEditor/package.manifest`. If you want to add your own editor configurations you can create your own package manifest file with the gridEditor config values (somewhere like `~/App_Plugins/DocTypeGridEditor.CustomEditors/package.manifest`). For details on the configuration options, please see below. 43 | 44 | #### Example 45 | 46 | ```javascript 47 | [ 48 | ... 49 | { 50 | "name": "Doc Type", 51 | "alias": "docType", 52 | "view": "/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.html", 53 | "render": "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml", 54 | "icon": "icon-item-arrangement", 55 | "config": { 56 | "allowedDocTypes": [...], 57 | "nameTemplate": "", 58 | "enablePreview": true, 59 | "overlaySize": "medium", 60 | "viewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/", 61 | "previewViewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/Previews/", 62 | "previewCssFilePath": "", 63 | "previewJsFilePath": "" 64 | } 65 | }, 66 | ... 67 | ] 68 | ``` 69 | 70 | For the main part, the root properties shouldn’t need to be modified, however the only properties that MUST not be changed are the **view** and **render** properties. 71 | 72 | | Member | Type | Description | 73 | |--------|--------|-------------| 74 | | Name | String | The name of the grid editor as it appears in the grid editor prevalue editor / selector screen. | 75 | | Alias | String | A unique alias for this grid editor. | 76 | | View | String | The path to the **Doc Type Grid Editor** editor view. **MUST NOT BE CHANGED**. | 77 | | Render | String | The path to the **Doc Type Grid Editor** render view. **MUST NOT BE CHANGED**. | 78 | | Icon | String | The icon class name to use for this grid editor (minus the '.') | 79 | | Config | Object | Config options for this grid editor. | 80 | 81 | The **Doc Type Grid Editor** supports 3 config options, all of which are optional. 82 | 83 | | Member | Type | Description | 84 | |-----------------|----------|-------------| 85 | | AllowedDocTypes | String[] | An array of doc type aliases of which should be allowed to be selected in the grid editor. By default Strings are REGEX patterns to allow matching groups of doc types in a single entry. e.g. "widgetAlias" will match all doc types with an alias starting in "widgetAlias". By adding "$" to the end of the string you can stop this behaviour e.g. "widgetAlias$" will only match a doc type with an alias of "widgetAlias". However if a single doc type is matched, (aka **Single Doc Type Mode**), then doc type selection stage (in the DTGE panel) will be skipped. Note, your document type must be an Element type, in order to be usable in DTGE. | 86 | | NameTemplate | String | Allows using any of the doctype's property values in the name/label: {{propertyAlias}} | 87 | | EnablePreview | Boolean | Enables rendering a preview of the grid cell in the grid editor. | 88 | | overlaySize | String | Define the size of the grid editor dialog. You can write `large`, `medium` or `small`. If no size is set, the grid editor dialog will be small. Note, the medium size requires a minimum Umbraco version of 8.3 | 89 | | LargeDialog | Boolean | (obsolete, use overlaySize instead) Makes the editing dialog larger. Especially useful for grid editors with complex property editors. | 90 | | size | string | (obsolete, use overlaySize instead) 91 | | ViewPath | String | Sets an alternative view path for where the **Doc Type Grid Editor** should look for views when rendering. Defaults to `~/Views/Partials/` | 92 | | PreviewViewPath | String | Sets an alternative view path for where the **Doc Type Grid Editor** should look for views when rendering previews in the backoffice | 93 | | ShowDocTypeSelectAsGrid | Boolean | Makes the content type selection dialog render a grid, in stead of the default list with descriptions | 94 | 95 | By default, a universal grid editor allowing all available element document types is added upon installation. 96 | 97 | **Since Doc Type Grid Editor does not support culture variation, element document types allowing culture variations is not available for use in Doc Type Grid Editor.** 98 | 99 | --- 100 | 101 | ### Hooking Up The Doc Type Grid Editor 102 | 103 | To hook up the **Doc Type Grid Editor**, within your grids prevalue, select the row configs you want to use the **Doc Type Grid Editor** in and for each cell config, check the **Doc Type** checkbox option to true. If you changed the name in the config, then select the item with the name you enter in the config. And, if you add your own editors either by package.manifest, or by editing grid.editors.config.js, you will need to select those too. 104 | 105 | ![Doc Type Grid Editor - Prevalue Editor](img/screenshot-01.png) 106 | 107 | With the Doc Type Grid Editor enabled, from within your grid editor, you should now have a new option in the **Choose type of content** dialog. 108 | 109 | ![Doc Type Grid Editor - Insert Control](img/screenshot-02.png) 110 | 111 | From there, simply click the **Doc Type** icon. If you have multiple document types matching the `AllowedDocTypes` setting in the selected grid editor (the default **Doc Type**, lets you pick between all available document types), you need to choose the document type you wish to render. 112 | 113 | ![Doc Type Grid Editor - Select Doc Type](img/screenshot-03b.png) 114 | 115 | If you have any Content Templates available for the selected document type, you need to choose which template to use for the content. 116 | 117 | ![Doc Type Grid Editor - Select Doc Type](img/screenshot-03.png) 118 | 119 | Then you should be presented with a form for all the fields in your document type. 120 | 121 | ![Doc Type Grid Editor - Doc Type Fields](img/screenshot-04.png) 122 | 123 | Fill in the fields and click Submit. You should then see the grid populated with a preview of your item. If you have disabled previews using the `EnablePreview` setting, an icon will be shown to represent your content block. 124 | 125 | ![Doc Type Grid Editor - Grid Preview](img/screenshot-05.png) 126 | 127 | Make sure save / save & publish the current page to persist your changes. 128 | 129 | --- 130 | 131 | ### Rendering a Doc Type Grid Editor 132 | 133 | The **Doc Type Grid Editor** uses standard ASP.NET MVC partials as the rendering mechanism. By default it will look for partial files in the `ViewPath` setting of your grid editor, or in the default partial view location (for exmaple `~/Views/Partials`). The rendering mechanism looks for a file with a name that matches the document type alias. For example, for the default setup, if your document type alias is `TestDocType`, the Doc Type Grid Editor will look for the partial file `~/Views/Partials/Grid/Editors/DocTypeGridEditor/TestDocType.cshtml`. 134 | 135 | To access the properties of your completed doc type, simply have your partial view inherit the standard `UmbracoViewPage` class, and you’ll be able to access them via the standard `Model` view property as a native `IPublishedElement` instance. 136 | 137 | ``` 138 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 139 |

@Model.Name

140 | ``` 141 | 142 | Because we treat your data as a standard `IPublishedElement` entity, that means you can use all the property value converters you are used to using. 143 | 144 | ``` 145 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 146 |

@Model.Name

147 | @Model.Value("bodyText") 148 | More 149 | ``` 150 | 151 | Doc Type Grid Editor also supports ModelsBuilder, so you can simplify your views like this: 152 | ``` 153 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 154 | @using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; 155 |

@Model.Name

156 | @Model.BodyText 157 | More 158 | ``` 159 | 160 | ![Doc Type Grid Editor - Rendered Content](img/screenshot-06.png) 161 | 162 | 163 | #### Rendering Alternative Preview Content 164 | 165 | If your front end view is rather complex, you may decide that you want to feed the back office preview an alternative, less complex view. To do this, you can use the `PreviewViewPath` setting on your grid editor, and place a view named after your document type there. **Doc Type Grid Editor** will use the preview view file, when rendering previews in the backoffice. 166 | 167 | If you don't want to have a seperate preview view file, you can add preview logic within your Razor view/partial. Check for a querystring parameter `dtgePreview` being set to "1" to detect being in preview mode to provide an alternative view. 168 | 169 | ``` 170 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 171 | @if (Request.QueryString["dtgePreview"] == "1") 172 | { 173 | // Render preview view 174 | } 175 | else 176 | { 177 | // Render front end view 178 | } 179 | ``` 180 | 181 | #### DocTypeGridEditorViewComponent 182 | Behind the scenes, Doc Type Grid Editor uses a ViewComponent to render all items. You can let it use the default component for all your editors if you like, but you can also specify your own document type specific, or editor specific view component. Or specify your own default view component. 183 | 184 | If you are not the type of developer that likes to put business logic in your views, then the ability to seperate logic from your view is a must. To render your grid editor item with a document type or editor specific view component, you need to create a view component, and give it a name in the format `{EditorAlias or Document Type Alias}DocTypeGridEditorViewComponent`. 185 | 186 | Note: The name of the viewcomponent is case sensitive, but DTGE is helpful enough to look for view components starting both with an uppercase letter and lowercase letter of your alias. 187 | 188 | The view component must implement a method called Invoke taking a dynamic model parameter, and a viewPath parameter as a string. The method should return an IViewComponentResult. As an example, the default view component does this: 189 | 190 | ```cs 191 | public IViewComponentResult Invoke(dynamic model, string viewPath) 192 | { 193 | return View(viewPath, model); 194 | } 195 | ``` 196 | 197 | #### Overriding the default view component 198 | Doc Type Grid Editor comes with a default view component, that simply takes the model of the editor and sends it to the correct view. If you want to override this ViewComponent and replace it with your own, you can do it in your Startup.cs file, in the ConfigureServices method. 199 | 200 | Add the following code, to configure DocTypeGridEditor: 201 | 202 | ```cs 203 | public void ConfigureServices(IServiceCollection services) 204 | { 205 | services.AddUmbraco(_env, _config) 206 | .AddBackOffice() 207 | .AddWebsite() 208 | .AddComposers() 209 | .Build(); 210 | 211 | services.SetDocTypeGridEditorSettings(c => 212 | { 213 | c.DefaultDocTypeGridEditorViewComponent = typeof(MyDocTypeGridEditorViewComponent); 214 | }); 215 | } 216 | ``` 217 | 218 | --- 219 | 220 | ### Value Processors 221 | Since Doc Type Grid Editor stores the data for each property as a JSON-blob, we're not processing the values in the same way as Umbraco-core before storing it. This also means that the values that comes back and are passed into a Property Value Converter might be in of a type/format that that Property Value Convert can't handle. 222 | 223 | We've added something that we call "ValueProcessors" and these can be used to modify the raw property value before we send it to the property value converter. One example of where this is needed is the Tags-editor. 224 | 225 | If you need to perform some processing of a value before it's sent to the property value converter you can add your own ValueProcessor. 226 | 227 | ```csharp 228 | public class UmbracoColorPickerValueProcessor : IDocTypeGridEditorValueProcessor 229 | { 230 | public bool IsProcessorFor(string propertyEditorAlias) 231 | => propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.ColorPicker); 232 | 233 | public object ProcessValue(object value) 234 | { 235 | // Do something with the value 236 | return value; 237 | } 238 | } 239 | ``` 240 | 241 | Then register this during composition withing IUserComposer, 242 | ```csharp 243 | public class MyCustomDocTypeGridEditorComposer : IUserComposer 244 | { 245 | public void Compose(Composition composition) 246 | { 247 | composition.DocTypeGridEditorValueProcessors() 248 | .Append(); 249 | } 250 | } 251 | ``` 252 | 253 | Dot Type Grid editor ships with a ValueProcessor for the Umbraco-Tags property. 254 | 255 | **Note:** When using a Tag-editor inside a DTGE this would not create any relationship between the current node and that tag, if you need to tag a node you should use the Tags-editor as a property directly on the document type. 256 | 257 | TODO: How to do this with IUmbracoBuilder? 258 | 259 | 260 | 261 | 262 | 263 | ### Useful Links 264 | 265 | * [Source Code](https://github.com/skttl/umbraco-doc-type-grid-editor) 266 | * [Our Umbraco Project Page](http://our.umbraco.org/projects/backoffice-extensions/doc-type-grid-editor) 267 | -------------------------------------------------------------------------------- /docs/img/doc-type-grid-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/doc-type-grid-editor.png -------------------------------------------------------------------------------- /docs/img/doc-type-grid-editor.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/doc-type-grid-editor.psd -------------------------------------------------------------------------------- /docs/img/screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-01.png -------------------------------------------------------------------------------- /docs/img/screenshot-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-02.png -------------------------------------------------------------------------------- /docs/img/screenshot-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-03.png -------------------------------------------------------------------------------- /docs/img/screenshot-03b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-03b.png -------------------------------------------------------------------------------- /docs/img/screenshot-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-04.png -------------------------------------------------------------------------------- /docs/img/screenshot-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-05.png -------------------------------------------------------------------------------- /docs/img/screenshot-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/umbraco-doc-type-grid-editor/1ae7f19d45f7f4ed9801229c1cad238f3fa1f530/docs/img/screenshot-06.png -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Our.Umbraco.DocTypeGridEditor", "Our.Umbraco.DocTypeGridEditor\Our.Umbraco.DocTypeGridEditor.csproj", "{D4318AFD-033C-46FC-9DD2-9FC57934CD2F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4933D56D-BD6F-4C2D-A9CF-40B90148E933}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\CHANGELOG.md = ..\CHANGELOG.md 11 | ..\CONTRIBUTING.md = ..\CONTRIBUTING.md 12 | ..\FUNDING.yml = ..\FUNDING.yml 13 | ..\README.md = ..\README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{8E6B61BE-CD4E-4C09-B386-81AF21ABE55D}" 17 | ProjectSection(SolutionItems) = preProject 18 | ..\docs\developers-guide-v1.md = ..\docs\developers-guide-v1.md 19 | ..\docs\developers-guide-v2.md = ..\docs\developers-guide-v2.md 20 | ..\docs\README.md = ..\docs\README.md 21 | EndProjectSection 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {D4318AFD-033C-46FC-9DD2-9FC57934CD2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {D4318AFD-033C-46FC-9DD2-9FC57934CD2F}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {D4318AFD-033C-46FC-9DD2-9FC57934CD2F}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {D4318AFD-033C-46FC-9DD2-9FC57934CD2F}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(NestedProjects) = preSolution 38 | {8E6B61BE-CD4E-4C09-B386-81AF21ABE55D} = {4933D56D-BD6F-4C2D-A9CF-40B90148E933} 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {66EC167C-845A-4DCC-87B7-C7DB5CC1E38B} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml: -------------------------------------------------------------------------------- 1 | @using Our.Umbraco.DocTypeGridEditor.Helpers 2 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 3 | @inject DocTypeGridEditorHelper DocTypeGridEditorHelper 4 | @DocTypeGridEditorHelper.RenderDocTypeGridEditorItem(Component, Html, Model) -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditorPreviewer.cshtml: -------------------------------------------------------------------------------- 1 | @using Our.Umbraco.DocTypeGridEditor.Helpers 2 | @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage 3 | @inject DocTypeGridEditorHelper DocTypeGridEditorHelper 4 | @DocTypeGridEditorHelper.RenderDocTypeGridEditorItem(Component, Html, Model.Item, Model.EditorAlias, Model.ViewPath, Model.PreviewViewPath, true) -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Config/DocTypeGridEditorSettings.cs: -------------------------------------------------------------------------------- 1 | using Our.Umbraco.DocTypeGridEditor.ViewComponents; 2 | using System; 3 | 4 | namespace Our.Umbraco.DocTypeGridEditor.Config 5 | { 6 | public class DocTypeGridEditorSettings 7 | { 8 | public Type DefaultDocTypeGridEditorViewComponent { get; set; } = typeof(DocTypeGridEditorViewComponent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Controllers/DocTypeGridEditorApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http.Extensions; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | using Our.Umbraco.DocTypeGridEditor.Helpers; 6 | using Our.Umbraco.DocTypeGridEditor.Models; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Globalization; 10 | using System.Linq; 11 | using System.Text.RegularExpressions; 12 | using Umbraco.Cms.Core; 13 | using Umbraco.Cms.Core.Models; 14 | using Umbraco.Cms.Core.Models.PublishedContent; 15 | using Umbraco.Cms.Core.PropertyEditors; 16 | using Umbraco.Cms.Core.Routing; 17 | using Umbraco.Cms.Core.Services; 18 | using Umbraco.Cms.Core.Strings; 19 | using Umbraco.Cms.Core.Web; 20 | using Umbraco.Cms.Web.BackOffice.Controllers; 21 | using Umbraco.Cms.Web.Common.Attributes; 22 | using Umbraco.Extensions; 23 | 24 | namespace Our.Umbraco.DocTypeGridEditor.Controllers 25 | { 26 | [PluginController("DocTypeGridEditorApi")] 27 | public class DocTypeGridEditorApiController : UmbracoAuthorizedJsonController 28 | { 29 | private readonly IUmbracoContextAccessor _umbracoContext; 30 | private readonly IVariationContextAccessor _variationContextAccessor; 31 | private readonly IContentTypeService _contentTypeService; 32 | private readonly IContentService _contentService; 33 | private readonly IDataTypeService _dataTypeService; 34 | private readonly IShortStringHelper _shortStringHelper; 35 | private readonly IPublishedContentQuery _contentQuery; 36 | private readonly IPublishedRouter _router; 37 | private readonly DocTypeGridEditorHelper _dtgeHelper; 38 | private readonly ServiceContext _serviceContext; 39 | private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; 40 | private readonly PropertyEditorCollection _propertyEditorCollection; 41 | 42 | public DocTypeGridEditorApiController(IUmbracoContextAccessor umbracoContext, 43 | IVariationContextAccessor variationContextAccessor, 44 | IContentTypeService contentTypeService, 45 | IContentService contentService, 46 | IDataTypeService dataTypeService, 47 | IShortStringHelper shortStringHelper, 48 | IPublishedContentQuery contentQuery, 49 | IPublishedRouter router, 50 | ServiceContext serviceContext, 51 | IPublishedContentTypeFactory publishedContentTypeFactory, 52 | PropertyEditorCollection propertyEditorCollection, 53 | DocTypeGridEditorHelper dtgeHelper) 54 | { 55 | _umbracoContext = umbracoContext; 56 | _variationContextAccessor = variationContextAccessor; 57 | _contentTypeService = contentTypeService; 58 | _contentService = contentService; 59 | _dataTypeService = dataTypeService; 60 | _shortStringHelper = shortStringHelper; 61 | _contentQuery = contentQuery; 62 | _router = router; 63 | _dtgeHelper = dtgeHelper; 64 | _serviceContext = serviceContext; 65 | _publishedContentTypeFactory = publishedContentTypeFactory; 66 | _propertyEditorCollection = propertyEditorCollection; 67 | } 68 | 69 | [HttpGet] 70 | public object GetContentTypeAliasByGuid([FromQuery] Guid guid) 71 | { 72 | return new 73 | { 74 | alias = _contentTypeService.GetAllContentTypeAliases(guid).FirstOrDefault() 75 | }; 76 | } 77 | 78 | [HttpGet] 79 | public IEnumerable GetContentTypes([FromQuery]string[] allowedContentTypes) 80 | { 81 | var allContentTypes = _contentTypeService.GetAll().ToList(); 82 | var contentTypes = allContentTypes 83 | .Where(x => x.IsElement && x.VariesByCulture() == false) 84 | .Where(x => allowedContentTypes == null || allowedContentTypes.Length == 0 || allowedContentTypes.Any(y => Regex.IsMatch(x.Alias, y))) 85 | .OrderBy(x => x.Name) 86 | .ToList(); 87 | 88 | var blueprints = _contentService.GetBlueprintsForContentTypes(contentTypes.Select(x => x.Id).ToArray()).ToArray(); 89 | 90 | return contentTypes 91 | .Select(x => new 92 | { 93 | id = x.Id, 94 | guid = x.Key, 95 | name = x.Name, 96 | alias = x.Alias, 97 | description = x.Description, 98 | icon = x.Icon, 99 | blueprints = blueprints.Where(bp => bp.ContentTypeId == x.Id).Select(bp => new 100 | { 101 | id = bp.Id, 102 | name = bp.Name 103 | }) 104 | }); 105 | } 106 | 107 | [HttpGet] 108 | public object GetContentType([FromQuery]string contentTypeAlias) 109 | { 110 | Guid docTypeGuid; 111 | if (Guid.TryParse(contentTypeAlias, out docTypeGuid)) 112 | contentTypeAlias = _contentTypeService.GetAllContentTypeAliases(docTypeGuid).FirstOrDefault(); 113 | 114 | var contentType = _contentTypeService.Get(contentTypeAlias); 115 | return new 116 | { 117 | icon = contentType != null ? contentType.Icon : "icon-science", 118 | title = contentType != null ? contentType.Name : "Doc Type", 119 | description = contentType != null ? contentType.Description : string.Empty 120 | }; 121 | } 122 | 123 | [HttpGet] 124 | public object GetDataTypePreValues([FromQuery]string dtdId) 125 | { 126 | Guid guidDtdId; 127 | int intDtdId; 128 | 129 | IDataType dtd; 130 | 131 | // Parse the ID 132 | if (int.TryParse(dtdId, out intDtdId)) 133 | { 134 | // Do nothing, we just want the int ID 135 | dtd = _dataTypeService.GetDataType(intDtdId); 136 | } 137 | else if (Guid.TryParse(dtdId, out guidDtdId)) 138 | { 139 | dtd = _dataTypeService.GetDataType(guidDtdId); 140 | } 141 | else 142 | { 143 | return null; 144 | } 145 | 146 | if (dtd == null) 147 | return null; 148 | 149 | // Convert to editor config 150 | var dataType = _dataTypeService.GetDataType(dtd.Id); 151 | var propEditor = dataType.Editor; 152 | var content = propEditor.GetValueEditor().ConvertDbToString(new PropertyType(_shortStringHelper, dataType), dataType.Configuration); 153 | return content; 154 | } 155 | 156 | [HttpPost] 157 | public PartialViewResult GetPreviewMarkup([FromForm] PreviewData data, [FromQuery]int pageId) 158 | { 159 | var page = default(IPublishedContent); 160 | 161 | // If the page is new, then the ID will be zero 162 | if (pageId > 0) 163 | { 164 | // Get page container node 165 | page = _contentQuery.Content(pageId); 166 | if (page == null) 167 | { 168 | // If unpublished, then fake PublishedContent 169 | page = new UnpublishedContent(pageId, _contentService, _contentTypeService, _dataTypeService, _propertyEditorCollection, _publishedContentTypeFactory); 170 | } 171 | } 172 | 173 | 174 | if (_umbracoContext.GetRequiredUmbracoContext().PublishedRequest == null) 175 | { 176 | var request = _router.CreateRequestAsync(new Uri(Request.GetDisplayUrl())).Result; 177 | request.SetPublishedContent(page); 178 | _umbracoContext.GetRequiredUmbracoContext().PublishedRequest = request.Build(); 179 | } 180 | 181 | // Set the culture for the preview 182 | if (page != null && page.Cultures != null) 183 | { 184 | var currentCulture = string.IsNullOrWhiteSpace(data.Culture) ? page.GetCultureFromDomains() : data.Culture; 185 | if (currentCulture != null && page.Cultures.ContainsKey(currentCulture)) 186 | { 187 | var culture = new CultureInfo(page.Cultures[currentCulture].Culture); 188 | System.Threading.Thread.CurrentThread.CurrentCulture = culture; 189 | System.Threading.Thread.CurrentThread.CurrentUICulture = culture; 190 | _variationContextAccessor.VariationContext = new VariationContext(culture.Name); 191 | } 192 | } 193 | 194 | // Get content node object 195 | var content = _dtgeHelper.ConvertValueToContent(data.Id, data.ContentTypeAlias, data.Value); 196 | 197 | // Construct preview model 198 | var model = new PreviewModel 199 | { 200 | Page = page, 201 | Item = content, 202 | EditorAlias = data.EditorAlias, 203 | PreviewViewPath = data.PreviewViewPath, 204 | ViewPath = data.ViewPath 205 | }; 206 | 207 | 208 | // Render view 209 | 210 | var partialName = "~/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditorPreviewer.cshtml"; 211 | 212 | var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); 213 | viewData.Model = model; 214 | return new PartialViewResult() 215 | { 216 | ViewName = partialName, 217 | ViewData = viewData 218 | }; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Controllers/DocTypeGridEditorBlueprintApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Umbraco.Cms.Core.Cache; 10 | using Umbraco.Cms.Core.Configuration.Models; 11 | using Umbraco.Cms.Core.Logging; 12 | using Umbraco.Cms.Core.Models.ContentEditing; 13 | using Umbraco.Cms.Core.PropertyEditors; 14 | using Umbraco.Cms.Core.PublishedCache; 15 | using Umbraco.Cms.Core.Services; 16 | using Umbraco.Cms.Core.Web; 17 | using Umbraco.Cms.Infrastructure.Persistence; 18 | using Umbraco.Cms.Web.BackOffice.Controllers; 19 | using Umbraco.Cms.Web.BackOffice.Filters; 20 | using Umbraco.Cms.Web.BackOffice.ModelBinders; 21 | using Umbraco.Cms.Web.Common.Attributes; 22 | using Umbraco.Cms.Infrastructure.Scoping; 23 | using Umbraco.Cms.Core.Dictionary; 24 | using Microsoft.Extensions.Logging; 25 | using Umbraco.Cms.Core.Strings; 26 | using Umbraco.Cms.Core.Events; 27 | using Umbraco.Cms.Core.Security; 28 | using Umbraco.Cms.Core.Mapping; 29 | using Umbraco.Cms.Core.Routing; 30 | using Umbraco.Cms.Core.Actions; 31 | using Umbraco.Cms.Core.Serialization; 32 | using Umbraco.Cms.Core.Scoping; 33 | using Microsoft.AspNetCore.Authorization; 34 | 35 | namespace Our.Umbraco.DocTypeGridEditor.Controllers 36 | { 37 | [PluginController("DocTypeGridEditorApi")] 38 | public class DocTypeGridEditorBlueprintApiController : UmbracoAuthorizedApiController 39 | { 40 | private readonly IContentService _contentService; 41 | private ContentController _contentController; 42 | 43 | public DocTypeGridEditorBlueprintApiController(ICultureDictionary cultureDictionary, ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IEventMessagesFactory eventMessages, ILocalizedTextService localizedTextService, PropertyEditorCollection propertyEditors, IContentService contentService, IUserService userService, IBackOfficeSecurityAccessor backofficeSecurityAccessor, IContentTypeService contentTypeService, IUmbracoMapper umbracoMapper, IPublishedUrlProvider publishedUrlProvider, IDomainService domainService, IDataTypeService dataTypeService, ILocalizationService localizationService, IFileService fileService, INotificationService notificationService, ActionCollection actionCollection, ISqlContext sqlContext, IJsonSerializer serializer, ICoreScopeProvider scopeProvider, IAuthorizationService authorizationService, IContentVersionService contentVersionService, ICultureImpactFactory cultureImpactFactory) 44 | { 45 | _contentService = contentService; 46 | _contentController = new ContentController(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, propertyEditors, contentService, userService, backofficeSecurityAccessor, contentTypeService, umbracoMapper, publishedUrlProvider, domainService, dataTypeService, localizationService, fileService, notificationService, actionCollection, sqlContext, serializer, scopeProvider, authorizationService, contentVersionService, cultureImpactFactory); 47 | } 48 | 49 | [FileUploadCleanupFilter] 50 | [HttpPost] 51 | public async Task?>?> PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) 52 | { 53 | return await _contentController.PostSaveBlueprint(contentItem); 54 | } 55 | [HttpDelete] 56 | [HttpPost] 57 | public IActionResult DeleteBlueprint(int id) 58 | { 59 | var found = _contentService.GetBlueprintById(id); 60 | 61 | if (found == null) 62 | { 63 | return BadRequest(); 64 | } 65 | 66 | _contentService.DeleteBlueprint(found); 67 | 68 | return Ok(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/DocTypeGridEditorComposer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Our.Umbraco.DocTypeGridEditor.Events; 3 | using Our.Umbraco.DocTypeGridEditor.Helpers; 4 | using Our.Umbraco.DocTypeGridEditor.ValueProcessing; 5 | using Umbraco.Cms.Core.Composing; 6 | using Umbraco.Cms.Core.DependencyInjection; 7 | using Umbraco.Cms.Core.Notifications; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor 10 | { 11 | public class DocTypeGridEditorComposer : IComposer 12 | { 13 | public void Compose(IUmbracoBuilder builder) 14 | { 15 | builder.ManifestFilters().Append(); 16 | builder.DocTypeGridEditorValueProcessors().Append(); 17 | builder.DataValueReferenceFactories().Append(); 18 | builder.Services.AddSingleton(); 19 | 20 | // Add Event handlers 21 | builder.AddNotificationHandler(); 22 | builder.AddNotificationHandler(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/DocTypeGridEditorManifestFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Umbraco.Cms.Core.Manifest; 4 | using Umbraco.Cms.Core.PropertyEditors; 5 | 6 | namespace Our.Umbraco.DocTypeGridEditor 7 | { 8 | public class DocTypeGridEditorManifestFilter : IManifestFilter 9 | { 10 | public void Filter(List manifests) 11 | { 12 | manifests.Add(new PackageManifest() 13 | { 14 | AllowPackageTelemetry = true, 15 | PackageName = "Doc Type Grid Editor", 16 | GridEditors = new[] 17 | { 18 | new GridEditor() 19 | { 20 | Alias = "docType", 21 | Name = "Doc Type", 22 | View = "/App_Plugins/DocTypeGridEditor/Views/DocTypeGridEditor.html", 23 | Render = "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml", 24 | Icon = "icon-science", 25 | Config = new Dictionary() 26 | { 27 | { "allowedDocTypes", Array.Empty() }, 28 | { "nameTemplate", "" }, 29 | { "enablePreview", true }, 30 | { "viewPath", "/Views/Partials/grid/editors/DocTypeGridEditor/" }, 31 | { "previewViewPath", "/Views/Partials/grid/editors/DocTypeGridEditor/Previews/" }, 32 | { "previewCssFilePath", "" }, 33 | { "previewJsFilePath", "" } 34 | } 35 | } 36 | }, 37 | Scripts = new[] 38 | { 39 | "/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.resources.js", 40 | "/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.services.js", 41 | "/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.controllers.js", 42 | "/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.directives.js" 43 | }, 44 | Stylesheets = new[] 45 | { 46 | "/App_Plugins/DocTypeGridEditor/Css/doctypegrideditor.css" 47 | } 48 | 49 | }); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Events/ContentTypeCacheRefreshHandler.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using Umbraco.Cms.Core.Cache; 4 | using Umbraco.Cms.Core.Events; 5 | using Umbraco.Cms.Core.Notifications; 6 | using Umbraco.Cms.Core.Sync; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.Events 9 | { 10 | public class ContentTypeCacheRefreshHandler : INotificationHandler 11 | { 12 | private readonly AppCaches _appCaches; 13 | 14 | public ContentTypeCacheRefreshHandler(AppCaches appCaches) 15 | { 16 | _appCaches = appCaches; 17 | } 18 | 19 | public void Handle(ContentTypeCacheRefresherNotification notification) 20 | { 21 | if (notification.MessageType == MessageType.RefreshByJson) 22 | { 23 | var payload = JsonConvert.DeserializeAnonymousType((string)notification.MessageObject, 24 | new[] { new { Alias = default(string) } }); 25 | if (payload != null) 26 | { 27 | foreach (var item in payload) 28 | { 29 | _appCaches.RuntimeCache.ClearByKey( 30 | string.Concat( 31 | "Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetContentTypesByAlias_", 32 | item.Alias)); 33 | 34 | // NOTE: Unsure how to get the doctype GUID, without hitting the database? 35 | // So we end up clearing the entire cache for this key. [LK:2018-01-30] 36 | _appCaches.RuntimeCache.ClearByKey( 37 | "Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetContentTypeAliasByGuid_"); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Events/DataTypeCacheRefreshHandler.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using Umbraco.Cms.Core.Cache; 4 | using Umbraco.Cms.Core.Events; 5 | using Umbraco.Cms.Core.Notifications; 6 | using Umbraco.Cms.Core.Sync; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.Events 9 | { 10 | public class DataTypeCacheRefreshHandler : INotificationHandler 11 | { 12 | private readonly AppCaches _appCaches; 13 | 14 | public DataTypeCacheRefreshHandler(AppCaches appCaches) 15 | { 16 | _appCaches = appCaches; 17 | } 18 | 19 | public void Handle(DataTypeCacheRefresherNotification notification) 20 | { 21 | if (notification.MessageType == MessageType.RefreshByJson) 22 | { 23 | var payload = JsonConvert.DeserializeAnonymousType((string)notification.MessageObject, 24 | new[] { new { Id = default(int), UniqueId = default(Guid) } }); 25 | if (payload != null) 26 | { 27 | foreach (var item in payload) 28 | { 29 | _appCaches.RuntimeCache.ClearByKey( 30 | string.Concat( 31 | "Our.Umbraco.DocTypeGridEditor.Web.Extensions.ContentTypeServiceExtensions.GetAliasById_", 32 | item.UniqueId)); 33 | 34 | _appCaches.RuntimeCache.ClearByKey( 35 | string.Concat( 36 | "Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetPreValuesCollectionByDataTypeId_", 37 | item.Id)); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Helpers/DocTypeGridEditorHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Html; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | using Microsoft.AspNetCore.Mvc.ViewComponents; 5 | using Microsoft.AspNetCore.Mvc.ViewEngines; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | using Our.Umbraco.DocTypeGridEditor.Config; 12 | using Our.Umbraco.DocTypeGridEditor.Models; 13 | using Our.Umbraco.DocTypeGridEditor.ValueProcessing.Collections; 14 | using System; 15 | using System.Collections.Generic; 16 | using System.Linq; 17 | using Umbraco.Cms.Core.Cache; 18 | using Umbraco.Cms.Core.Models; 19 | using Umbraco.Cms.Core.Models.Editors; 20 | using Umbraco.Cms.Core.Models.PublishedContent; 21 | using Umbraco.Cms.Core.PropertyEditors; 22 | using Umbraco.Cms.Core.Routing; 23 | using Umbraco.Cms.Core.Services; 24 | using Umbraco.Cms.Core.Web; 25 | using Umbraco.Extensions; 26 | 27 | namespace Our.Umbraco.DocTypeGridEditor.Helpers 28 | { 29 | public class DocTypeGridEditorHelper 30 | { 31 | private readonly AppCaches _appCaches; 32 | private readonly IUmbracoContextAccessor _umbracoContext; 33 | private readonly PropertyEditorCollection _dataEditors; 34 | private readonly ILogger _logger; 35 | private readonly IDataTypeService _dataTypeService; 36 | private readonly IPublishedModelFactory _publishedModelFactory; 37 | private readonly IContentTypeService _contentTypeService; 38 | private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; 39 | private readonly IViewComponentSelector _viewComponentSelector; 40 | private readonly DocTypeGridEditorValueProcessorsCollection _docTypeGridEditorValueProcessorsCollection; 41 | private readonly DocTypeGridEditorSettings _options; 42 | 43 | public DocTypeGridEditorHelper( 44 | AppCaches appCaches, 45 | IUmbracoContextAccessor umbracoContextAccessor, 46 | PropertyEditorCollection dataEditors, 47 | ILoggerFactory loggerFactory, 48 | IDataTypeService dataTypeService, 49 | IPublishedModelFactory publishedModelFactory, 50 | IContentTypeService contentTypeService, 51 | IPublishedContentTypeFactory publishedContentTypeFactory, 52 | IViewComponentSelector viewComponentSelector, 53 | DocTypeGridEditorValueProcessorsCollection docTypeGridEditorValueProcessorsCollection, 54 | IOptions options) 55 | { 56 | _appCaches = appCaches; 57 | _umbracoContext = umbracoContextAccessor; 58 | _dataEditors = dataEditors; 59 | _logger = loggerFactory.CreateLogger(); 60 | _dataTypeService = dataTypeService; 61 | _publishedModelFactory = publishedModelFactory; 62 | _contentTypeService = contentTypeService; 63 | _publishedContentTypeFactory = publishedContentTypeFactory; 64 | _viewComponentSelector = viewComponentSelector; 65 | _docTypeGridEditorValueProcessorsCollection = docTypeGridEditorValueProcessorsCollection; 66 | _options = options.Value; 67 | 68 | } 69 | public IPublishedElement ConvertValueToContent(string id, string contentTypeAlias, string dataJson) 70 | { 71 | if (string.IsNullOrWhiteSpace(contentTypeAlias)) 72 | return null; 73 | 74 | if (dataJson == null) 75 | return null; 76 | 77 | if (_umbracoContext.GetRequiredUmbracoContext() == null) 78 | return ConvertValue(id, contentTypeAlias, dataJson); 79 | 80 | return (IPublishedElement)_appCaches.RequestCache.Get( 81 | $"Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.ConvertValueToContent_{id}_{contentTypeAlias}", 82 | () => 83 | { 84 | return ConvertValue(id, contentTypeAlias, dataJson); 85 | }); 86 | } 87 | 88 | private IPublishedElement ConvertValue(string id, string contentTypeAlias, string dataJson) 89 | { 90 | var contentTypes = GetContentTypesByAlias(contentTypeAlias); 91 | var properties = new List(); 92 | 93 | // Convert all the properties 94 | var data = JsonConvert.DeserializeObject(dataJson); 95 | var propValues = ((JObject)data).ToObject>(); 96 | foreach (var jProp in propValues) 97 | { 98 | var propType = contentTypes.PublishedContentType.GetPropertyType(jProp.Key); 99 | if (propType == null) 100 | continue; 101 | 102 | 103 | /* Because we never store the value in the database, we never run the property editors 104 | * "ConvertEditorToDb" method however the property editors will expect their value to 105 | * be in a "DB" state so to get round this, we run the "ConvertEditorToDb" here before 106 | * we go on to convert the value for the view. 107 | */ 108 | _dataEditors.TryGet(propType.EditorAlias, out var propEditor); 109 | var propPreValues = GetPreValuesCollectionByDataTypeId(propType.DataType.Id); 110 | 111 | var contentPropData = new ContentPropertyData(jProp.Value, propPreValues); 112 | 113 | var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, jProp.Value); 114 | 115 | // Performing "ValueProcessing" if any ValueProcessor is configured for this Property Editor-alias. 116 | var processor = _docTypeGridEditorValueProcessorsCollection.FirstOrDefault(x => x.IsProcessorFor(propEditor.Alias)); 117 | if (processor != null) 118 | { 119 | newValue = processor.ProcessValue(newValue); 120 | } 121 | 122 | /* Now that we have the DB stored value, we actually need to then convert it into its 123 | * XML serialized state as expected by the published property by calling ConvertDbToString 124 | */ 125 | var propType2 = contentTypes.ContentType.CompositionPropertyTypes.First(x => x.PropertyEditorAlias.InvariantEquals(propType.DataType.EditorAlias)); 126 | 127 | Property prop2 = null; 128 | try 129 | { 130 | /* HACK: [LK:2016-04-01] When using the "Umbraco.Tags" property-editor, the converted DB value does 131 | * not match the datatypes underlying db-column type. So it throws a "Type validation failed" exception. 132 | * We feel that the Umbraco core isn't handling the Tags value correctly, as it should be the responsiblity 133 | * of the "Umbraco.Tags" property-editor to handle the value conversion into the correct type. 134 | * See: http://issues.umbraco.org/issue/U4-8279 135 | */ 136 | prop2 = new Property(propType2); 137 | prop2.SetValue(newValue); 138 | } 139 | catch (Exception ex) 140 | { 141 | _logger.LogError(new EventId(0), ex, "[DocTypeGridEditor] Error creating Property object."); 142 | } 143 | 144 | if (prop2 != null) 145 | { 146 | var newValue2 = propEditor.GetValueEditor().ConvertDbToString(propType2, newValue); 147 | 148 | properties.Add(new DetachedPublishedProperty(propType, newValue2)); 149 | } 150 | } 151 | 152 | // Manually parse out the special properties 153 | propValues.TryGetValue("name", out object nameObj); 154 | Guid.TryParse(id, out Guid key); 155 | 156 | // Get the current request node we are embedded in 157 | 158 | var pcr = _umbracoContext.GetRequiredUmbracoContext().PublishedRequest; 159 | var containerNode = pcr != null && pcr.HasPublishedContent() ? pcr.PublishedContent : null; 160 | 161 | // Create the model based on our implementation of IPublishedElement 162 | IPublishedElement content = new DetachedPublishedElement( 163 | key, 164 | contentTypes.PublishedContentType, 165 | properties.ToArray()); 166 | 167 | if (_publishedModelFactory != null) 168 | { 169 | // Let the current model factory create a typed model to wrap our model 170 | content = _publishedModelFactory.CreateModel(content); 171 | } 172 | 173 | return content; 174 | 175 | } 176 | 177 | private object GetPreValuesCollectionByDataTypeId(int dataTypeId) 178 | { 179 | return _appCaches.RuntimeCache.GetCacheItem( 180 | string.Concat( 181 | "Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetPreValuesCollectionByDataTypeId_", 182 | dataTypeId), 183 | () => _dataTypeService.GetDataType(dataTypeId).Configuration); 184 | } 185 | 186 | private ContentTypeContainer GetContentTypesByAlias(string contentTypeAlias) 187 | { 188 | if (Guid.TryParse(contentTypeAlias, out Guid contentTypeGuid)) 189 | contentTypeAlias = GetContentTypeAliasByGuid(contentTypeGuid); 190 | 191 | return _appCaches.RuntimeCache.GetCacheItem( 192 | string.Concat("Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetContentTypesByAlias_", contentTypeAlias), 193 | () => new ContentTypeContainer 194 | { 195 | PublishedContentType = new PublishedContentType(_contentTypeService.Get(contentTypeAlias), _publishedContentTypeFactory), 196 | ContentType = _contentTypeService.Get(contentTypeAlias) 197 | }); 198 | } 199 | 200 | private string GetContentTypeAliasByGuid(Guid contentTypeGuid) 201 | { 202 | return _appCaches.RuntimeCache.GetCacheItem( 203 | string.Concat("Our.Umbraco.DocTypeGridEditor.Helpers.DocTypeGridEditorHelper.GetContentTypeAliasByGuid_", contentTypeGuid), 204 | () => _contentTypeService.GetAllContentTypeAliases(contentTypeGuid).FirstOrDefault()); 205 | } 206 | 207 | public IHtmlContent RenderDocTypeGridEditorItem( 208 | IViewComponentHelper helper, 209 | IHtmlHelper htmlHelper, 210 | dynamic model) 211 | { 212 | if (model.value != null) 213 | { 214 | string id = model.value.id.ToString(); 215 | string editorAlias = model.editor.alias.ToString(); 216 | string contentTypeAlias = (model.value.dtgeContentTypeAlias ?? model.value.docType).ToString(); 217 | string value = model.value.value.ToString(); 218 | string viewPath = model.editor.config.viewPath.ToString(); 219 | 220 | var content = ConvertValue(id, contentTypeAlias, value); 221 | return RenderDocTypeGridEditorItem(helper, htmlHelper, content, editorAlias, viewPath); 222 | } 223 | 224 | return null; 225 | } 226 | 227 | public IHtmlContent RenderDocTypeGridEditorItem( 228 | IViewComponentHelper helper, 229 | IHtmlHelper htmlHelper, 230 | IPublishedElement content, 231 | string editorAlias = "", 232 | string viewPath = "", 233 | string previewViewPath = "", 234 | bool isPreview = false) 235 | { 236 | if (content == null) 237 | { 238 | _logger.LogError("Failed rendering DocTypeGridEditorItem. " + 239 | "content is null"); 240 | return new HtmlString(""); 241 | } 242 | 243 | if (!TryGetViewPath(htmlHelper.ViewContext, editorAlias, content.ContentType.Alias, viewPath, previewViewPath, isPreview, out string fullViewPath)) 244 | { 245 | _logger.LogError("Failed rendering DocTypeGridEditorItem. " + 246 | "Could not get viewpath. " + 247 | "{editorAlias}, {content.ContentType.Alias}, {viewPath}, {previewViewPath}, {isPreview}, {fullViewPath}", editorAlias, content.ContentType.Alias, viewPath, previewViewPath, isPreview, fullViewPath); 248 | return new HtmlString(""); 249 | } 250 | 251 | var componentName = GetComponentName(new[] { editorAlias, content.ContentType.Alias }); 252 | return helper.InvokeAsync(componentName, new { model = content, viewPath = fullViewPath }).Result; 253 | } 254 | 255 | private bool TryGetViewPath(ViewContext viewContext, string editorAlias, string contentTypeAlias, string viewPath, string previewViewPath, bool isPreview, out string fullViewPath) 256 | { 257 | fullViewPath = ""; 258 | 259 | if (isPreview && !previewViewPath.IsNullOrWhiteSpace()) 260 | { 261 | previewViewPath = previewViewPath.EnsureEndsWith('/'); 262 | fullViewPath = $"{previewViewPath}{editorAlias}.cshtml"; 263 | if (!ViewExists(viewContext, fullViewPath)) 264 | { 265 | fullViewPath = $"{previewViewPath}{contentTypeAlias}.cshtml"; 266 | if (!ViewExists(viewContext, fullViewPath)) 267 | { 268 | fullViewPath = $"{previewViewPath}Default.cshtml"; 269 | if (!ViewExists(viewContext, fullViewPath)) 270 | { 271 | fullViewPath = ""; 272 | } 273 | } 274 | } 275 | } 276 | if (!viewPath.IsNullOrWhiteSpace() && (fullViewPath.IsNullOrWhiteSpace() || !isPreview)) 277 | { 278 | viewPath = viewPath.EnsureEndsWith('/'); 279 | fullViewPath = $"{viewPath}{editorAlias}.cshtml"; 280 | if (!ViewExists(viewContext, fullViewPath)) 281 | { 282 | fullViewPath = $"{viewPath}{contentTypeAlias}.cshtml"; 283 | if (!ViewExists(viewContext, fullViewPath)) 284 | { 285 | fullViewPath = $"{viewPath}Default.cshtml"; 286 | if (!ViewExists(viewContext, fullViewPath)) 287 | { 288 | fullViewPath = ""; 289 | } 290 | } 291 | } 292 | } 293 | 294 | return !fullViewPath.IsNullOrWhiteSpace(); 295 | } 296 | 297 | private string GetComponentName(string[] names) 298 | { 299 | var componentName = _options.DefaultDocTypeGridEditorViewComponent.Name; 300 | if (componentName.EndsWith("ViewComponent")) componentName = componentName.Substring(0, componentName.LastIndexOf("ViewComponent")); 301 | names = names.SelectMany(x => new[] { x.ToFirstUpper(), x }).Distinct().ToArray(); 302 | 303 | foreach (var name in names) 304 | { 305 | if (_viewComponentSelector.SelectComponent($"{name}DocTypeGridEditor") != null) 306 | { 307 | return $"{name}DocTypeGridEditor"; 308 | } 309 | } 310 | 311 | return componentName; 312 | } 313 | 314 | public static bool ViewExists(ViewContext viewContext, string viewName) 315 | { 316 | var viewEngine = viewContext.HttpContext.RequestServices.GetRequiredService(); 317 | var result = viewEngine.GetView(null, viewName, true).Success || viewEngine.GetView(null, viewName, false).Success; 318 | return result; 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Helpers/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Our.Umbraco.DocTypeGridEditor.Config; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Our.Umbraco.DocTypeGridEditor.Helpers 11 | { 12 | public static class ServiceCollectionExtensions 13 | { 14 | public static IServiceCollection SetDocTypeGridEditorSettings(this IServiceCollection services, Action settings) 15 | { 16 | if (settings is not null) 17 | { 18 | services.Configure(settings); 19 | } 20 | 21 | return services; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Helpers/UmbracoBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Our.Umbraco.DocTypeGridEditor.ValueProcessing.Collections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Umbraco.Cms.Core.DependencyInjection; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor.Helpers 10 | { 11 | public static class UmbracoBuilderExtensions 12 | { 13 | /// 14 | /// Used to modify the collection of Value Processors for Doc Type Grid Editor 15 | /// 16 | /// 17 | /// 18 | public static DocTypeGridEditorValueProcessorsCollectionBuilder DocTypeGridEditorValueProcessors(this IUmbracoBuilder builder) 19 | => builder.WithCollectionBuilder(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/ContentTypeContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core.Models; 7 | using Umbraco.Cms.Core.Models.PublishedContent; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor.Models 10 | { 11 | public class ContentTypeContainer 12 | { 13 | public PublishedContentType PublishedContentType { get; set; } 14 | public IContentType ContentType { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/DetachedPublishedElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core.Models.PublishedContent; 7 | using Umbraco.Extensions; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor.Models 10 | { 11 | internal class DetachedPublishedElement : IPublishedElement 12 | { 13 | private readonly IPublishedContentType _contentType; 14 | private readonly IEnumerable _properties; 15 | private readonly bool _isPreviewing; 16 | private readonly Guid _key; 17 | 18 | 19 | public DetachedPublishedElement( 20 | Guid key, 21 | IPublishedContentType contentType, 22 | IEnumerable properties, 23 | bool isPreviewing = false) 24 | { 25 | _key = key; 26 | _contentType = contentType; 27 | _properties = properties; 28 | _isPreviewing = isPreviewing; 29 | } 30 | public IPublishedContentType ContentType => _contentType; 31 | 32 | public IPublishedProperty GetProperty(string alias) => _properties.FirstOrDefault(x => x.PropertyType.Alias.InvariantEquals(alias)); 33 | 34 | public Guid Key => _key; 35 | 36 | public IEnumerable Properties => _properties; 37 | 38 | public bool IsDraft => _isPreviewing; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/DetachedPublishedProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Umbraco.Cms.Core.Models.PublishedContent; 3 | using Umbraco.Cms.Core.PropertyEditors; 4 | 5 | namespace Our.Umbraco.DocTypeGridEditor.Models 6 | { 7 | internal class DetachedPublishedProperty : IPublishedProperty 8 | { 9 | private readonly IPublishedPropertyType _propertyType; 10 | private readonly object _rawValue; 11 | private readonly Lazy _sourceValue; 12 | private readonly Lazy _objectValue; 13 | private readonly Lazy _xpathValue; 14 | private readonly bool _isPreview; 15 | 16 | public DetachedPublishedProperty(IPublishedPropertyType propertyType, object value) 17 | : this(propertyType, value, false) 18 | { } 19 | 20 | public DetachedPublishedProperty(IPublishedPropertyType propertyType, object value, bool isPreview) 21 | { 22 | _propertyType = propertyType; 23 | _isPreview = isPreview; 24 | 25 | _rawValue = value; 26 | 27 | _sourceValue = new Lazy(() => _propertyType.ConvertSourceToInter(null, _rawValue, _isPreview)); 28 | _objectValue = new Lazy(() => _propertyType.ConvertInterToObject(null, PropertyCacheLevel.None, _sourceValue.Value, _isPreview)); 29 | _xpathValue = new Lazy(() => _propertyType.ConvertInterToXPath(null, PropertyCacheLevel.None, _sourceValue.Value, _isPreview)); 30 | } 31 | 32 | public string PropertyTypeAlias 33 | { 34 | get 35 | { 36 | return _propertyType.DataType.EditorAlias; 37 | } 38 | } 39 | 40 | public bool HasValue 41 | { 42 | get { return DataValue != null && DataValue.ToString().Trim().Length > 0; } 43 | } 44 | 45 | public object DataValue 46 | { 47 | get { return _rawValue; } 48 | } 49 | 50 | public object Value 51 | { 52 | get { return _objectValue.Value; } 53 | } 54 | 55 | public object XPathValue 56 | { 57 | get { return _xpathValue.Value; } 58 | } 59 | 60 | bool IPublishedProperty.HasValue(string culture, string segment) 61 | { 62 | return HasValue; 63 | } 64 | 65 | public object GetSourceValue(string culture = null, string segment = null) 66 | { 67 | return DataValue; 68 | } 69 | 70 | public object GetValue(string culture = null, string segment = null) 71 | { 72 | return Value; 73 | } 74 | 75 | public object GetXPathValue(string culture = null, string segment = null) 76 | { 77 | return XPathValue; 78 | } 79 | 80 | public object GetDeliveryApiValue(bool expanding, string culture = null, string segment = null) 81 | { 82 | return Value; 83 | } 84 | 85 | public IPublishedPropertyType PropertyType 86 | { 87 | get 88 | { 89 | return _propertyType; 90 | } 91 | } 92 | public string Alias 93 | { 94 | get 95 | { 96 | return _propertyType.Alias; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/DocTypeGridEditorValue.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor.Models 10 | { 11 | public class DocTypeGridEditorValue 12 | { 13 | [JsonProperty("value")] 14 | public JObject Value { get; set; } 15 | [JsonProperty("dtgeContentTypeAlias")] 16 | public string ContentTypeAlias { get; set; } 17 | [JsonProperty("id")] 18 | public Guid Id { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/PreviewData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.Models 9 | { 10 | [DataContract] 11 | public class PreviewData 12 | { 13 | [DataMember(Name = "contentTypeAlias")] 14 | public string ContentTypeAlias { get; set; } 15 | 16 | [DataMember(Name = "editorAlias")] 17 | public string EditorAlias { get; set; } 18 | 19 | [DataMember(Name = "id")] 20 | public string Id { get; set; } 21 | 22 | [DataMember(Name = "previewViewPath")] 23 | public string PreviewViewPath { get; set; } 24 | 25 | [DataMember(Name = "value")] 26 | public string Value { get; set; } 27 | 28 | [DataMember(Name = "viewPath")] 29 | public string ViewPath { get; set; } 30 | 31 | [DataMember(Name = "culture")] 32 | public string Culture { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/PreviewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core.Models.PublishedContent; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.Models 9 | { 10 | public class PreviewModel : IPublishedElement 11 | { 12 | public IPublishedContent Page { get; set; } 13 | 14 | public IPublishedElement Item { get; set; } 15 | 16 | public string EditorAlias { get; set; } 17 | 18 | public string PreviewViewPath { get; set; } 19 | 20 | public string ViewPath { get; set; } 21 | 22 | #region IPublishedContent Implementation 23 | 24 | public IPublishedProperty GetProperty(string alias) => Item.GetProperty(alias); 25 | 26 | public IPublishedContentType ContentType => Item.ContentType; 27 | 28 | public Guid Key => Item.Key; 29 | 30 | public IEnumerable Properties => Item.Properties; 31 | 32 | #endregion 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/UnpublishedContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Umbraco.Cms.Core.Models; 5 | using Umbraco.Cms.Core.Models.PublishedContent; 6 | using Umbraco.Cms.Core.PropertyEditors; 7 | using Umbraco.Cms.Core.Services; 8 | using Umbraco.Extensions; 9 | 10 | namespace Our.Umbraco.DocTypeGridEditor.Models 11 | { 12 | internal class UnpublishedContent : IPublishedContent 13 | { 14 | private readonly IContent content; 15 | 16 | private readonly Lazy> children; 17 | private readonly Lazy contentType; 18 | private readonly Lazy parent; 19 | private readonly Lazy> properties; 20 | 21 | public UnpublishedContent(int id, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, IPublishedContentTypeFactory publishedContentTypeFactory) 22 | : this(contentService.GetById(id), contentService, contentTypeService, dataTypeService, propertyEditorCollection, publishedContentTypeFactory) 23 | { } 24 | 25 | public UnpublishedContent(IContent content, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, IPublishedContentTypeFactory publishedContentTypeFactory) 26 | : base() 27 | { 28 | 29 | this.content = content; 30 | var contentType = contentTypeService.Get(this.content.ContentType.Id); 31 | 32 | //this.children = new Lazy>(() => this.content.Children().Select(x => new UnpublishedContent(x, serviceContext)).ToList()); 33 | this.contentType = new Lazy(() => publishedContentTypeFactory.CreateContentType(contentType)); 34 | this.parent = new Lazy(() => new UnpublishedContent(contentService.GetById(this.content.ParentId), contentService, contentTypeService, dataTypeService, propertyEditorCollection, publishedContentTypeFactory)); 35 | this.properties = new Lazy>(() => MapProperties(dataTypeService, propertyEditorCollection)); 36 | //this.urlName = new Lazy(() => this.content.Name.ToUrlSegment()); 37 | //this.writerName = new Lazy(() => this.content.GetWriterProfile(userService.Value).Name); 38 | } 39 | 40 | public Guid Key => this.content.Key; 41 | 42 | IEnumerable IPublishedElement.Properties => Properties; 43 | 44 | public IReadOnlyDictionary Cultures { get; } 45 | 46 | public PublishedItemType ItemType => PublishedItemType.Content; 47 | 48 | public string GetUrl(string culture = null) 49 | { 50 | return null; 51 | } 52 | 53 | public PublishedCultureInfo GetCulture(string culture = null) 54 | { 55 | return null; 56 | } 57 | 58 | bool IPublishedContent.IsDraft(string culture) 59 | { 60 | return true; 61 | } 62 | 63 | public bool IsPublished(string culture = null) 64 | { 65 | return false; 66 | } 67 | 68 | public int Id => this.content.Id; 69 | 70 | public string UrlSegment { get; } 71 | public int SortOrder => this.content.SortOrder; 72 | 73 | public string Name => this.content.Name; 74 | 75 | public int DocumentTypeId => this.content.ContentType?.Id ?? default(int); 76 | 77 | public int WriterId => this.content.WriterId; 78 | 79 | int? IPublishedContent.TemplateId => this.content.TemplateId; 80 | 81 | public int CreatorId => this.content.CreatorId; 82 | 83 | public string Path => this.content.Path; 84 | 85 | public DateTime CreateDate => this.content.CreateDate; 86 | 87 | public DateTime UpdateDate => this.content.UpdateDate; 88 | 89 | 90 | public int Level => this.content.Level; 91 | 92 | public bool IsDraft => true; 93 | 94 | public IPublishedContent Parent => this.parent.Value; 95 | 96 | public IEnumerable Children => this.children.Value; 97 | 98 | public IPublishedContentType ContentType => this.contentType.Value; 99 | 100 | public ICollection Properties => this.properties.Value.Values; 101 | 102 | public IEnumerable ChildrenForAllCultures => this.children.Value; 103 | 104 | public IPublishedProperty GetProperty(string alias) 105 | { 106 | return this.properties.Value.TryGetValue(alias, out IPublishedProperty property) ? property : null; 107 | } 108 | 109 | private Dictionary MapProperties(IDataTypeService dataTypeService, PropertyEditorCollection dataEditors) 110 | { 111 | var contentType = this.contentType.Value; 112 | var properties = this.content.Properties; 113 | 114 | var items = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 115 | 116 | foreach (var propertyType in contentType.PropertyTypes) 117 | { 118 | var property = properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.DataType.EditorAlias)); 119 | var value = property?.GetValue(); 120 | if (value != null) 121 | { 122 | dataEditors.TryGet(propertyType.DataType.EditorAlias, out var editor); 123 | if (editor != null) 124 | { 125 | value = editor.GetValueEditor().ConvertDbToString(property.PropertyType, value); 126 | } 127 | } 128 | 129 | items.Add(propertyType.DataType.EditorAlias, new UnpublishedProperty(propertyType, value)); 130 | } 131 | 132 | return items; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Models/UnpublishedProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Umbraco.Cms.Core.Models.PublishedContent; 3 | using Umbraco.Cms.Core.PropertyEditors; 4 | 5 | namespace Our.Umbraco.DocTypeGridEditor.Models 6 | { 7 | internal class UnpublishedProperty : IPublishedProperty 8 | { 9 | private readonly IPublishedPropertyType propertyType; 10 | private readonly object dataValue; 11 | private readonly Lazy hasValue; 12 | private readonly Lazy sourceValue; 13 | private readonly Lazy objectValue; 14 | private readonly Lazy xpathValue; 15 | 16 | public UnpublishedProperty(IPublishedPropertyType propertyType, object value) 17 | { 18 | this.propertyType = propertyType; 19 | 20 | this.dataValue = value; 21 | this.hasValue = new Lazy(() => value != null && value.ToString().Trim().Length > 0); 22 | 23 | this.sourceValue = new Lazy(() => this.propertyType.ConvertSourceToInter(null, this.dataValue, true)); 24 | this.objectValue = new Lazy(() => this.propertyType.ConvertInterToObject(null, PropertyCacheLevel.None, this.sourceValue.Value, true)); 25 | this.xpathValue = new Lazy(() => this.propertyType.ConvertInterToXPath(null, PropertyCacheLevel.None, this.sourceValue.Value, true)); 26 | } 27 | 28 | public string PropertyTypeAlias => this.propertyType.DataType.EditorAlias; 29 | 30 | bool IPublishedProperty.HasValue(string culture, string segment) => this.hasValue.Value; 31 | 32 | public object DataValue => this.dataValue; 33 | 34 | public object Value => this.objectValue.Value; 35 | 36 | public object XPathValue => this.xpathValue.Value; 37 | 38 | public object GetSourceValue(string culture = null, string segment = null) => this.sourceValue.Value; 39 | 40 | public object GetValue(string culture = null, string segment = null) => this.objectValue.Value; 41 | 42 | public object GetXPathValue(string culture = null, string segment = null) => this.xpathValue.Value; 43 | 44 | public object GetDeliveryApiValue(bool expanding, string culture = null, string segment = null) => this.objectValue.Value; 45 | 46 | public IPublishedPropertyType PropertyType => this.PropertyType; 47 | 48 | public string Alias => this.Alias; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/Our.Umbraco.DocTypeGridEditor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | . 5 | App_Plugins/DocTypeGridEditor 6 | true 7 | 8 | Our.Umbraco.DocTypeGridEditor 9 | Our.Umbraco.DocTypeGridEditor 10 | 11 | Our.Umbraco.DocTypeGridEditor 12 | umbraco plugin package 13 | 14 | 12.1.0-blackknight 15 | Søren Kottal 16 | 17 | 18 | Our.Umbraco.DocTypeGridEditor 19 | Our.Umbraco.DocTypeGridEditor 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ValueProcessing/Collections/DocTypeGridEditorValueProcessorsCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core.Composing; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.ValueProcessing.Collections 9 | { 10 | /// 11 | /// Collection to hold references to Value Processors to be used with Doc Type Grid Editor. 12 | /// 13 | public class DocTypeGridEditorValueProcessorsCollection : BuilderCollectionBase 14 | { 15 | public DocTypeGridEditorValueProcessorsCollection(Func> items) : base(items) 16 | { 17 | 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ValueProcessing/Collections/DocTypeGridEditorValueProcessorsCollectionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core.Composing; 7 | 8 | namespace Our.Umbraco.DocTypeGridEditor.ValueProcessing.Collections 9 | { 10 | /// 11 | /// Collection builder for Value Processors 12 | /// 13 | public class DocTypeGridEditorValueProcessorsCollectionBuilder : OrderedCollectionBuilderBase 14 | { 15 | protected override DocTypeGridEditorValueProcessorsCollectionBuilder This => this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ValueProcessing/DocTypeGridEditorDataValueReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Umbraco.Cms.Core; 7 | using Umbraco.Cms.Core.Models; 8 | using Umbraco.Cms.Core.Models.Editors; 9 | using Umbraco.Cms.Core.PropertyEditors; 10 | using Umbraco.Cms.Core.Services; 11 | using Umbraco.Extensions; 12 | using Newtonsoft.Json; 13 | using Our.Umbraco.DocTypeGridEditor.Models; 14 | using Newtonsoft.Json.Linq; 15 | 16 | namespace Our.Umbraco.DocTypeGridEditor.ValueProcessing 17 | { 18 | public class DocTypeGridEditorDataValueReference : IDataValueReferenceFactory, IDataValueReference 19 | { 20 | private readonly Lazy> _contentTypes; 21 | private readonly Lazy _dataEditors; 22 | 23 | public IDataValueReference GetDataValueReference() => this; 24 | 25 | public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); 26 | 27 | 28 | public DocTypeGridEditorDataValueReference(Lazy contentTypeService, Lazy dataEditors) 29 | { 30 | _contentTypes = new Lazy>(() => contentTypeService.Value.GetAll().ToDictionary(c => c.Alias)); 31 | _dataEditors = dataEditors; 32 | } 33 | 34 | public IEnumerable GetReferences(object value) 35 | { 36 | var result = new List(); 37 | var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); 38 | 39 | if (rawJson.IsNullOrWhiteSpace()) return result; 40 | 41 | DeserializeGridValue(rawJson, out var dtgeValues); 42 | 43 | foreach (var control in dtgeValues) 44 | { 45 | if (_contentTypes.Value.TryGetValue(control.ContentTypeAlias, out var contentType)) 46 | { 47 | var propertyTypes = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); 48 | var properties = control.Value.Properties(); 49 | 50 | foreach (var property in properties) 51 | { 52 | if (propertyTypes.TryGetValue(property.Name, out var propertyType)) 53 | { 54 | if (_dataEditors.Value.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor)) 55 | { 56 | if (propertyEditor.GetValueEditor() is IDataValueReference reference) 57 | { 58 | var propertyValue = property.Value.ToString(); 59 | var refs = reference.GetReferences(propertyValue); 60 | result.AddRange(refs); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | return result; 68 | 69 | } 70 | 71 | internal GridValue DeserializeGridValue(string rawJson, out IEnumerable dtgeValues) 72 | { 73 | var grid = JsonConvert.DeserializeObject(rawJson); 74 | 75 | if (grid != null) 76 | { 77 | // Find all controls that uses DTGE editor 78 | var controls = grid.Sections.SelectMany(x => x.Rows.SelectMany(r => r.Areas).SelectMany(a => a.Controls)).ToArray(); 79 | dtgeValues = controls.Where(x => x.Value is JObject && x.Value["dtgeContentTypeAlias"] != null).Select(x => x.Value.ToObject()); 80 | } 81 | else 82 | { 83 | dtgeValues = Enumerable.Empty(); 84 | } 85 | 86 | return grid; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ValueProcessing/IDocTypeGridEditorValueProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Our.Umbraco.DocTypeGridEditor.ValueProcessing 8 | { 9 | /// 10 | /// Abstracts a ValueProcessor that is used to modify a property value before it's sent to a Property Value Converter 11 | /// These are useful when the data format stored for DTGE is different from what the PVC expects ie. like Umbraco.Tags 12 | /// 13 | public interface IDocTypeGridEditorValueProcessor 14 | { 15 | /// 16 | /// Returns if this processor can handle a certain property editor based on the property editors alias. 17 | /// 18 | /// Contains the alias of the property editor ie. "Umbraco.Tags" or "Umbraco.Picker". 19 | /// 20 | bool IsProcessorFor(string propertyEditorAlias); 21 | 22 | /// 23 | /// Processes the value to de desired format/type. Most of the time this is to make sure that the object passed to the property value converters is 24 | /// of the correct type and in the correct format for the property value converter to handle. 25 | /// 26 | /// 27 | /// 28 | object ProcessValue(object value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ValueProcessing/UmbracoTagsValueProcessor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Umbraco.Cms.Core; 8 | 9 | namespace Our.Umbraco.DocTypeGridEditor.ValueProcessing 10 | { 11 | /// 12 | /// Processes the value stored in DTGE to match the string-json-format that the Property Value Converter expects. 13 | /// 14 | /// 15 | /// When we're setting a value on a Umbraco.Core.Models.Property it will be converted into the type that this Property expects. 16 | /// In the case with Tags, there will be a .ToString() that converts the object passed into a string, something like System.Linq.Enumerable+WhereSelectEnumerableIterator`2[Newtonsoft.Json.Linq.JToken,System.String] 17 | /// This value would be passed to the Property Value Converter which would blow up. The processing below will safely convert the value to a valid json-string before it's passed 18 | /// to the Property Value Converter. 19 | /// 20 | public class UmbracoTagsValueProcessor : IDocTypeGridEditorValueProcessor 21 | { 22 | public bool IsProcessorFor(string propertyEditorAlias) => propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.Tags); 23 | 24 | public object ProcessValue(object value) 25 | { 26 | if (value == null) 27 | { 28 | // When the value is null, we need to fake an empty array since this is 29 | // how Umbraco would store an empty "Umbraco.Tags"-property. 30 | return "[]"; 31 | } 32 | 33 | // Returns a string-version of the JArray that DTGE would pass. 34 | return JsonConvert.SerializeObject(value); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/ViewComponents/DocTypeGridEditorViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ViewComponents; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Umbraco.Cms.Core.Models.PublishedContent; 9 | using Umbraco.Extensions; 10 | 11 | namespace Our.Umbraco.DocTypeGridEditor.ViewComponents 12 | { 13 | public class DocTypeGridEditorViewComponent : ViewComponent 14 | { 15 | public virtual IViewComponentResult Invoke(dynamic model, string viewPath) 16 | { 17 | return View(viewPath, model); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Css/doctypegrideditor.css: -------------------------------------------------------------------------------- 1 | /*.dtge-dialog fieldset legend { 2 | display: none; 3 | }*/ 4 | 5 | .dtge-dialog input.ng-invalid { 6 | border-color: #9d261d; 7 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); 8 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); 9 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); 10 | } 11 | 12 | .dtge-dialog .help-inline { 13 | color: #9d261d; 14 | } 15 | 16 | .dtge-dialog textarea.textstring { 17 | resize: both; 18 | border: 1px solid #bbbabf; 19 | } 20 | 21 | .dtge-editor-preview-container { 22 | text-align: initial; 23 | } 24 | /* 25 | Style the doc type grid editor previews 26 | 27 | .dtge-editor-placeholder { 28 | background-color: #f8f8f8; 29 | background-color: rgba(0,0,0,0.03); 30 | padding: 20px; 31 | } 32 | 33 | .dtge-editor-placeholder .icon { 34 | text-align: center; 35 | display: block; 36 | color: #ddd; 37 | color: rgba(0,0,0,0.1); 38 | font-size: 50px; 39 | line-height: 50px; 40 | } 41 | 42 | .dtge-editor-placeholder .help-text { 43 | text-align: center; 44 | display: block; 45 | } 46 | */ 47 | 48 | /* workaround for https://github.com/skttl/umbraco-doc-type-grid-editor/issues/199 from https://github.com/umbraco/Umbraco-CMS/issues/7754 */ 49 | body.pre870 .dtge-dialog .umb-overlay { 50 | left: inherit !important; 51 | right: 0 !important; 52 | top: 0 !important; 53 | bottom: 0 !important; 54 | } -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Js/doctypegrideditor.controllers.js: -------------------------------------------------------------------------------- 1 | angular.module("umbraco").controller("Our.Umbraco.DocTypeGridEditor.GridEditors.DocTypeGridEditor", [ 2 | 3 | "$scope", 4 | "$rootScope", 5 | "$timeout", 6 | "editorState", 7 | 'assetsService', 8 | "Our.Umbraco.DocTypeGridEditor.Resources.DocTypeGridEditorResources", 9 | "umbRequestHelper", 10 | "localizationService", 11 | "editorService", 12 | 13 | function ($scope, $rootScope, $timeout, editorState, assetsService, dtgeResources, umbRequestHelper, localizationService, editorService) { 14 | 15 | var overlayOptions = { 16 | view: umbRequestHelper.convertVirtualToAbsolutePath( 17 | "~/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.dialog.html"), 18 | model: {}, 19 | titles: { 20 | insertItem: "Click to insert item", 21 | editItem: "Edit item", 22 | selectContentType: "Choose a Content Type", 23 | selectBlueprint: "Choose a Content Template" 24 | }, 25 | title: "Edit item", 26 | submitButtonlabelKey: "bulk_done" 27 | }; 28 | 29 | $scope.icon = "icon-item-arrangement"; 30 | 31 | // init cached content types if it doesnt exist. 32 | if (!$rootScope.dtgeContentTypes) $rootScope.dtgeContentTypes = {}; 33 | 34 | // localize strings 35 | localizationService.localizeMany(["docTypeGridEditor_insertItem", "docTypeGridEditor_editItem", "docTypeGridEditor_selectContentType", "blueprints_selectBlueprint"]).then(function (data) { 36 | overlayOptions.titles.insertItem = data[0]; 37 | overlayOptions.titles.editItem = data[1]; 38 | overlayOptions.titles.selectContentType = data[2]; 39 | overlayOptions.titles.selectBlueprint = data[3]; 40 | }); 41 | 42 | $scope.setValue = function (data, callback) { 43 | $scope.title = $scope.control.editor.name; 44 | $scope.icon = $scope.control.editor.icon; 45 | $scope.control.value = data; 46 | 47 | if (!("id" in $scope.control.value) || $scope.control.value.id == "") { 48 | $scope.control.value.id = guid(); 49 | } 50 | if ("name" in $scope.control.value.value && $scope.control.value.value.name) { 51 | $scope.title = $scope.control.value.value.name; 52 | } 53 | if ("dtgeContentTypeAlias" in $scope.control.value && $scope.control.value.dtgeContentTypeAlias) { 54 | if (!$rootScope.dtgeContentTypes[$scope.control.value.dtgeContentTypeAlias]) { 55 | 56 | dtgeResources.getContentType($scope.control.value.dtgeContentTypeAlias).then(function (data2) { 57 | var contentType = { 58 | title: data2.title, 59 | description: data2.description, 60 | icon: data2.icon 61 | }; 62 | 63 | // save to cached content types 64 | $rootScope.dtgeContentTypes[$scope.control.value.dtgeContentTypeAlias] = contentType; 65 | $scope.setTitleAndDescription(contentType); 66 | }); 67 | } 68 | else { 69 | $scope.setTitleAndDescription($rootScope.dtgeContentTypes[$scope.control.value.dtgeContentTypeAlias]); 70 | } 71 | } 72 | if (callback) 73 | callback(); 74 | }; 75 | 76 | $scope.setTitleAndDescription = function (contentType) { 77 | $scope.title = contentType.title; 78 | $scope.description = contentType.description; 79 | $scope.icon = contentType.icon; 80 | }; 81 | 82 | $scope.setDocType = function () { 83 | 84 | overlayOptions.editorName = $scope.control.editor.name; 85 | overlayOptions.allowedDocTypes = $scope.control.editor.config.allowedDocTypes || []; 86 | overlayOptions.showDocTypeSelectAsGrid = $scope.control.editor.config.showDocTypeSelectAsGrid === true; 87 | overlayOptions.nameTemplate = $scope.control.editor.config.nameTemplate; 88 | overlayOptions.size = $scope.control.editor.config.largeDialog ? null : $scope.control.editor.config.overlaySize || "small"; 89 | 90 | overlayOptions.dialogData = { 91 | docTypeAlias: $scope.control.value.dtgeContentTypeAlias, 92 | value: $scope.control.value.value, 93 | id: $scope.control.value.id 94 | }; 95 | overlayOptions.close = function () { 96 | // ensure an empty DTGE without ContentType Alias is not persisted 97 | if ($scope.control.value && $scope.control.value.dtgeContentTypeAlias === "") { 98 | let indexOfThis = $scope.area.controls.map(function (control) { return control.$uniqueId }).indexOf($scope.control.$uniqueId); 99 | if (indexOfThis > -1) { 100 | $scope.removeControl($scope.area, indexOfThis); 101 | } 102 | } 103 | 104 | editorService.close(); 105 | } 106 | overlayOptions.submit = function (newModel) { 107 | 108 | // Copy property values to scope model value 109 | if (newModel.node) { 110 | var value = { 111 | name: newModel.editorName 112 | }; 113 | 114 | for (var v = 0; v < newModel.node.variants.length; v++) { 115 | var variant = newModel.node.variants[v]; 116 | for (var t = 0; t < variant.tabs.length; t++) { 117 | var tab = variant.tabs[t]; 118 | for (var p = 0; p < tab.properties.length; p++) { 119 | var prop = tab.properties[p]; 120 | if (typeof prop.value !== "function") { 121 | value[prop.alias] = prop.value; 122 | } 123 | } 124 | } 125 | } 126 | 127 | if (newModel.nameExp) { 128 | var newName = newModel.nameExp(value); // Run it against the stored dictionary value, NOT the node object 129 | if (newName && (newName = $.trim(newName))) { 130 | value.name = newName; 131 | } 132 | } 133 | 134 | newModel.dialogData.value = value; 135 | } else { 136 | newModel.dialogData.value = null; 137 | 138 | } 139 | 140 | $scope.setValue({ 141 | dtgeContentTypeAlias: newModel.dialogData.docTypeAlias, 142 | value: newModel.dialogData.value, 143 | id: newModel.dialogData.id 144 | }); 145 | $scope.setPreview($scope.control.value); 146 | editorService.close(); 147 | }; 148 | 149 | editorService.open(overlayOptions); 150 | }; 151 | 152 | $scope.setPreview = function (model) { 153 | if ($scope.control.editor.config && "enablePreview" in $scope.control.editor.config && $scope.control.editor.config.enablePreview) { 154 | var activeVariant = editorState.current.variants?.find(v => v.active); 155 | var culture = activeVariant?.language?.culture; 156 | dtgeResources.getEditorMarkupForDocTypePartial(editorState.current.id, model.id, 157 | $scope.control.editor.alias, model.dtgeContentTypeAlias, model.value, 158 | $scope.control.editor.config.viewPath, 159 | $scope.control.editor.config.previewViewPath, 160 | !!editorState.current.publishDate, culture) 161 | .then(function (response) { 162 | var htmlResult = response.data; 163 | if (htmlResult.trim().length > 0) { 164 | $scope.preview = htmlResult; 165 | } 166 | }); 167 | } 168 | }; 169 | 170 | function init() { 171 | $timeout(function () { 172 | if ($scope.control.$initializing) { 173 | $scope.setDocType(); 174 | } else if ($scope.control.value) { 175 | $scope.setPreview($scope.control.value); 176 | } 177 | }, 200); 178 | } 179 | 180 | if ($scope.control.value) { 181 | if (!$scope.control.value.dtgeContentTypeAlias && $scope.control.value.docType) { 182 | $scope.control.value.dtgeContentTypeAlias = $scope.control.value.docType; 183 | } 184 | if ($scope.control.value.docType) { 185 | delete $scope.control.value.docType; 186 | } 187 | if (isGuid($scope.control.value.dtgeContentTypeAlias)) { 188 | dtgeResources.getContentTypeAliasByGuid($scope.control.value.dtgeContentTypeAlias).then(function (data1) { 189 | $scope.control.value.dtgeContentTypeAlias = data1.alias; 190 | $scope.setValue($scope.control.value, init); 191 | }); 192 | } else { 193 | $scope.setValue($scope.control.value, init); 194 | } 195 | } else { 196 | $scope.setValue({ 197 | id: guid(), 198 | dtgeContentTypeAlias: "", 199 | value: {} 200 | }, init); 201 | } 202 | 203 | // Load preview css / js files 204 | if ($scope.control.editor.config && "enablePreview" in $scope.control.editor.config && $scope.control.editor.config.enablePreview) { 205 | if ("previewCssFilePath" in $scope.control.editor.config && $scope.control.editor.config.previewCssFilePath) { 206 | assetsService.loadCss($scope.control.editor.config.previewCssFilePath, $scope); 207 | }; 208 | 209 | if ("previewJsFilePath" in $scope.control.editor.config && $scope.control.editor.config.previewJsFilePath) { 210 | assetsService.loadJs($scope.control.editor.config.previewJsFilePath, $scope); 211 | } 212 | } 213 | 214 | function guid() { 215 | function s4() { 216 | return Math.floor((1 + Math.random()) * 0x10000) 217 | .toString(16) 218 | .substring(1); 219 | } 220 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 221 | s4() + '-' + s4() + s4() + s4(); 222 | } 223 | 224 | function isGuid(input) { 225 | return new RegExp("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$", "i").test(input.toString()); 226 | } 227 | 228 | } 229 | ]); 230 | 231 | angular.module("umbraco").controller("Our.Umbraco.DocTypeGridEditor.Dialogs.DocTypeGridEditorDialog", 232 | [ 233 | "$scope", 234 | "$interpolate", 235 | "formHelper", 236 | "contentResource", 237 | "Our.Umbraco.DocTypeGridEditor.Resources.DocTypeGridEditorResources", 238 | "Our.Umbraco.DocTypeGridEditor.Services.DocTypeGridEditorUtilityService", 239 | "blueprintConfig", 240 | "contentEditingHelper", 241 | "serverValidationManager", 242 | "$routeParams", 243 | 244 | function ($scope, $interpolate, formHelper, contentResource, dtgeResources, dtgeUtilityService, blueprintConfig, contentEditingHelper, serverValidationManager, $routeParams) { 245 | 246 | var vm = this; 247 | vm.submit = submit; 248 | vm.close = close; 249 | vm.loading = true; 250 | vm.blueprintConfig = blueprintConfig; 251 | vm.saveButtonState = "init"; 252 | 253 | function cleanup() { 254 | if ($scope.model.node && $scope.model.node.id > 0) { 255 | // delete any temporary blueprints used for validation 256 | dtgeResources.deleteBlueprint($scope.model.node.id); 257 | 258 | // set current node id, so subsequent deletes, giving 404 errors is avoided 259 | $scope.model.node.id = 0; 260 | } 261 | 262 | //clear server validation messages when this editor is destroyed 263 | serverValidationManager.clear(); 264 | } 265 | 266 | $scope.$on('$destroy', cleanup); 267 | 268 | function submit() { 269 | if ($scope.model.submit) { 270 | serverValidationManager.reset(); 271 | vm.saveButtonState = "busy"; 272 | $scope.model.node.name = "Dtge Temp: " + $scope.model.node.key; 273 | $scope.model.node.variants[0].name = $scope.model.node.name 274 | $scope.model.node.variants[0].save = true; 275 | 276 | // Reset route create to prevent showing up the changed content dialog 277 | var routeParamsCreate = $routeParams.create; 278 | $routeParams.create = undefined; 279 | 280 | // save the content as a blueprint, to trigger validation 281 | var args = { 282 | saveMethod: dtgeResources.saveBlueprint, 283 | scope: $scope, 284 | content: $scope.model.node, 285 | create: true, 286 | action: "save", 287 | showNotifications: false, 288 | softRedirect: true 289 | } 290 | 291 | contentEditingHelper.contentEditorPerformSave(args).then(function (data) { 292 | $scope.model.node.id = data.id; 293 | $scope.model.submit($scope.model); 294 | // Reset original value of $routeParams.create 295 | $routeParams.create = routeParamsCreate; 296 | }, 297 | function (err) { 298 | // Set original value of $routeParams.create 299 | $routeParams.create = routeParamsCreate; 300 | // cleanup the blueprint immediately 301 | if (err && err.data) { 302 | $scope.model.node.id = err.data.id; 303 | } 304 | cleanup(); 305 | vm.saveButtonState = "error"; 306 | }); 307 | vm.saveButtonState = "init"; 308 | } 309 | } 310 | function close() { 311 | if ($scope.model.close) { 312 | $scope.model.close(); 313 | } 314 | } 315 | 316 | $scope.docTypes = []; 317 | $scope.dialogMode = null; 318 | $scope.selectedDocType = null; 319 | $scope.model.node = null; 320 | 321 | var nameExp = !!$scope.model.nameTemplate 322 | ? $interpolate($scope.model.nameTemplate) 323 | : undefined; 324 | 325 | $scope.model.nameExp = nameExp; 326 | 327 | function createBlank() { 328 | $scope.dialogMode = "edit"; 329 | loadNode(); 330 | }; 331 | 332 | function createOrSelectBlueprintIfAny(docType) { 333 | 334 | $scope.model.dialogData.docTypeAlias = docType.alias; 335 | var blueprintIds = _.keys(docType.blueprints || {}); 336 | $scope.selectedDocType = docType; 337 | 338 | if (blueprintIds.length) { 339 | if (blueprintConfig.skipSelect) { 340 | createFromBlueprint(blueprintIds[0]); 341 | } else { 342 | $scope.dialogMode = "selectBlueprint"; 343 | vm.loading = false; 344 | } 345 | } else { 346 | createBlank(); 347 | } 348 | }; 349 | 350 | function createFromBlueprint(blueprintId) { 351 | contentResource.getBlueprintScaffold(-1, blueprintId).then(function (data) { 352 | // Assign the model to scope 353 | $scope.nodeContext = $scope.model.node = data; 354 | $scope.dialogMode = "edit"; 355 | vm.content = $scope.nodeContext.variants[0]; 356 | vm.loading = false; 357 | }); 358 | }; 359 | 360 | $scope.createBlank = createBlank; 361 | $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; 362 | $scope.createFromBlueprint = createFromBlueprint; 363 | 364 | function loadNode() { 365 | vm.loading = true; 366 | contentResource.getScaffold(-1, $scope.model.dialogData.docTypeAlias).then(function (data) { 367 | 368 | // Merge current value 369 | if ($scope.model.dialogData.value) { 370 | for (var v = 0; v < data.variants.length; v++) { 371 | var variant = data.variants[v]; 372 | for (var t = 0; t < variant.tabs.length; t++) { 373 | var tab = variant.tabs[t]; 374 | for (var p = 0; p < tab.properties.length; p++) { 375 | var prop = tab.properties[p]; 376 | if ($scope.model.dialogData.value[prop.alias]) { 377 | prop.value = $scope.model.dialogData.value[prop.alias]; 378 | } 379 | } 380 | } 381 | } 382 | }; 383 | 384 | // Assign the model to scope 385 | $scope.nodeContext = $scope.model.node = data; 386 | vm.content = $scope.nodeContext.variants[0]; 387 | vm.loading = false; 388 | }); 389 | } 390 | 391 | if ($scope.model.dialogData.docTypeAlias) { 392 | $scope.dialogMode = "edit"; 393 | loadNode(); 394 | } else { 395 | $scope.dialogMode = "selectDocType"; 396 | // No data type, so load a list to choose from 397 | dtgeResources.getContentTypes($scope.model.allowedDocTypes).then(function (docTypes) { 398 | $scope.docTypes = docTypes; 399 | if ($scope.docTypes.length == 1) { 400 | createOrSelectBlueprintIfAny($scope.docTypes[0]); 401 | } 402 | else { 403 | vm.loading = false; 404 | } 405 | }); 406 | } 407 | 408 | if (dtgeUtilityService.compareCurrentUmbracoVersion("8.7.0", {}) && !$("body").hasClass("pre870")) { 409 | $("body").addClass("pre870"); 410 | }; 411 | 412 | } 413 | 414 | ]); 415 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Js/doctypegrideditor.directives.js: -------------------------------------------------------------------------------- 1 | angular.module("umbraco.directives").directive("dtgeBindHtmlCompile", ["$compile", function ($compile) { 2 | return { 3 | restrict: "A", 4 | link: function (scope, element, attrs) { 5 | scope.$watch(function () { 6 | return scope.$eval(attrs.dtgeBindHtmlCompile); 7 | }, function (value) { 8 | element.html(value); 9 | $compile(element.contents())(scope); 10 | }); 11 | } 12 | }; 13 | }]); -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Js/doctypegrideditor.resources.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco.resources').factory('Our.Umbraco.DocTypeGridEditor.Resources.DocTypeGridEditorResources', 2 | function ($q, $http, umbRequestHelper, umbDataFormatter) { 3 | return { 4 | getContentTypeAliasByGuid: function (guid) { 5 | var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorApi/GetContentTypeAliasByGuid?guid=" + guid; 6 | return umbRequestHelper.resourcePromise( 7 | $http.get(url), 8 | 'Failed to retrieve content type alias by guid' 9 | ); 10 | }, 11 | getContentTypes: function (allowedContentTypes) { 12 | var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorApi/GetContentTypes"; 13 | if (allowedContentTypes) { 14 | for (var i = 0; i < allowedContentTypes.length; i++) { 15 | url += (i == 0 ? "?" : "&") + "allowedContentTypes=" + allowedContentTypes[i]; 16 | } 17 | } 18 | return umbRequestHelper.resourcePromise( 19 | $http.get(url), 20 | 'Failed to retrieve content types' 21 | ); 22 | }, 23 | getContentType: function (contentTypeAlias) { 24 | var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorApi/GetContentType?contentTypeAlias=" + contentTypeAlias; 25 | return umbRequestHelper.resourcePromise( 26 | $http.get(url), 27 | 'Failed to retrieve content type icon' 28 | ); 29 | }, 30 | getDataTypePreValues: function (dtdId) { 31 | var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorApi/GetDataTypePreValues?dtdid=" + dtdId; 32 | return umbRequestHelper.resourcePromise( 33 | $http.get(url), 34 | 'Failed to retrieve datatypes' 35 | ); 36 | }, 37 | getEditorMarkupForDocTypePartial: function (pageId, id, editorAlias, contentTypeAlias, value, viewPath, previewViewPath, published, culture) { 38 | var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorApi/GetPreviewMarkup?dtgePreview=1&pageId=" + pageId; 39 | return $http({ 40 | method: 'POST', 41 | url: url, 42 | data: $.param({ 43 | id: id, 44 | editorAlias: editorAlias, 45 | contentTypeAlias: contentTypeAlias, 46 | value: JSON.stringify(value), 47 | viewPath: viewPath, 48 | previewViewPath: previewViewPath, 49 | culture: culture 50 | }), 51 | headers: { 52 | 'Content-Type': 'application/x-www-form-urlencoded' 53 | } 54 | }); 55 | }, 56 | saveBlueprint: function (content, isNew, files, showNotifications) { 57 | var restApiUrl = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorBlueprintApi/PostSaveBlueprint"; 58 | return umbRequestHelper.postSaveContent({ 59 | restApiUrl: restApiUrl, 60 | content: content, 61 | action: "save" + (isNew ? "New" : ""), 62 | files: files, 63 | showNotifications: showNotifications, 64 | dataFormatter: function (c, a) { 65 | return umbDataFormatter.formatContentPostData(c, a); 66 | } 67 | }); 68 | }, 69 | deleteBlueprint: function (id) { 70 | var restApiUrl = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/DocTypeGridEditorApi/DocTypeGridEditorBlueprintApi/DeleteBlueprint"; 71 | var requestData = { id: id }; 72 | $http.post(restApiUrl, null, { params: requestData }) 73 | } 74 | }; 75 | }); -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Js/doctypegrideditor.services.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco.services').factory('Our.Umbraco.DocTypeGridEditor.Services.DocTypeGridEditorUtilityService', function () { 2 | 3 | function compareCurrentUmbracoVersion(v, options) { 4 | return this.compareVersions(Umbraco.Sys.ServerVariables.application.version, v, options); 5 | } 6 | 7 | function compareVersions(v1, v2, options) { 8 | 9 | var lexicographical = options && options.lexicographical, 10 | zeroExtend = options && options.zeroExtend, 11 | v1parts = v1.split('.'), 12 | v2parts = v2.split('.'); 13 | 14 | function isValidPart(x) { 15 | return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); 16 | } 17 | 18 | if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 19 | return NaN; 20 | } 21 | 22 | if (zeroExtend) { 23 | while (v1parts.length < v2parts.length) { 24 | v1parts.push("0"); 25 | } 26 | while (v2parts.length < v1parts.length) { 27 | v2parts.push("0"); 28 | } 29 | } 30 | 31 | if (!lexicographical) { 32 | v1parts = v1parts.map(Number); 33 | v2parts = v2parts.map(Number); 34 | } 35 | 36 | for (var i = 0; i < v1parts.length; ++i) { 37 | if (v2parts.length === i) { 38 | return 1; 39 | } 40 | 41 | if (v1parts[i] === v2parts[i]) { 42 | continue; 43 | } else if (v1parts[i] > v2parts[i]) { 44 | return 1; 45 | } else { 46 | return -1; 47 | } 48 | } 49 | 50 | if (v1parts.length !== v2parts.length) { 51 | return -1; 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | var service = { 58 | compareCurrentUmbracoVersion: compareCurrentUmbracoVersion, 59 | compareVersions: compareVersions 60 | }; 61 | 62 | return service; 63 | }); -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Lang/da-DK.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Vælg en indholdstype 5 | Rediger indhold 6 | Klik for at indsætte 7 | 8 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Lang/en-GB.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Select a Content Type 5 | Edit item 6 | Click to insert item 7 | 8 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Lang/en-US.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Select a Content Type 5 | Edit item 6 | Click to insert item 7 | 8 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Views/DocTypeGridEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
5 | 6 |
7 | {{title}} 8 |
9 |
10 | {{description}} 11 |
12 |
13 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/Our.Umbraco.DocTypeGridEditor/wwwroot/Views/doctypegrideditor.dialog.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 |

20 | 21 |

22 | 23 | 33 | 34 | 49 | 50 | 61 |
62 |
63 |
64 | 65 | 66 |
67 | 68 | 69 | 90 | 91 | 92 |
93 | 94 | 95 | 96 |
97 | 98 |
99 | 100 | 101 | 102 | 107 | 108 | 115 | 116 | 117 | 118 |
119 |
120 |
121 | --------------------------------------------------------------------------------