├── .gitignore ├── LICENSE.md ├── README.md ├── debug.bat ├── release.bat ├── releases └── nuget │ └── Skybrud.Umbraco.GridData.13.0.0.nupkg └── src ├── .editorconfig ├── Skybrud.Umbraco.GridData.sln ├── Skybrud.Umbraco.GridData ├── Composers │ ├── GridComposer.cs │ └── GridComposerExtensions.cs ├── Converters │ ├── GridConverterBase.cs │ ├── GridConverterCollection.cs │ ├── GridConverterCollectionBuilder.cs │ ├── IGridConverter.cs │ └── Umbraco │ │ └── UmbracoGridConverter.cs ├── Extensions │ └── TypedGridExtensionMethods.cs ├── Factories │ ├── DefaultGridFactory.cs │ └── IGridFactory.cs ├── GridContext.cs ├── GridPackage.cs ├── GridUtils.cs ├── Json │ └── Converters │ │ ├── GridControlValueStringConverter.cs │ │ └── GridJsonConverter.cs ├── Manifests │ └── GridManifestFilter.cs ├── Models │ ├── Config │ │ ├── GridEditorConfigBase.cs │ │ ├── GridEditorMediaConfig.cs │ │ ├── GridEditorMediaConfigSize.cs │ │ ├── GridEditorTextConfig.cs │ │ └── IGridEditorConfig.cs │ ├── GridArea.cs │ ├── GridControl.cs │ ├── GridDataModel.cs │ ├── GridDictionary.cs │ ├── GridDictionaryItem.cs │ ├── GridEditor.cs │ ├── GridElement.cs │ ├── GridJsonObject.cs │ ├── GridRow.cs │ ├── GridSection.cs │ └── Values │ │ ├── GridControlEmbedValue.cs │ │ ├── GridControlHtmlValue.cs │ │ ├── GridControlMacroValue.cs │ │ ├── GridControlMediaFocalPoint.cs │ │ ├── GridControlMediaValue.cs │ │ ├── GridControlRichTextValue.cs │ │ ├── GridControlTextValue.cs │ │ ├── GridControlValueBase.cs │ │ └── IGridControlValue.cs ├── Skybrud.Umbraco.GridData.csproj └── ValueConverters │ └── GridValueConverter.cs └── build └── Limbo.png /.gitignore: -------------------------------------------------------------------------------- 1 | backup 2 | dev/web/uSync.Archive 3 | 4 | # Grunt exclusions 5 | .sass-cache/* 6 | node_modules/* 7 | #dev/web/media 8 | dev/packages 9 | dev/web/App_Data/Import 10 | Markup/* 11 | !Markup/search.php 12 | 13 | 14 | # Do not match build files 15 | dev/web/App_Data/umbraco.config 16 | 17 | 18 | ##### File globs to match #### 19 | syntax: glob 20 | # Data folders/files 21 | */ExamineIndexes/* 22 | www/App_Data/umbraco.config 23 | */App_Browsers/* 24 | */App_Data/TEMP/* 25 | */App_Data/Logs/* 26 | */_systemUmbracoIndexDontDelete/* 27 | dev/web/App_Data/Logs/UmbracoTraceLog.txt.* 28 | dev/web/App_Data/preview/*.config 29 | 30 | 31 | # Diverse Frontend Cachedfiler + node.js moduler 32 | [Ss]ass-cache 33 | [Nn]ode_modules 34 | 35 | # System efterladte filer 36 | .DS_Store 37 | .DS_Store? 38 | ._* 39 | .Spotlight-V100 40 | .Trashes 41 | ehthumbs.db 42 | Thumbs.db 43 | 44 | 45 | obj 46 | #bin 47 | _ReSharper.* 48 | *.csproj.user 49 | *.resharper.user 50 | *.resharper 51 | *.suo 52 | *.cache 53 | *.Publish.xml 54 | ~$* 55 | 56 | 57 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 58 | #[Bb]in/ 59 | [Oo]bj/ 60 | 61 | # mstest test results 62 | TestResults 63 | 64 | ## Ignore Visual Studio temporary files, build results, and 65 | ## files generated by popular Visual Studio add-ons. 66 | 67 | # User-specific files 68 | *.suo 69 | *.user 70 | *.sln.docstates 71 | 72 | # Build results 73 | [Dd]ebug/ 74 | [Rr]elease/ 75 | x64/ 76 | *_i.c 77 | *_p.c 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.pch 82 | *.pdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opensdf 101 | *.sdf 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | 108 | # Guidance Automation Toolkit 109 | *.gpState 110 | 111 | # ReSharper is a .NET coding add-in 112 | _ReSharper* 113 | 114 | # NCrunch 115 | *.ncrunch* 116 | .*crunch*.local.xml 117 | 118 | # Installshield output folder 119 | [Ee]xpress 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish 133 | 134 | # Publish Web Output 135 | *.Publish.xml 136 | 137 | # NuGet Packages Directory 138 | packages 139 | 140 | # Windows Azure Build Output 141 | csx 142 | *.build.csdef 143 | 144 | # Windows Store app package directory 145 | AppPackages/ 146 | 147 | # Others 148 | #[Bb]in 149 | [Oo]bj 150 | sql 151 | TestResults 152 | [Tt]est[Rr]esult* 153 | *.Cache 154 | ClientBin 155 | [Ss]tyle[Cc]op.* 156 | ~$* 157 | *.dbmdl 158 | Generated_Code #added for RIA/Silverlight projects 159 | 160 | # Backup & report files from converting an old project file to a newer 161 | # Visual Studio version. Backup files are not needed, because we have git ;-) 162 | _UpgradeReport_Files/ 163 | Backup*/ 164 | UpgradeLog*.XML 165 | Backup af Stage inden Contour opgradering.zip 166 | Dev/web/App_Data/Logs/UmbracoTraceLog.txt 167 | Dev/web/App_Data/TEMP/ClientDependency/ 168 | Dev/web/App_Data/TEMP/ 169 | Dev/web/App_Data/access.config 170 | Dev/web/css/ 171 | Dev/web/img/ 172 | Dev/web/scripts/ 173 | Dev/web/bin/ 174 | Dev/web/media/ 175 | Dev/web/App_Data/ContentReminder.txt 176 | Dev/web/App_Data/umbraco.licensing.log.txt 177 | files/ 178 | src/.vs/ 179 | src/.idea/.idea.Skybrud.Umbraco.GridData/.idea/.name 180 | src/.idea/ 181 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 [Limbo](https://www.limbo.works/) 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Skybrud Grid Data 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/skybrud/Skybrud.Umbraco.GridData/blob/v13/main/LICENSE.md) 4 | [![NuGet](https://img.shields.io/nuget/vpre/Skybrud.Umbraco.GridData.svg)](https://www.nuget.org/packages/Skybrud.Umbraco.GridData) 5 | [![NuGet](https://img.shields.io/nuget/dt/Skybrud.Umbraco.GridData.svg)](https://www.nuget.org/packages/Skybrud.Umbraco.GridData) 6 | [![Umbraco Marketplace](https://img.shields.io/badge/umbraco-marketplace-%233544B1)](https://marketplace.umbraco.com/package/skybrud.umbraco.griddata) 7 | 8 | **Skybrud.Umbraco.GridData** is a package with a strongly typed model for the grid in Umbraco. The package makes it easy to work the grid in your MVC views, master pages or even in your custom logic - eg. to index the grid data in Examine for better searches. 9 | 10 | Version 13 of this package specifically targets Umbraco 3, but past major versions also support older versions of Umbraco. For the Umbraco 10-12 package, see v4/main branch. For the Umbraco 9 package, see v4/main branch. For the Umbraco 8 package, see v3/main branch. 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | ## Links 19 | 20 | - Installation 21 | - Examples 22 | - Indexing with Examine 23 | - Rendering the grid 24 | - Extending the grid 25 | 26 | 27 | 28 | 29 |

30 | ## Installation 31 | 32 | The Umbraco 13 version of this package is only available via NuGet. To install the package, you can use either .NET CLI: 33 | 34 | ``` 35 | dotnet add package Skybrud.Umbraco.GridData --version 13.0.0 36 | ``` 37 | 38 | or the older NuGet Package Manager: 39 | 40 | ``` 41 | Install-Package Skybrud.Umbraco.GridData -Version 13.0.0 42 | ``` 43 | 44 | **Umbraco 10-12** 45 | For the Umbraco 9 version of this package, see the [**v5/main**](https://github.com/skybrud/Skybrud.Umbraco.GridData/tree/v5/main) branch instead. 46 | ``` 47 | 48 | **Umbraco 9** 49 | For the Umbraco 9 version of this package, see the [**v4/main**](https://github.com/skybrud/Skybrud.Umbraco.GridData/tree/v4/main) branch instead. 50 | 51 | **Umbraco 8** 52 | For the Umbraco 8 version of this package, see the [**v3/main**](https://github.com/skybrud/Skybrud.Umbraco.GridData/tree/v3/main) branch instead. 53 | 54 | 55 | 56 | 57 | 58 | 59 |

60 | 61 | ## Add-ons 62 | 63 | - [**Skybrud.Umbraco.GridData.Dtge**](https://github.com/skybrud/Skybrud.Umbraco.GridData.Dtge) 64 | Adds support for working with [**Doc Type Grid Editor**](https://github.com/skttl/umbraco-doc-type-grid-editor). 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

74 | 75 | ## Examples 76 | 77 | The package has its own property value converter, so you can simply get the grid model as: 78 | 79 | ```C# 80 | GridDataModel grid = Model.Content.GetPropertyValue("content"); 81 | ``` 82 | 83 | If you have the raw JSON string, you can parse it like: 84 | 85 | ```C# 86 | GridDataModel grid = GridDataModel.Deserialize(json); 87 | ``` 88 | 89 | But you can also just call an extension method to get the grid model: 90 | 91 | ```C# 92 | GridDataModel grid = Model.Content.GetGridModel("content"); 93 | ``` 94 | 95 | The benefit of the extension method is that it will always return an instance of `GridDataModel` - even if the property doesn't exists or doesn't have a value, so you don't have to check whether the returned value is `null`. However if you need it, you can use the `IsValid` property to validate that the model is valid (eg. not empty). 96 | 97 | 98 | 99 | 100 | 101 |

102 | 103 | ## Indexing with Examine 104 | 105 | As of `v2.0`, the `GridDataModel` contains a `GetSearchableText` method that will return a textual representation of the entire grid model - see the example below: 106 | 107 | ```C# 108 | @using Skybrud.Umbraco.GridData 109 | @using Skybrud.Umbraco.GridData.Extensions 110 | @inherits UmbracoTemplatePage 111 | @{ 112 | 113 | GridDataModel grid = Model.Content.GetGridModel("content"); 114 | 115 |
@grid.GetSearchableText()
116 | 117 | } 118 | ``` 119 | 120 | The `GetSearchableText` method works by traversing all the controls of the grid, and calling a similar `GetSearchableText` method on each control. The end result will then be a string combined of the returned values from all the controls. 121 | 122 | This of course requires that each control (or the model of it's value, really) can provide a textual representation of it's value. 123 | 124 | If you need further control of the indexing, you can have a look at this example Gist: 125 | 126 | * [Gist: Indexing the Umbraco Grid.md](https://gist.github.com/abjerner/bdd89e0788d274ec5a33) 127 | 128 | 129 | 130 | 131 | 132 |

133 | 134 | ## Rendering the grid 135 | 136 | The package supports a number of different ways to render the grid. If we start out with the entire grid model, you can do something like (`Fanoe` is the framework/view that should be used for rendering the grid): 137 | 138 | ```C# 139 | @using Skybrud.Umbraco.GridData 140 | @using Skybrud.Umbraco.GridData.Extensions 141 | @inherits UmbracoTemplatePage 142 | @{ 143 | 144 | GridDataModel grid = Model.Content.GetGridModel("content"); 145 | 146 | @Html.GetTypedGridHtml(grid, "Fanoe") 147 | 148 | } 149 | ``` 150 | 151 | This works by first getting the grid value, and then rendering the model into the current view. This can also be done in a single line instead (`Model.Content` as specified for the first parameter is an instance of `IPublishedContent`): 152 | 153 | ```C# 154 | @using Skybrud.Umbraco.GridData.Extensions 155 | @inherits UmbracoTemplatePage 156 | 157 | @Html.GetTypedGridHtml(Model.Content, "content", "Fanoe") 158 | ``` 159 | 160 | Since both examples specifies the `Fanoe` view, the package will look for a partial view located at `~/Views/Partials/TypedGrid/Fanoe.cshtml` and with an instance of `GridDataModel` as the model. You can find an example of this partial view at the link below: 161 | 162 | https://github.com/abjerner/UmbracoGridDataDemo/blob/master/dev/web/Views/Partials/TypedGrid/Fanoe.cshtml 163 | 164 | You can also have a look at an example partial view for rendering the individual rows of the grid: 165 | 166 | https://github.com/abjerner/UmbracoGridDataDemo/blob/master/dev/web/Views/Partials/TypedGrid/Rows/Default.cshtml 167 | 168 | 169 | 170 | 171 | 172 |

173 | 174 | ## Extending the grid 175 | 176 | The package will only provide models for the grid editors thats comes with Umbraco by default (as well as the editors from the Fanoe starter kit), but it is also possible to create your own models for custom controls. 177 | 178 | This process might however be a bit complex, so I've written an article for [**Skrift.io**](http://skrift.io/) that describes this a bit further: 179 | 180 | http://skrift.io/articles/archive/strongly-typed-models-in-the-umbraco-grid/ 181 | -------------------------------------------------------------------------------- /debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet build src/Skybrud.Umbraco.GridData --configuration Debug /t:rebuild /t:pack -p:PackageOutputPath=c:\nuget\Umbraco13 -------------------------------------------------------------------------------- /release.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet build src/Skybrud.Umbraco.GridData --configuration Release /t:rebuild /t:pack -p:PackageOutputPath=../../releases/nuget -------------------------------------------------------------------------------- /releases/nuget/Skybrud.Umbraco.GridData.13.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skybrud/Skybrud.Umbraco.GridData/24e85ae1fc414cc56ea5550da9e1073eeab0e6ac/releases/nuget/Skybrud.Umbraco.GridData.13.0.0.nupkg -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # Version 1.0.7 2 | 3 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 4 | root = true 5 | 6 | # C# files 7 | [*.cs] 8 | 9 | #### Core EditorConfig Options #### 10 | 11 | # Indentation and spacing 12 | indent_size = 4 13 | indent_style = space 14 | tab_width = 4 15 | trim_trailing_whitespace = true 16 | 17 | # New line preferences 18 | end_of_line = crlf 19 | insert_final_newline = false 20 | 21 | #### .NET Coding Conventions #### 22 | 23 | # Organize usings 24 | dotnet_separate_import_directive_groups = false 25 | dotnet_sort_system_directives_first = true 26 | 27 | # this. and Me. preferences 28 | dotnet_style_qualification_for_event = false:silent 29 | dotnet_style_qualification_for_field = false:silent 30 | dotnet_style_qualification_for_method = false:silent 31 | dotnet_style_qualification_for_property = false:silent 32 | 33 | # Language keywords vs BCL types preferences 34 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 35 | dotnet_style_predefined_type_for_member_access = true:warning 36 | 37 | # Parentheses preferences 38 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 39 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 40 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 41 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 42 | 43 | # Modifier preferences 44 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 45 | 46 | # Expression-level preferences 47 | dotnet_style_coalesce_expression = true:suggestion 48 | dotnet_style_collection_initializer = true:suggestion 49 | dotnet_style_explicit_tuple_names = true:suggestion 50 | dotnet_style_null_propagation = true:suggestion 51 | dotnet_style_object_initializer = true:suggestion 52 | dotnet_style_prefer_auto_properties = true:silent 53 | dotnet_style_prefer_compound_assignment = true:suggestion 54 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 55 | dotnet_style_prefer_conditional_expression_over_return = true:silent 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 57 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 59 | 60 | # Field preferences 61 | dotnet_style_readonly_field = true:suggestion 62 | 63 | # Parameter preferences 64 | dotnet_code_quality_unused_parameters = all:suggestion 65 | 66 | #### C# Coding Conventions #### 67 | 68 | # var preferences 69 | csharp_style_var_elsewhere = false:silent 70 | csharp_style_var_for_built_in_types = false:silent 71 | csharp_style_var_when_type_is_apparent = false:silent 72 | 73 | # Expression-bodied members 74 | csharp_style_expression_bodied_accessors = true:silent 75 | csharp_style_expression_bodied_constructors = false:silent 76 | csharp_style_expression_bodied_indexers = true:silent 77 | csharp_style_expression_bodied_lambdas = true:silent 78 | csharp_style_expression_bodied_local_functions = false:silent 79 | csharp_style_expression_bodied_methods = false:silent 80 | csharp_style_expression_bodied_operators = false:silent 81 | csharp_style_expression_bodied_properties = true:silent 82 | 83 | # Pattern matching preferences 84 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 85 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 86 | csharp_style_prefer_switch_expression = true:suggestion 87 | 88 | # Null-checking preferences 89 | csharp_style_conditional_delegate_call = true:suggestion 90 | 91 | # Modifier preferences 92 | csharp_prefer_static_local_function = true:suggestion 93 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 94 | 95 | # Code-block preferences 96 | csharp_prefer_braces = true:silent 97 | csharp_prefer_simple_using_statement = true:suggestion 98 | 99 | # Expression-level preferences 100 | csharp_prefer_simple_default_expression = true:suggestion 101 | csharp_style_deconstructed_variable_declaration = true:suggestion 102 | csharp_style_inlined_variable_declaration = true:suggestion 103 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 104 | csharp_style_prefer_index_operator = true:suggestion 105 | csharp_style_prefer_range_operator = true:suggestion 106 | csharp_style_throw_expression = true:suggestion 107 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 108 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 109 | 110 | # 'using' directive preferences 111 | csharp_using_directive_placement = outside_namespace:warning 112 | 113 | #### C# Formatting Rules #### 114 | 115 | # New line preferences 116 | csharp_new_line_before_catch = false 117 | csharp_new_line_before_else = false 118 | csharp_new_line_before_finally = false 119 | csharp_new_line_before_members_in_anonymous_types = true 120 | csharp_new_line_before_members_in_object_initializers = true 121 | csharp_new_line_before_open_brace = none 122 | csharp_new_line_between_query_expression_clauses = true 123 | 124 | # Indentation preferences 125 | csharp_indent_block_contents = true 126 | csharp_indent_braces = false 127 | csharp_indent_case_contents = true 128 | csharp_indent_case_contents_when_block = true 129 | csharp_indent_labels = no_change 130 | csharp_indent_switch_labels = true 131 | 132 | # Space preferences 133 | csharp_space_after_cast = true 134 | csharp_space_after_colon_in_inheritance_clause = true 135 | csharp_space_after_comma = true 136 | csharp_space_after_dot = false 137 | csharp_space_after_keywords_in_control_flow_statements = true 138 | csharp_space_after_semicolon_in_for_statement = true 139 | csharp_space_around_binary_operators = before_and_after 140 | csharp_space_around_declaration_statements = false 141 | csharp_space_before_colon_in_inheritance_clause = true 142 | csharp_space_before_comma = false 143 | csharp_space_before_dot = false 144 | csharp_space_before_open_square_brackets = false 145 | csharp_space_before_semicolon_in_for_statement = false 146 | csharp_space_between_empty_square_brackets = false 147 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 148 | csharp_space_between_method_call_name_and_opening_parenthesis = false 149 | csharp_space_between_method_call_parameter_list_parentheses = false 150 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 151 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 152 | csharp_space_between_method_declaration_parameter_list_parentheses = false 153 | csharp_space_between_parentheses = false 154 | csharp_space_between_square_brackets = false 155 | 156 | # Wrapping preferences 157 | csharp_preserve_single_line_blocks = true 158 | csharp_preserve_single_line_statements = true 159 | 160 | #### Naming styles #### 161 | 162 | # Naming rules 163 | 164 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 165 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 166 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 167 | 168 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 169 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 170 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 171 | 172 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 173 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 174 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 175 | 176 | # Symbol specifications 177 | 178 | dotnet_naming_symbols.interface.applicable_kinds = interface 179 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 180 | dotnet_naming_symbols.interface.required_modifiers = 181 | 182 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 183 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 184 | dotnet_naming_symbols.types.required_modifiers = 185 | 186 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 187 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 188 | dotnet_naming_symbols.non_field_members.required_modifiers = 189 | 190 | # Naming styles 191 | 192 | dotnet_naming_style.pascal_case.required_prefix = 193 | dotnet_naming_style.pascal_case.required_suffix = 194 | dotnet_naming_style.pascal_case.word_separator = 195 | dotnet_naming_style.pascal_case.capitalization = pascal_case 196 | 197 | dotnet_naming_style.begins_with_i.required_prefix = I 198 | dotnet_naming_style.begins_with_i.required_suffix = 199 | dotnet_naming_style.begins_with_i.word_separator = 200 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 201 | 202 | # Define what we will treat as private fields 203 | dotnet_naming_symbols.private_fields.applicable_kinds = field 204 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 205 | 206 | # Define rule that something must begin with an underscore and be in camel case 207 | dotnet_naming_style.require_underscore_prefix_and_camel_case.required_prefix = _ 208 | dotnet_naming_style.require_underscore_prefix_and_camel_case.capitalization = camel_case 209 | 210 | # Apply our rule to private fields 211 | dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.symbols = private_fields 212 | dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.style = require_underscore_prefix_and_camel_case 213 | dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.severity = warning 214 | 215 | # Prefer file-scoped namespace declarations 216 | csharp_style_namespace_declarations = file_scoped:warning 217 | 218 | # Don't prefer or suggest primary constructors 219 | csharp_style_prefer_primary_constructors = false 220 | -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31112.23 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skybrud.Umbraco.GridData", "Skybrud.Umbraco.GridData\Skybrud.Umbraco.GridData.csproj", "{99DC24EA-4933-4BB9-B6A1-BF565E9E93C0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {99DC24EA-4933-4BB9-B6A1-BF565E9E93C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {99DC24EA-4933-4BB9-B6A1-BF565E9E93C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {99DC24EA-4933-4BB9-B6A1-BF565E9E93C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {99DC24EA-4933-4BB9-B6A1-BF565E9E93C0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {57B0500C-D959-4699-AB7D-481ACDEC9F0A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Composers/GridComposer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Skybrud.Umbraco.GridData.Converters.Umbraco; 3 | using Skybrud.Umbraco.GridData.Factories; 4 | using Skybrud.Umbraco.GridData.Manifests; 5 | using Umbraco.Cms.Core.Composing; 6 | using Umbraco.Cms.Core.DependencyInjection; 7 | using Umbraco.Extensions; 8 | 9 | namespace Skybrud.Umbraco.GridData.Composers; 10 | 11 | internal class GridComposer : IComposer { 12 | 13 | public void Compose(IUmbracoBuilder builder) { 14 | 15 | builder.Services.AddSingleton(); 16 | builder.Services.AddUnique(); 17 | 18 | builder.GridConverters().Append(); 19 | 20 | builder.ManifestFilters().Append(); 21 | 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Composers/GridComposerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Skybrud.Umbraco.GridData.Converters; 2 | using Umbraco.Cms.Core.DependencyInjection; 3 | 4 | // ReSharper disable InconsistentNaming 5 | 6 | namespace Skybrud.Umbraco.GridData.Composers; 7 | 8 | /// 9 | /// Static class with extension methods for composing the grid. 10 | /// 11 | public static class GridComposerExtensions { 12 | 13 | /// 14 | /// Returns the current instance of . 15 | /// 16 | /// The current . 17 | /// An instance of . 18 | public static GridConverterCollectionBuilder GridConverters(this IUmbracoBuilder builder) { 19 | return builder.WithCollectionBuilder(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Converters/GridConverterBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.IO; 5 | using Newtonsoft.Json.Linq; 6 | using Skybrud.Umbraco.GridData.Models; 7 | using Skybrud.Umbraco.GridData.Models.Config; 8 | using Skybrud.Umbraco.GridData.Models.Values; 9 | using Umbraco.Cms.Core.Models.PublishedContent; 10 | 11 | namespace Skybrud.Umbraco.GridData.Converters; 12 | 13 | /// 14 | /// Abstract base implementation of . 15 | /// 16 | public abstract class GridConverterBase : IGridConverter { 17 | 18 | /// 19 | /// Attemtps to get the type of the configuration object of the specified . 20 | /// 21 | /// The editor. 22 | /// When this method returns, holds an instance of representing the type if successful; otherwise, . 23 | /// if successful; otherwise, . 24 | public virtual bool TryGetConfigType(GridEditor editor, [NotNullWhen(true)] out Type? type) { 25 | type = null; 26 | return false; 27 | } 28 | 29 | /// 30 | /// Attempts to get the type of the value of the specified . 31 | /// 32 | /// The control. 33 | /// When this method returns, holds an instance of representing the type if successful; otherwise, . 34 | /// if successful; otherwise, . 35 | public virtual bool TryGetValueType(GridControl control, [NotNullWhen(true)] out Type? type) { 36 | type = null; 37 | return false; 38 | } 39 | 40 | /// 41 | /// Converts the specified into an instance of . 42 | /// 43 | /// A reference to the parent . 44 | /// The instance of representing the control value. 45 | /// The converted control value. 46 | public virtual bool TryConvertControlValue(GridControl control, JToken token, [NotNullWhen(true)] out IGridControlValue? value) { 47 | value = null; 48 | return false; 49 | } 50 | 51 | /// 52 | /// Converts the specified into an instance of . 53 | /// 54 | /// A reference to the parent . 55 | /// The instance of representing the editor config. 56 | /// The converted editor config. 57 | public virtual bool TryConvertEditorConfig(GridEditor editor, JToken token, [NotNullWhen(true)] out IGridEditorConfig? config) { 58 | config = null; 59 | return false; 60 | } 61 | 62 | /// 63 | /// Writes a string representation of to . 64 | /// 65 | /// The current grid context. 66 | /// The element. 67 | /// The writer. 68 | /// if successful; otherwise, . 69 | public virtual bool TryWriteSearchableText(GridContext context, IPublishedElement element, TextWriter writer) { 70 | return false; 71 | } 72 | 73 | /// 74 | /// Attempts to check whether the specified represents a valid grid control value. 75 | /// 76 | /// The value to check. 77 | /// When this method returns, holds a boolean value indicating whether is valid if successful; otherwise, . 78 | /// if successful; otherwise, . 79 | public virtual bool TryGetValid(IGridControlValue value, out bool result) { 80 | result = false; 81 | return false; 82 | } 83 | 84 | /// 85 | /// Attempts to check whether the specified represents a valid element. 86 | /// 87 | /// The element. 88 | /// When this method returns, holds a boolean value indicating whether is valid if successful; otherwise, . 89 | /// if successful; otherwise, . 90 | public virtual bool TryGetValid(IPublishedElement element, out bool result) { 91 | result = false; 92 | return false; 93 | } 94 | 95 | /// 96 | /// Returns whether is contained in (case insensitive). 97 | /// 98 | /// The source string. 99 | /// The value to search for. 100 | /// true if contains ; otherwise false. 101 | protected bool ContainsIgnoreCase(string source, string value) { 102 | if (string.IsNullOrWhiteSpace(source)) return false; 103 | if (string.IsNullOrWhiteSpace(value)) return false; 104 | return CultureInfo.InvariantCulture.CompareInfo.IndexOf(source, value, CompareOptions.IgnoreCase) >= 0; 105 | } 106 | 107 | /// 108 | /// Returns whether is equal (case insensitive). 109 | /// 110 | /// The source string. 111 | /// The value to search for. 112 | /// true if equal to ; otherwise false. 113 | protected bool EqualsIgnoreCase(string source, string value) { 114 | if (string.IsNullOrWhiteSpace(source)) return false; 115 | if (string.IsNullOrWhiteSpace(value)) return false; 116 | return source.Equals(value, StringComparison.InvariantCultureIgnoreCase); 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Converters/GridConverterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Umbraco.Cms.Core.Composing; 4 | 5 | namespace Skybrud.Umbraco.GridData.Converters; 6 | 7 | /// 8 | /// Collection of . 9 | /// 10 | public class GridConverterCollection : BuilderCollectionBase { 11 | 12 | /// 13 | /// Initializes a new converter collection based on the specified . 14 | /// 15 | /// The items to make up the collection. 16 | public GridConverterCollection(Func> items) : base(items) { } 17 | 18 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Converters/GridConverterCollectionBuilder.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Cms.Core.Composing; 2 | 3 | namespace Skybrud.Umbraco.GridData.Converters; 4 | 5 | /// 6 | public class GridConverterCollectionBuilder : OrderedCollectionBuilderBase { 7 | 8 | /// 9 | protected override GridConverterCollectionBuilder This => this; 10 | 11 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Converters/IGridConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.IO; 4 | using Newtonsoft.Json.Linq; 5 | using Skybrud.Umbraco.GridData.Models; 6 | using Skybrud.Umbraco.GridData.Models.Config; 7 | using Skybrud.Umbraco.GridData.Models.Values; 8 | using Umbraco.Cms.Core.Models.PublishedContent; 9 | 10 | namespace Skybrud.Umbraco.GridData.Converters; 11 | 12 | /// 13 | /// Interface describing a Grid converter. 14 | /// 15 | public interface IGridConverter { 16 | 17 | /// 18 | /// Attemtps to get the type of the configuration object of the specified . 19 | /// 20 | /// The editor. 21 | /// When this method returns, holds an instance of representing the type if successful; otherwise, . 22 | /// if successful; otherwise, . 23 | bool TryGetConfigType(GridEditor editor, [NotNullWhen(true)] out Type? type); 24 | 25 | /// 26 | /// Attempts to get the type of the value of the specified . 27 | /// 28 | /// The control. 29 | /// When this method returns, holds an instance of representing the type if successful; otherwise, . 30 | /// if successful; otherwise, . 31 | bool TryGetValueType(GridControl control, [NotNullWhen(true)] out Type? type); 32 | 33 | /// 34 | /// Attempts to convert the specified into an instance of . 35 | /// 36 | /// The parent control. 37 | /// The instance of representing the control value. 38 | /// The converted value. 39 | /// if successful; otherwise, . 40 | bool TryConvertControlValue(GridControl control, JToken token, [NotNullWhen(true)] out IGridControlValue? value); 41 | 42 | /// 43 | /// Attempts to convert the specified into an instance of . 44 | /// 45 | /// 46 | /// The instance of representing the editor config. 47 | /// The converted config. 48 | /// if successful; otherwise, . 49 | bool TryConvertEditorConfig(GridEditor editor, JToken token, [NotNullWhen(true)] out IGridEditorConfig? config); 50 | 51 | /// 52 | /// Attempts to write a string representation of to . 53 | /// 54 | /// The current grid context. 55 | /// The element. 56 | /// The writer. 57 | /// if successful; otherwise, . 58 | bool TryWriteSearchableText(GridContext context, IPublishedElement element, TextWriter writer); 59 | 60 | /// 61 | /// Attempts to check whether the specified represents a valid grid control value. 62 | /// 63 | /// The value to check. 64 | /// When this method returns, holds a boolean value indicating whether is valid if successful; otherwise, . 65 | /// if successful; otherwise, . 66 | bool TryGetValid(IGridControlValue value, out bool result); 67 | 68 | /// 69 | /// Attempts to check whether the specified represents a valid element. 70 | /// 71 | /// The element. 72 | /// When this method returns, holds a boolean value indicating whether is valid if successful; otherwise, . 73 | /// if successful; otherwise, . 74 | bool TryGetValid(IPublishedElement element, out bool result); 75 | 76 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Converters/Umbraco/UmbracoGridConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Umbraco.GridData.Models; 5 | using Skybrud.Umbraco.GridData.Models.Config; 6 | using Skybrud.Umbraco.GridData.Models.Values; 7 | using Umbraco.Cms.Core.Web; 8 | 9 | #pragma warning disable CS1591 10 | 11 | namespace Skybrud.Umbraco.GridData.Converters.Umbraco; 12 | 13 | /// 14 | /// Converter for handling the default editors (and their values and configs) of Umbraco. 15 | /// 16 | public class UmbracoGridConverter : GridConverterBase { 17 | 18 | private readonly IUmbracoContextAccessor _umbracoContextAccessor; 19 | 20 | public UmbracoGridConverter(IUmbracoContextAccessor umbracoContextAccessor) { 21 | _umbracoContextAccessor = umbracoContextAccessor; 22 | } 23 | 24 | public override bool TryGetConfigType(GridEditor editor, [NotNullWhen(true)] out Type? type) { 25 | 26 | type = null; 27 | 28 | if (IsMediaEditor(editor)) { 29 | type = typeof(GridEditorMediaConfig); 30 | } else if (IsTextStringEditor(editor)) { 31 | type = typeof(GridEditorTextConfig); 32 | } 33 | 34 | return type != null; 35 | 36 | } 37 | 38 | public override bool TryGetValueType(GridControl control, [NotNullWhen(true)] out Type? type) { 39 | 40 | type = null; 41 | 42 | if (IsEmbedEditor(control.Editor)) { 43 | type = typeof(GridControlEmbedValue); 44 | } else if (IsMacroEditor(control.Editor)) { 45 | type = typeof(GridControlMacroValue); 46 | } else if (IsMediaEditor(control.Editor)) { 47 | type = typeof(GridControlMediaValue); 48 | } else if (IsRichTextEditor(control.Editor)) { 49 | type = typeof(GridControlRichTextValue); 50 | } else if (IsTextStringEditor(control.Editor)) { 51 | type = typeof(GridControlTextValue); 52 | } 53 | 54 | return type != null; 55 | 56 | } 57 | 58 | /// 59 | /// Converts the specified into an instance of . 60 | /// 61 | /// The parent control. 62 | /// The instance of representing the control value. 63 | /// The converted value. 64 | public override bool TryConvertControlValue(GridControl control, JToken token, [NotNullWhen(true)] out IGridControlValue? value) { 65 | 66 | value = null; 67 | 68 | if (IsEmbedEditor(control.Editor)) { 69 | value = new GridControlEmbedValue(control); 70 | } else if (IsMacroEditor(control.Editor)) { 71 | value = new GridControlMacroValue(control); 72 | } else if (IsMediaEditor(control.Editor)) { 73 | value = ParseGridControlMediaValue(control); 74 | } else if (IsRichTextEditor(control.Editor)) { 75 | value = new GridControlRichTextValue(control); 76 | } else if (IsTextStringEditor(control.Editor)) { 77 | value = new GridControlTextValue(control); 78 | } 79 | 80 | return value != null; 81 | 82 | } 83 | 84 | protected virtual IGridControlValue ParseGridControlMediaValue(GridControl control) { 85 | 86 | GridControlMediaValue value = new(control); 87 | 88 | if (value.Id > 0 && _umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? context)) { 89 | value.PublishedImage = context.Media?.GetById(value.Id); 90 | } 91 | 92 | return value; 93 | 94 | } 95 | 96 | /// 97 | /// Converts the specified into an instance of . 98 | /// 99 | /// 100 | /// The instance of representing the editor config. 101 | /// The converted config. 102 | public override bool TryConvertEditorConfig(GridEditor editor, JToken token, [NotNullWhen(true)] out IGridEditorConfig? config) { 103 | 104 | config = null; 105 | 106 | if (IsMediaEditor(editor)) { 107 | config = new GridEditorMediaConfig((JObject) token, editor); 108 | } else if (IsTextStringEditor(editor)) { 109 | config = new GridEditorTextConfig((JObject) token, editor); 110 | } 111 | 112 | return config != null; 113 | 114 | } 115 | 116 | protected static bool IsEmbedEditor(GridEditor? editor) { 117 | return editor?.View == "embed"; 118 | } 119 | 120 | protected static bool IsTextStringEditor(GridEditor? editor) { 121 | return editor?.View == "textstring"; 122 | } 123 | 124 | protected static bool IsMediaEditor(GridEditor? editor) { 125 | return editor?.View == "media"; 126 | } 127 | 128 | protected static bool IsMacroEditor(GridEditor? editor) { 129 | return editor?.View == "macro"; 130 | } 131 | 132 | protected static bool IsRichTextEditor(GridEditor? editor) { 133 | return editor?.View == "rte"; 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Extensions/TypedGridExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Skybrud.Umbraco.GridData.Models; 2 | using Umbraco.Cms.Core.Models.PublishedContent; 3 | using Umbraco.Extensions; 4 | 5 | namespace Skybrud.Umbraco.GridData.Extensions; 6 | 7 | /// 8 | /// Class holding various extension methods for using the typed Grid. 9 | /// 10 | public static class TypedGridExtensionMethods { 11 | 12 | #region Constants 13 | 14 | /// 15 | /// Gets the default framework for rendering the Grid. 16 | /// 17 | public const string DefaultFramework = "bootstrap3"; 18 | 19 | #endregion 20 | 21 | #region Static methods 22 | 23 | /// 24 | /// Returns a instance representing the value of the property with the specified 25 | /// . If the property doesn't exist, or it's value doesn't match a 26 | /// instance, a instance representing an empty grid 27 | /// model is returned instead. 28 | /// 29 | /// The parent content item. 30 | /// The alias of the property. 31 | public static GridDataModel GetGridModel(this IPublishedContent? content, string propertyAlias) { 32 | return content?.Value(propertyAlias) ?? GridDataModel.GetEmptyModel(); 33 | } 34 | 35 | /// 36 | /// Returns a instance representing the value of the property with the specified 37 | /// . If the property doesn't exist, or it's value doesn't match a 38 | /// instance, is returned instead. 39 | /// 40 | /// The parent content item. 41 | /// The alias of the property. 42 | public static GridDataModel? GetGridModelorNull(this IPublishedContent? content, string propertyAlias) { 43 | return content?.Value(propertyAlias); 44 | } 45 | 46 | /// 47 | /// Attempts to get a instance from the property with the specified . 48 | /// 49 | /// The parent content item. 50 | /// The alias of the property. 51 | /// When this method returns, holds the instance if successful; otherwise, . 52 | /// if successful; otherwise, . 53 | public static bool TryGetGridModel(this IPublishedContent? content, string propertyAlias, out GridDataModel? result) { 54 | result = content?.Value(propertyAlias); 55 | return result != null; 56 | } 57 | 58 | #endregion 59 | 60 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Factories/DefaultGridFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json.Linq; 5 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 6 | using Skybrud.Umbraco.GridData.Converters; 7 | using Skybrud.Umbraco.GridData.Models; 8 | using Skybrud.Umbraco.GridData.Models.Values; 9 | using Umbraco.Cms.Core.Configuration.Grid; 10 | using Umbraco.Cms.Core.Models.PublishedContent; 11 | using IGridEditorConfig = Skybrud.Umbraco.GridData.Models.Config.IGridEditorConfig; 12 | 13 | #pragma warning disable CS1591 14 | 15 | namespace Skybrud.Umbraco.GridData.Factories; 16 | 17 | public class DefaultGridFactory : IGridFactory { 18 | 19 | private readonly ILogger _logger; 20 | private readonly IGridConfig _gridConfig; 21 | private readonly GridConverterCollection _converters; 22 | 23 | #region Constructors 24 | 25 | public DefaultGridFactory(ILogger logger, IGridConfig gridConfig, GridConverterCollection converters) { 26 | _logger = logger; 27 | _gridConfig = gridConfig; 28 | _converters = converters; 29 | } 30 | 31 | #endregion 32 | 33 | #region Member methods 34 | 35 | /// 36 | public virtual GridDataModel CreateGridModel(IPublishedElement owner, IPublishedPropertyType propertyType, JObject json, bool preview) { 37 | return new GridDataModel(owner, propertyType, json, this); 38 | } 39 | 40 | /// 41 | public virtual GridSection CreateGridSection(JObject json, GridDataModel grid) { 42 | return new GridSection(json, grid, this); 43 | } 44 | 45 | /// 46 | public virtual GridRow CreateGridRow(JObject json, GridSection section) { 47 | return new GridRow(json, section, this); 48 | } 49 | 50 | /// 51 | public virtual GridArea CreateGridArea(JObject json, GridRow row) { 52 | return new GridArea(json, row, this); 53 | } 54 | 55 | /// 56 | public virtual GridControl CreateGridControl(JObject json, GridArea area) { 57 | 58 | // The saved JSON for the editor only contains the alias of the editor as other information may change over 59 | // time. As a result of this, we need to inject a new editor object into the JSON. 60 | ReplaceEditorObjectFromConfig(json); 61 | 62 | // Parse the Grid editor (undelrying type may be generic ... or not) 63 | GridEditor editor = json.GetObject("editor", CreateGridEditor)!; 64 | 65 | // Initialize a new Grid control 66 | GridControl control = new(json, area) { 67 | // Make sure to set the editor before we parse the control value 68 | Editor = editor 69 | }; 70 | 71 | // Parse the control value 72 | control.Value = ParseGridControlValue(control); 73 | 74 | // Get the type of the editor config (it may not have a config) 75 | Type? configType = control.Editor.Config?.GetType(); 76 | 77 | // Determine the value type 78 | Type? valueType = null; 79 | foreach (IGridConverter converter in _converters) { 80 | if (converter.TryGetValueType(control, out valueType)) break; 81 | } 82 | 83 | // If no converters specify a value type, we just return the control right away 84 | if (valueType == null) return control; 85 | 86 | // If the editor doesn't have a configuration, we can create a new generic type from just the value type. 87 | // If we both have a value type and config type, we create a new generic type from both types 88 | if (configType == null) { 89 | Type genericType = typeof(GridControl<>).MakeGenericType(valueType); 90 | control = (GridControl) Activator.CreateInstance(genericType, control)!; 91 | } else { 92 | Type genericType = typeof(GridControl<,>).MakeGenericType(valueType, configType); 93 | control = (GridControl) Activator.CreateInstance(genericType, control, editor)!; 94 | } 95 | 96 | // Return the control 97 | return control; 98 | 99 | } 100 | 101 | /// 102 | public virtual GridEditor CreateGridEditor(JObject json) { 103 | 104 | Type? configType = null; 105 | 106 | // Initialize a new Grid editor 107 | GridEditor editor = new(json); 108 | 109 | foreach (var converter in _converters) { 110 | 111 | if (converter.TryGetConfigType(editor, out configType)) break; 112 | 113 | } 114 | 115 | if (configType != null) { 116 | 117 | Type genericType = typeof(GridEditor<>).MakeGenericType(configType); 118 | 119 | editor = (GridEditor) Activator.CreateInstance(genericType, editor)!; 120 | 121 | } 122 | 123 | // Parse the grid editor configuration 124 | editor.Config = ParseGridEditorConfig(editor); 125 | 126 | // Return the editor 127 | return editor; 128 | 129 | } 130 | 131 | protected virtual IGridControlValue? ParseGridControlValue(GridControl control) { 132 | 133 | // Parse the control value 134 | JToken? value = control.JObject.GetValue("value"); 135 | if (value is null) return null; 136 | 137 | foreach (IGridConverter converter in _converters) { 138 | try { 139 | if (!converter.TryConvertControlValue(control, value, out IGridControlValue? converted)) continue; 140 | return converted; 141 | } catch (Exception ex) { 142 | _logger.LogError(ex, $"Converter of type {converter} failed for ConvertControlValue()"); 143 | } 144 | } 145 | 146 | return null; 147 | 148 | } 149 | 150 | protected virtual IGridEditorConfig? ParseGridEditorConfig(GridEditor editor) { 151 | 152 | // Parse the editor configuration 153 | JToken? config = editor.JObject.GetValue("config"); 154 | if (config is null) return null; 155 | 156 | foreach (IGridConverter converter in _converters) { 157 | try { 158 | if (!converter.TryConvertEditorConfig(editor, config, out IGridEditorConfig? converted)) continue; 159 | return converted; 160 | } catch (Exception ex) { 161 | _logger.LogError(ex, $"Converter of type {converter} failed for ConvertEditorConfig()"); 162 | } 163 | } 164 | 165 | return null; 166 | 167 | } 168 | 169 | protected virtual void ReplaceEditorObjectFromConfig(JObject json) { 170 | 171 | // Get the "editor" object from the JSON 172 | JObject? editor = json.GetObject("editor"); 173 | if (editor is null) return; 174 | 175 | // Get the alias of the editor 176 | string? alias = editor.GetString("alias"); 177 | 178 | // Skip if we dont have an alias 179 | if (string.IsNullOrWhiteSpace(alias)) return; 180 | 181 | // Find the editor in the configuration 182 | var found = _gridConfig.EditorsConfig.Editors.FirstOrDefault(x => x.Alias == alias); 183 | 184 | // Skip if not found 185 | if (found == null) return; 186 | 187 | // Set a new editor object with the updated config 188 | json["editor"] = new JObject { 189 | {"name", found.Name}, 190 | {"alias", found.Alias}, 191 | {"view", found.View}, 192 | {"render", found.Render}, 193 | {"icon", found.Icon}, 194 | {"config", JObject.FromObject(found.Config)} 195 | }; 196 | 197 | } 198 | 199 | #endregion 200 | 201 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Factories/IGridFactory.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using Skybrud.Umbraco.GridData.Models; 3 | using Umbraco.Cms.Core.Models.PublishedContent; 4 | 5 | namespace Skybrud.Umbraco.GridData.Factories; 6 | 7 | /// 8 | /// Interface describing a factory for initializing the various parts of the Grid. 9 | /// 10 | public interface IGridFactory { 11 | 12 | /// 13 | /// Returns a new from the specified object. 14 | /// 15 | /// and may be specified if the grid value comes directly from a property value. If either aren't available, it's fine to specify null for both of them. 16 | /// 17 | /// An instance of representing the owner holding the grid value. 18 | /// An instance of representing the property holding the grid value. 19 | /// The instance of representing the grid model. 20 | /// 21 | /// An instance of representing the grid model. 22 | GridDataModel CreateGridModel(IPublishedElement owner, IPublishedPropertyType propertyType, JObject json, bool preview); 23 | 24 | /// 25 | /// Returns a new instance of from the specified object. 26 | /// 27 | /// The instance of representing the grid section. 28 | /// An instance of representing the parent grid model. 29 | /// An instance of representing the grid section. 30 | GridSection CreateGridSection(JObject json, GridDataModel grid); 31 | 32 | /// 33 | /// Creates a new based on the specified object. 34 | /// 35 | /// An instance of representing the row. 36 | /// A reference to the parent . 37 | /// An instance of . 38 | GridRow CreateGridRow(JObject json, GridSection section); 39 | 40 | /// 41 | /// Creates a new based on the specified object. 42 | /// 43 | /// An instance of representing the area. 44 | /// A reference to the parent . 45 | /// An instance of . 46 | GridArea CreateGridArea(JObject json, GridRow row); 47 | 48 | /// 49 | /// Creates a new based on the specified object. 50 | /// 51 | /// An instance of representing the control. 52 | /// A reference to the parent . 53 | /// An instance of . 54 | GridControl CreateGridControl(JObject json, GridArea area); 55 | 56 | /// 57 | /// Creates a new based on the specified object. 58 | /// 59 | /// An instance of representing the editor. 60 | /// An instance of . 61 | GridEditor CreateGridEditor(JObject json); 62 | 63 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/GridContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Microsoft.Extensions.Logging; 5 | using Skybrud.Umbraco.GridData.Converters; 6 | using Umbraco.Cms.Core.Models.PublishedContent; 7 | 8 | namespace Skybrud.Umbraco.GridData; 9 | 10 | /// 11 | /// Singleton class used for configuring and using the grid. 12 | /// 13 | public class GridContext { 14 | 15 | private readonly ILogger _logger; 16 | private readonly GridConverterCollection _converterCollection; 17 | 18 | #region Constructors 19 | 20 | /// 21 | /// Initializes a new instance based on the specified dependencies. 22 | /// 23 | /// A reference to the current logger. 24 | /// A reference to the current . 25 | public GridContext(ILogger logger, GridConverterCollection converterCollection) { 26 | _logger = logger; 27 | _converterCollection = converterCollection; 28 | } 29 | 30 | #endregion 31 | 32 | #region Member methods 33 | 34 | /// 35 | /// Writes a string representation of to . 36 | /// 37 | /// The element. 38 | /// The writer. 39 | public virtual void WriteSearchableText(IPublishedElement element, TextWriter writer) { 40 | foreach (IGridConverter converter in _converterCollection) { 41 | try { 42 | if (converter.TryWriteSearchableText(this, element, writer)) return; 43 | } catch (Exception ex) { 44 | _logger.LogError(ex, $"Converter of type {converter} failed for WriteSearchableText()"); 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// Returns a string representation of the specified . 51 | /// 52 | /// The element. 53 | /// A string representation of . 54 | public virtual string GetSearchableText(IPublishedElement element) { 55 | StringBuilder sb = new(); 56 | using TextWriter writer = new StringWriter(sb); 57 | WriteSearchableText(element, writer); 58 | return sb.ToString(); 59 | } 60 | 61 | #endregion 62 | 63 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/GridPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Umbraco.Cms.Core.Semver; 4 | 5 | namespace Skybrud.Umbraco.GridData; 6 | 7 | /// 8 | /// Static class with various information and constants about the package. 9 | /// 10 | public class GridPackage { 11 | 12 | /// 13 | /// Gets the alias of the package. 14 | /// 15 | public const string Alias = "Skybrud.Umbraco.GridData"; 16 | 17 | /// 18 | /// Gets the friendly name of the package. 19 | /// 20 | public const string Name = "Skybrud Grid Data"; 21 | 22 | /// 23 | /// Gets the version of the package. 24 | /// 25 | public static readonly Version Version = typeof(GridPackage).Assembly.GetName().Version!; 26 | 27 | /// 28 | /// Gets the informational version of the package. 29 | /// 30 | public static readonly string InformationalVersion = FileVersionInfo.GetVersionInfo(typeof(GridPackage).Assembly.Location).ProductVersion!; 31 | 32 | /// 33 | /// Gets the semantic version of the package. 34 | /// 35 | public static readonly SemVersion SemVersion = InformationalVersion; 36 | 37 | /// 38 | /// Gets the URL of the GitHub repository for this package. 39 | /// 40 | public const string GitHubUrl = "https://github.com/skybrud/Skybrud.Umbraco.GridData/"; 41 | 42 | /// 43 | /// Gets the URL of the issue tracker for this package. 44 | /// 45 | public const string IssuesUrl = "https://github.com/skybrud/Skybrud.Umbraco.GridData/issues"; 46 | 47 | /// 48 | /// Gets the website URL of the package. 49 | /// 50 | public const string WebsiteUrl = "https://packages.skybrud.dk/skybrud.umbraco.griddata/v5/"; 51 | 52 | /// 53 | /// Gets the URL of the documentation for this package. 54 | /// 55 | public const string DocumentationUrl = "https://packages.skybrud.dk/skybrud.umbraco.griddata/v5/docs/"; 56 | 57 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/GridUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Text.RegularExpressions; 4 | using Skybrud.Essentials.Reflection; 5 | 6 | namespace Skybrud.Umbraco.GridData; 7 | 8 | /// 9 | /// Various utility methods for the grid. 10 | /// 11 | public static class GridUtils { 12 | 13 | #region Version specific methods 14 | 15 | /// 16 | /// Gets the assembly version as a string. 17 | /// 18 | public static string GetVersion() { 19 | return ReflectionUtils.GetVersion(typeof(GridUtils).Assembly); 20 | } 21 | 22 | /// 23 | /// Gets the file version as a string. 24 | /// 25 | /// 26 | public static string GetFileVersion() { 27 | Assembly assembly = typeof(GridUtils).Assembly; 28 | return FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion!; 29 | } 30 | 31 | #endregion 32 | 33 | #region Rendering 34 | 35 | /// 36 | /// Gets whether the specified is a valid partial name. 37 | /// 38 | /// The name of the partial. 39 | /// true if is valid; otherwise false. 40 | public static bool IsValidPartialName(string? name) { 41 | return name is not null && Regex.IsMatch(name, "^[a-zA-Z0-9-\\._ ]+$"); 42 | } 43 | 44 | #endregion 45 | 46 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Json/Converters/GridControlValueStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Skybrud.Umbraco.GridData.Models.Values; 4 | 5 | namespace Skybrud.Umbraco.GridData.Json.Converters; 6 | 7 | /// 8 | /// Converter for text based grid control values. 9 | /// 10 | public class GridControlValueStringConverter : JsonConverter { 11 | 12 | /// 13 | /// Writes the JSON representation of the object. 14 | /// 15 | /// The to write to. 16 | /// The value. 17 | /// The calling serializer. 18 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { 19 | 20 | if (value is GridControlTextValue text) { 21 | writer.WriteValue(text.Value); 22 | return; 23 | } 24 | 25 | serializer.Serialize(writer, value); 26 | 27 | } 28 | 29 | /// 30 | /// Reads the JSON representation of the object. 31 | /// 32 | /// The to read from. 33 | /// Type of the object. 34 | /// The existing value of object being read. 35 | /// The calling serializer. 36 | /// The object value. 37 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { 38 | throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter."); 39 | } 40 | 41 | /// 42 | /// Gets a value indicating whether this Newtonsoft.Json.JsonConverter can read JSON. 43 | /// 44 | public override bool CanRead => false; 45 | 46 | /// 47 | /// Determines whether this instance can convert the specified object type. 48 | /// 49 | /// Type of the object. 50 | /// true if this instance can convert the specified object type; otherwise false. 51 | public override bool CanConvert(Type type) { 52 | return false; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Json/Converters/GridJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Skybrud.Umbraco.GridData.Models; 4 | 5 | namespace Skybrud.Umbraco.GridData.Json.Converters; 6 | 7 | /// 8 | /// Converter for dictionary based values in the grid. 9 | /// 10 | public class GridJsonConverter : JsonConverter { 11 | 12 | /// 13 | /// Writes the JSON representation of the object. 14 | /// 15 | /// The to write to. 16 | /// The value. 17 | /// The calling serializer. 18 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { 19 | 20 | if (value is GridJsonObject obj) { 21 | serializer.Serialize(writer, obj.JObject); 22 | return; 23 | } 24 | 25 | serializer.Serialize(writer, value); 26 | 27 | } 28 | 29 | /// 30 | /// Reads the JSON representation of the object. 31 | /// 32 | /// The to read from. 33 | /// Type of the object. 34 | /// The existing value of object being read. 35 | /// The calling serializer. 36 | /// The object value. 37 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { 38 | throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter."); 39 | } 40 | 41 | /// 42 | /// Gets a value indicating whether this Newtonsoft.Json.JsonConverter can read JSON. 43 | /// 44 | public override bool CanRead => false; 45 | 46 | /// 47 | /// Determines whether this instance can convert the specified object type. 48 | /// 49 | /// Type of the object. 50 | /// true if this instance can convert the specified object type; otherwise false. 51 | public override bool CanConvert(Type type) { 52 | return false; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Manifests/GridManifestFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Umbraco.Cms.Core.Manifest; 3 | 4 | namespace Skybrud.Umbraco.GridData.Manifests; 5 | 6 | /// 7 | public class GridManifestFilter : IManifestFilter { 8 | 9 | /// 10 | public void Filter(List manifests) { 11 | manifests.Add(new PackageManifest { 12 | AllowPackageTelemetry = true, 13 | PackageId = GridPackage.Alias, 14 | PackageName = GridPackage.Name, 15 | Version = GridPackage.InformationalVersion 16 | }); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Config/GridEditorConfigBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Umbraco.GridData.Models.Values; 4 | 5 | namespace Skybrud.Umbraco.GridData.Models.Config; 6 | 7 | /// 8 | /// Abstract class with a basic implementation of the interface. 9 | /// 10 | public abstract class GridEditorConfigBase : GridJsonObject, IGridEditorConfig { 11 | 12 | #region Properties 13 | 14 | /// 15 | /// Gets a reference to the parent editor of the configuration. 16 | /// 17 | [JsonIgnore] 18 | public GridEditor Editor { get; } 19 | 20 | #endregion 21 | 22 | #region Constructors 23 | 24 | /// 25 | /// Initializes a new instance based on the specified object. 26 | /// 27 | /// An instance of representing the configuration of the editor. 28 | /// An instance of representing the parent editor. 29 | protected GridEditorConfigBase(JObject json, GridEditor editor) : base(json) { 30 | Editor = editor; 31 | } 32 | 33 | #endregion 34 | 35 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Config/GridEditorMediaConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 4 | 5 | namespace Skybrud.Umbraco.GridData.Models.Config; 6 | 7 | /// 8 | /// Class representing the configuration of a media editor. 9 | /// 10 | public class GridEditorMediaConfig : GridEditorConfigBase { 11 | 12 | #region Properties 13 | 14 | /// 15 | /// Gets an object describing the desired size of the media. 16 | /// 17 | [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] 18 | public GridEditorMediaConfigSize Size { get; } 19 | 20 | #endregion 21 | 22 | #region Constructors 23 | 24 | /// 25 | /// Initializes a new instance based on the specified object. 26 | /// 27 | /// An instance of representing the configuration of the editor. 28 | /// An instance of representing the parent editor. 29 | public GridEditorMediaConfig(JObject json, GridEditor editor) : base(json, editor) { 30 | Size = json.GetObject("size", GridEditorMediaConfigSize.Parse)!; 31 | } 32 | 33 | #endregion 34 | 35 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Config/GridEditorMediaConfigSize.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models.Config; 7 | 8 | /// 9 | /// Class describing the desired size of a media (image). 10 | /// 11 | public class GridEditorMediaConfigSize : GridJsonObject { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// Gets the desired width of the media. 17 | /// 18 | [JsonProperty("width")] 19 | public int Width { get; } 20 | 21 | /// 22 | /// Gets the desired height of the media. 23 | /// 24 | [JsonProperty("height")] 25 | public int Height { get; } 26 | 27 | #endregion 28 | 29 | #region Constructors 30 | 31 | private GridEditorMediaConfigSize(JObject json) : base(json) { 32 | Width = json.GetInt32("width"); 33 | Height = json.GetInt32("height"); 34 | } 35 | 36 | #endregion 37 | 38 | #region Static methods 39 | 40 | /// 41 | /// Gets an instance of from the specified object. 42 | /// 43 | /// The instance of to be parsed. 44 | [return: NotNullIfNotNull("json")] 45 | public static GridEditorMediaConfigSize? Parse(JObject? json) { 46 | return json == null ? null : new GridEditorMediaConfigSize(json); 47 | } 48 | 49 | #endregion 50 | 51 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Config/GridEditorTextConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 4 | 5 | namespace Skybrud.Umbraco.GridData.Models.Config; 6 | 7 | /// 8 | /// Class representing the configuration of a text editor. 9 | /// 10 | public class GridEditorTextConfig : GridEditorConfigBase { 11 | 12 | #region Properties 13 | 14 | /// 15 | /// Gets the style properties for the text. 16 | /// 17 | [JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)] 18 | public string Style { get; private set; } 19 | 20 | /// 21 | /// Gets whether the property has a value. 22 | /// 23 | public bool HasStyle => !string.IsNullOrWhiteSpace(Style); 24 | 25 | /// 26 | /// Gets the markup for the text. 27 | /// 28 | [JsonProperty("markup", NullValueHandling = NullValueHandling.Ignore)] 29 | public string Markup { get; private set; } 30 | 31 | /// 32 | /// Gets whether the property has a value. 33 | /// 34 | public bool HasMarkup => !string.IsNullOrWhiteSpace(Markup); 35 | 36 | #endregion 37 | 38 | #region Constructors 39 | 40 | /// 41 | /// Initializes a new instance based on the specified object. 42 | /// 43 | /// An instance of representing the configuration of the editor. 44 | /// An instance of representing the parent editor. 45 | public GridEditorTextConfig(JObject json, GridEditor editor) : base(json, editor) { 46 | Style = json.GetString("style")!; 47 | Markup = json.GetString("markup")!; 48 | } 49 | 50 | #endregion 51 | 52 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Config/IGridEditorConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Skybrud.Umbraco.GridData.Models.Config; 4 | 5 | /// 6 | /// Interface describing a grid editor config. 7 | /// 8 | public interface IGridEditorConfig { 9 | 10 | /// 11 | /// Gets a reference to the parent editor of the configuration. 12 | /// 13 | [JsonIgnore] 14 | GridEditor Editor { get; } 15 | 16 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridArea.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 8 | using Skybrud.Umbraco.GridData.Factories; 9 | 10 | namespace Skybrud.Umbraco.GridData.Models; 11 | 12 | /// 13 | /// Class representing an area in an Umbraco Grid. 14 | /// 15 | public class GridArea : GridElement { 16 | 17 | #region Properties 18 | 19 | /// 20 | /// Gets a reference to the entire . 21 | /// 22 | [JsonIgnore] 23 | public GridDataModel Model => Section.Model; 24 | 25 | /// 26 | /// Gets a reference to the parent . 27 | /// 28 | [JsonIgnore] 29 | public GridSection Section => Row.Section; 30 | 31 | /// 32 | /// Gets a reference to the parent . 33 | /// 34 | [JsonIgnore] 35 | public GridRow Row { get; } 36 | 37 | /// 38 | /// Gets the column width of the area. 39 | /// 40 | public int Grid { get; } 41 | 42 | /// 43 | /// Gets wether all editors are allowed for this area. 44 | /// 45 | public bool AllowAll { get; } 46 | 47 | /// 48 | /// Gets an array of all editors allowed for this area. If is true, this 49 | /// array may be empty. 50 | /// 51 | public string[] Allowed { get; } 52 | 53 | /// 54 | /// Gets an array of all controls added to this area. 55 | /// 56 | public IReadOnlyList Controls { get; } 57 | 58 | /// 59 | /// Gets a reference to the previous area. 60 | /// 61 | public GridArea? PreviousArea { get; internal set; } 62 | 63 | /// 64 | /// Gets a reference to the next area. 65 | /// 66 | public GridArea? NextArea { get; internal set; } 67 | 68 | /// 69 | /// Gets whether the area has any controls. 70 | /// 71 | public bool HasControls => Controls.Count > 0; 72 | 73 | /// 74 | /// Gets the first control of the area. If the area doesn't contain 75 | /// any controls, this property will return null. 76 | /// 77 | public GridControl? FirstControl => Controls.FirstOrDefault(); 78 | 79 | /// 80 | /// Gets the last control of the area. If the area doesn't contain 81 | /// any controls, this property will return null. 82 | /// 83 | public GridControl? LastControl => Controls.LastOrDefault(); 84 | 85 | /// 86 | /// Gets whether at least one control within the area is valid. 87 | /// 88 | public override bool IsValid { 89 | get { return Controls.Any(x => x.IsValid); } 90 | } 91 | 92 | #endregion 93 | 94 | #region Constructors 95 | 96 | /// 97 | /// Initializes a new instance based on the specified object, and . 98 | /// 99 | /// An instance of representing the section. 100 | /// The parent row. 101 | /// The factory used for parsing subsequent parts of the grid. 102 | public GridArea(JObject json, GridRow row, IGridFactory factory) : base(json) { 103 | 104 | Row = row; 105 | Grid = json.GetInt32("grid"); 106 | AllowAll = json.GetBoolean("allowAll"); 107 | Allowed = json.GetStringArray("allowed"); 108 | Controls = json.GetArray("controls", x => factory.CreateGridControl(x, this)) ?? []; 109 | 110 | // Update "PreviousControl" and "NextControl" properties 111 | for (int i = 1; i < Controls.Count; i++) { 112 | Controls[i - 1].NextControl = Controls[i]; 113 | Controls[i].PreviousControl = Controls[i - 1]; 114 | } 115 | 116 | } 117 | 118 | #endregion 119 | 120 | #region Member methods 121 | 122 | /// 123 | /// Writes a string representation of the area to . 124 | /// 125 | /// The current grid context. 126 | /// The writer. 127 | public override void WriteSearchableText(GridContext context, TextWriter writer) { 128 | foreach (GridControl control in Controls) control.WriteSearchableText(context, writer); 129 | } 130 | 131 | #endregion 132 | 133 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridControl.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Skybrud.Umbraco.GridData.Models.Config; 6 | using Skybrud.Umbraco.GridData.Models.Values; 7 | 8 | namespace Skybrud.Umbraco.GridData.Models; 9 | 10 | /// 11 | /// Class representing a control in an Umbraco Grid. 12 | /// 13 | public class GridControl : GridJsonObject { 14 | 15 | #region Properties 16 | 17 | /// 18 | /// Gets a reference to the entire . 19 | /// 20 | [JsonIgnore] 21 | public GridDataModel Model => Section.Model; 22 | 23 | /// 24 | /// Gets a reference to the parent . 25 | /// 26 | [JsonIgnore] 27 | public GridSection Section => Row.Section; 28 | 29 | /// 30 | /// Gets a reference to the parent . 31 | /// 32 | [JsonIgnore] 33 | public GridRow Row => Area.Row; 34 | 35 | /// 36 | /// Gets a reference to the parent . 37 | /// 38 | [JsonIgnore] 39 | public GridArea Area { get; } 40 | 41 | /// 42 | /// Gets the value of the control. Alternately use the method to get the type safe value. 43 | /// 44 | [JsonProperty("value")] 45 | public IGridControlValue? Value { get; internal set; } 46 | 47 | /// 48 | /// Gets a reference to the editor of the control. 49 | /// 50 | [JsonProperty("editor")] 51 | public GridEditor Editor { get; internal set; } 52 | 53 | /// 54 | /// Gets a reference to the previous control. 55 | /// 56 | public GridControl? PreviousControl { get; internal set; } 57 | 58 | /// 59 | /// Gets a reference to the next control. 60 | /// 61 | public GridControl? NextControl { get; internal set; } 62 | 63 | /// 64 | /// Gets whether the control and it's value is valid. 65 | /// 66 | [JsonIgnore] 67 | public bool IsValid => Value is { IsValid: true }; 68 | 69 | #endregion 70 | 71 | #region Constructors 72 | 73 | /// 74 | /// Initializes a new instance from the specified object and . 75 | /// 76 | /// The instance of representing the control. 77 | /// An instance of representing the parent area. 78 | internal GridControl(JObject json, GridArea area) : base(json) { 79 | Area = area; 80 | Value = null!; 81 | Editor = null!; 82 | } 83 | 84 | internal GridControl(GridControl control) : base(control.JObject) { 85 | Area = control.Area; 86 | Value = control.Value; 87 | Editor = control.Editor; 88 | PreviousControl = control.PreviousControl; 89 | NextControl = control.NextControl; 90 | } 91 | 92 | #endregion 93 | 94 | #region Member methods 95 | 96 | /// 97 | /// Returns the value of the control cast to the type of . 98 | /// 99 | /// The type of the value to be returned. 100 | public T? GetValue() where T : IGridControlValue { 101 | return Value is T value ? value : default; 102 | } 103 | 104 | /// 105 | /// Writes a string representation of the control to . 106 | /// 107 | /// The current grid context. 108 | /// The writer. 109 | public void WriteSearchableText(GridContext context, TextWriter writer) { 110 | Value?.WriteSearchableText(context, writer); 111 | } 112 | 113 | /// 114 | /// Returns the value of the control as a searchable text - e.g. to be used in Examine. 115 | /// 116 | /// The current grid context. 117 | /// An instance of with the value as a searchable text. 118 | public string GetSearchableText(GridContext context) { 119 | StringBuilder sb = new(); 120 | using TextWriter writer = new StringWriter(sb); 121 | WriteSearchableText(context, writer); 122 | return sb.ToString(); 123 | } 124 | 125 | #endregion 126 | 127 | } 128 | 129 | /// 130 | /// Class representing a grid control where the value is of type . 131 | /// 132 | /// The type of the value. 133 | public class GridControl : GridControl where TValue : IGridControlValue { 134 | 135 | /// 136 | /// Gets the value of the control. 137 | /// 138 | [JsonProperty("value")] 139 | public new TValue Value => (TValue) base.Value!; 140 | 141 | /// 142 | /// Initializes a new instance based on the specified . 143 | /// 144 | /// The control. 145 | public GridControl(GridControl control) : base(control) { } 146 | 147 | } 148 | 149 | /// 150 | /// Class representing a grid control where the value is of type and an editor with a config of type . 151 | /// 152 | /// The type of the value. 153 | /// The type of the editor config. 154 | public class GridControl : GridControl where TValue : IGridControlValue where TConfig : IGridEditorConfig { 155 | 156 | /// 157 | /// Gets a reference to the editor of the control. 158 | /// 159 | [JsonProperty("editor")] 160 | public new GridEditor Editor { get; } 161 | 162 | /// 163 | /// Initializes a new instance based on the specified and . 164 | /// 165 | /// The control. 166 | /// The editor. 167 | public GridControl(GridControl control, GridEditor editor) : base(control) { 168 | Editor = editor; 169 | } 170 | 171 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridDataModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 10 | using Skybrud.Umbraco.GridData.Factories; 11 | using Umbraco.Cms.Core.Models.PublishedContent; 12 | 13 | namespace Skybrud.Umbraco.GridData.Models; 14 | 15 | /// 16 | /// Class representing the value/model saved by an Umbraco Grid property. 17 | /// 18 | public class GridDataModel : GridJsonObject { 19 | 20 | #region Properties 21 | 22 | /// 23 | /// Gets whether the model is valid. The model is considered valid if it has been parsed from a JSON value and 24 | /// has at least one valid control. 25 | /// 26 | public bool IsValid { 27 | get { return JObject != null! && GetAllControls().Any(x => x.IsValid); } 28 | } 29 | 30 | /// 31 | /// Gets the name of the selected layout. 32 | /// 33 | public string Name { get; } 34 | 35 | /// 36 | /// Gets an array of the columns in the grid. 37 | /// 38 | public IReadOnlyList Sections { get; } 39 | 40 | /// 41 | /// Gets a reference to the parent , if the Grid model was loaded directly from a property value. 42 | /// 43 | [JsonIgnore] 44 | public IPublishedElement? Owner { get; } 45 | 46 | /// 47 | /// Gets whether the grid model has a reference to it's owner. 48 | /// 49 | [JsonIgnore] 50 | [MemberNotNullWhen(true, "Owner")] 51 | public bool HasOwner => Owner != null; 52 | 53 | /// 54 | /// Gets a reference to the parent property type, if the Grid model was loaded directly from a property value. 55 | /// 56 | public IPublishedPropertyType? PropertyType { get; } 57 | 58 | /// 59 | /// Gets whether a property type has been specified for the model. 60 | /// 61 | [MemberNotNullWhen(true, "PropertyType")] 62 | public bool HasPropertyType => PropertyType != null; 63 | 64 | #endregion 65 | 66 | #region Constructors 67 | 68 | /// 69 | /// Initializes a new instance based on the specified object. 70 | /// 71 | /// and may be specified if the grid value comes directly from a property value. If either aren't available, it's fine to specify null for both of them. 72 | /// 73 | /// An instance of representing the owner holding the grid value. 74 | /// An instance of representing the property holding the grid value. 75 | /// An instance of representing the grid model. 76 | /// The factory used for parsing subsequent parts of the grid. 77 | public GridDataModel(IPublishedElement? owner, IPublishedPropertyType? propertyType, JObject? json, IGridFactory? factory) : base(json ?? new JObject()) { 78 | 79 | Owner = owner; 80 | PropertyType = propertyType; 81 | Name = json.GetString("name")!; 82 | 83 | if (factory is null) { 84 | Sections = []; 85 | } else { 86 | Sections = json.GetArray("sections", x => factory.CreateGridSection(x, this)) ?? []; 87 | } 88 | 89 | } 90 | 91 | #endregion 92 | 93 | #region Member methods 94 | 95 | /// 96 | /// Returns a list of all nested controls. 97 | /// 98 | public IReadOnlyList GetAllControls() { 99 | return ( 100 | from section in Sections 101 | from row in section.Rows 102 | from area in row.Areas 103 | from control in area.Controls 104 | select control 105 | ).ToArray(); 106 | } 107 | 108 | /// 109 | /// Returns a list of all nested controls with the specified editor . 110 | /// 111 | /// The editor alias of controls to be returned. 112 | public IReadOnlyList GetAllControls(string alias) { 113 | return GetAllControls(x => x.Editor.Alias == alias); 114 | } 115 | 116 | /// 117 | /// Returns a list of all nested controls matching the specified . 118 | /// 119 | /// The predicate (callback function) used for comparison. 120 | public IReadOnlyList GetAllControls(Func predicate) { 121 | return ( 122 | from section in Sections 123 | from row in section.Rows 124 | from area in row.Areas 125 | from control in area.Controls 126 | where predicate(control) 127 | select control 128 | ).ToArray(); 129 | } 130 | 131 | /// 132 | /// Writes a string representation of the grid data model to . 133 | /// 134 | /// The current grid context. 135 | /// The writer. 136 | public void WriteSearchableText(GridContext context, TextWriter writer) { 137 | foreach (GridSection section in Sections) section.WriteSearchableText(context, writer); 138 | } 139 | 140 | /// 141 | /// Returns a textual representation of the grid model - e.g. to be used in Examine. 142 | /// 143 | /// The current grid context. 144 | /// An instance of representing the value of the element. 145 | public string GetSearchableText(GridContext context) { 146 | StringBuilder sb = new(); 147 | using TextWriter writer = new StringWriter(sb); 148 | WriteSearchableText(context, writer); 149 | return sb.ToString(); 150 | } 151 | 152 | #endregion 153 | 154 | #region Static methods 155 | 156 | /// 157 | /// Returns an empty (and invalid) model. This method can be used to get a fallback value for when an actual Grid model isn't available. 158 | /// 159 | public static GridDataModel GetEmptyModel() { 160 | return new GridDataModel(null, null, null, null); 161 | } 162 | 163 | /// 164 | /// Returns an empty (and invalid) model. This method can be used to get a fallback value for when an actual Grid model isn't available. 165 | /// 166 | /// An instance of representing the owner holding the grid value. 167 | /// An instance of representing the property holding the grid value. 168 | public static GridDataModel GetEmptyModel(IPublishedElement owner, IPublishedPropertyType propertyType) { 169 | return new GridDataModel(owner, propertyType, null, null); 170 | } 171 | 172 | #endregion 173 | 174 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Globalization; 5 | using System.Linq; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace Skybrud.Umbraco.GridData.Models; 10 | 11 | /// 12 | /// Dictionary representing a configuration for an element in the Umbraco Grid. 13 | /// 14 | public class GridDictionary : GridJsonObject, IEnumerable { 15 | 16 | #region Private fields 17 | 18 | private readonly Dictionary _dictionary; 19 | 20 | #endregion 21 | 22 | #region Properties 23 | 24 | /// 25 | /// Gets the keys of the underlying dictionary. 26 | /// 27 | [JsonIgnore] 28 | public string[] Keys => [.. _dictionary.Keys]; 29 | 30 | /// 31 | /// Gets the keys of the underlying dictionary. 32 | /// 33 | [JsonIgnore] 34 | public string[] Values => [.. _dictionary.Keys]; 35 | 36 | /// 37 | /// Gets the amount of items in the dictionary. 38 | /// 39 | public int Count => _dictionary.Count; 40 | 41 | /// 42 | /// Gets the value of an item with the specified . 43 | /// 44 | /// The key of the dictionary item. 45 | public string this[string key] => _dictionary[key]; 46 | 47 | #endregion 48 | 49 | #region Constructors 50 | 51 | private GridDictionary(Dictionary config, JObject json) : base(json) { 52 | _dictionary = config; 53 | } 54 | 55 | #endregion 56 | 57 | #region Member methods 58 | 59 | /// 60 | /// Gets whether the specified is contained in the dictionary. 61 | /// 62 | /// The key. 63 | public bool ContainsKey(string key) { 64 | return _dictionary.ContainsKey(key); 65 | } 66 | 67 | /// 68 | /// Gets the value associated with the specified . 69 | /// 70 | /// The key of the value to get. 71 | /// When this method returns, contains the value associated with the specified key, if the 72 | /// key is found; otherwise, the default value for the type of the value parameter. This parameter is passed 73 | /// uninitialized. 74 | /// true if the dictionary contains an element with the specified key; otherwise, false. 75 | public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { 76 | return _dictionary.TryGetValue(key, out value); 77 | } 78 | 79 | /// 80 | /// Returns an enumerator that iterates through the collection. 81 | /// 82 | /// An enumerator that can be used to iterate through the collection. 83 | public IEnumerator GetEnumerator() { 84 | return _dictionary.Select(x => new GridDictionaryItem(x.Key, x.Value)).GetEnumerator(); 85 | } 86 | 87 | IEnumerator IEnumerable.GetEnumerator() { 88 | return GetEnumerator(); 89 | } 90 | 91 | #endregion 92 | 93 | #region Static methods 94 | 95 | /// 96 | /// Parses the specified into an instance of . 97 | /// 98 | /// The instance of to be parsed. 99 | /// An instance of . 100 | public static GridDictionary Parse(JObject? json) { 101 | 102 | // Initialize an empty dictionary 103 | Dictionary config = new(); 104 | 105 | // Add all properties to the dictionary 106 | if (json != null) { 107 | foreach (JProperty property in json.Properties()) { 108 | config.Add(property.Name, string.Format(CultureInfo.InvariantCulture, "{0}", property.Value)); 109 | } 110 | } 111 | 112 | // Return the instance 113 | return new GridDictionary(config, json ?? new JObject()); 114 | 115 | } 116 | 117 | #endregion 118 | 119 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridDictionaryItem.cs: -------------------------------------------------------------------------------- 1 | namespace Skybrud.Umbraco.GridData.Models; 2 | 3 | /// 4 | /// Class representing an item of . 5 | /// 6 | public class GridDictionaryItem { 7 | 8 | #region Properties 9 | 10 | /// 11 | /// Gets the key of the item. 12 | /// 13 | public string Key { get; } 14 | 15 | /// 16 | /// Gets the value of the item. 17 | /// 18 | public string Value { get; } 19 | 20 | #endregion 21 | 22 | #region Constructors 23 | 24 | /// 25 | /// Initializes a new item based on the specified and . 26 | /// 27 | /// The key of the item. 28 | /// The value of the item. 29 | public GridDictionaryItem(string key, string value) { 30 | Key = key; 31 | Value = value; 32 | } 33 | 34 | #endregion 35 | 36 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridEditor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 4 | using Skybrud.Umbraco.GridData.Models.Config; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models; 7 | 8 | /// 9 | /// Class representing an editor of a control in an Umbraco Grid. 10 | /// 11 | public class GridEditor : GridJsonObject { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// Gets the name of the editor. 17 | /// 18 | [JsonProperty("name")] 19 | public string? Name { get; } 20 | 21 | /// 22 | /// Gets the alias of the editor. 23 | /// 24 | [JsonProperty("alias")] 25 | public string Alias { get; } 26 | 27 | /// 28 | /// Gets the view of the editor. 29 | /// 30 | [JsonProperty("view")] 31 | public string? View { get; } 32 | 33 | /// 34 | /// Gets renderer for the control/editor. If specified, the renderer refers to a partial 35 | /// view that should be used for rendering the control. 36 | /// 37 | [JsonProperty("render")] 38 | public string? Render { get; } 39 | 40 | /// 41 | /// Gets the icon of the editor. 42 | /// 43 | [JsonProperty("icon")] 44 | public string? Icon { get; } 45 | 46 | /// 47 | /// Gets the configuration object for the editor. This property will return null if the 48 | /// corresponding property in the underlying JSON is also null. 49 | /// 50 | [JsonProperty("config", NullValueHandling = NullValueHandling.Ignore)] 51 | public IGridEditorConfig? Config { get; internal set; } 52 | 53 | #endregion 54 | 55 | #region Constructors 56 | 57 | /// 58 | /// Initializes a new instance with the specified . 59 | /// 60 | /// The alias of the editor. 61 | public GridEditor(string alias) : base(null ?? new JObject()) { 62 | Alias = alias; 63 | } 64 | 65 | /// 66 | /// Initializes a new instance based on the specified object. 67 | /// 68 | /// The instance of representing the control. 69 | public GridEditor(JObject json) : base(json) { 70 | 71 | // Parse basic properties 72 | Name = json.GetString("name")!; 73 | Alias = json.GetString("alias")!; 74 | View = json.GetString("view")!; 75 | Render = json.GetString("render"); 76 | Icon = json.GetString("icon")!; 77 | 78 | } 79 | 80 | /// 81 | /// Initializes a new instance based on the specified . 82 | /// 83 | /// The editor to be wrapped. 84 | public GridEditor(GridEditor editor) : base(editor.JObject) { 85 | Name = editor.Name; 86 | Alias = editor.Alias; 87 | View = editor.View; 88 | Render = editor.Render; 89 | Icon = editor.Icon; 90 | } 91 | 92 | #endregion 93 | 94 | #region Member methods 95 | 96 | /// 97 | /// Returns the config of the editor cast to the type of . 98 | /// 99 | /// The type of the config to be returned. 100 | public T? GetConfig() where T : IGridEditorConfig { 101 | return Config is T value ? value : default; 102 | } 103 | 104 | #endregion 105 | 106 | } 107 | 108 | /// 109 | /// Class representing an editor where the config is of type . 110 | /// 111 | /// The type of the editor config. 112 | public class GridEditor : GridEditor where TConfig : IGridEditorConfig { 113 | 114 | /// 115 | /// Gets the editor config. 116 | /// 117 | public new TConfig Config => (TConfig) base.Config!; 118 | 119 | /// 120 | /// Initializes a new instance based on the specified . 121 | /// 122 | /// The editor to be wrapped. 123 | public GridEditor(GridEditor editor) : base(editor) { } 124 | 125 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridElement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models; 7 | 8 | /// 9 | /// Class representing a generic element in an Umbraco Grid. 10 | /// 11 | public abstract class GridElement : GridJsonObject { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// Gets a dictionary representing the configuration (called Settings in the backoffice) of the element. 17 | /// 18 | public GridDictionary Config { get; } 19 | 20 | /// 21 | /// Gets whetehr the element has one or more config values. 22 | /// 23 | public bool HasConfig => Config is { Count: > 0 }; 24 | 25 | /// 26 | /// Gets a dictionary representing the styles of the element. 27 | /// 28 | public GridDictionary Styles { get; } 29 | 30 | /// 31 | /// Gets whetehr the element has one or more style values. 32 | /// 33 | public bool HasStyles => Styles is { Count: > 0 }; 34 | 35 | /// 36 | /// Gets whether at least one control within the element is valid. 37 | /// 38 | public abstract bool IsValid { get; } 39 | 40 | #endregion 41 | 42 | #region Constructors 43 | 44 | /// 45 | /// Initializes a new instance based on the specified . 46 | /// 47 | /// An instance of representing the area. 48 | protected GridElement(JObject json) : base(json) { 49 | Styles = json.GetObject("styles", GridDictionary.Parse)!; 50 | Config = json.GetObject("config", GridDictionary.Parse)!; 51 | } 52 | 53 | #endregion 54 | 55 | #region Member methods 56 | 57 | /// 58 | /// Writes a string representation of the element to . 59 | /// 60 | /// The current grid context. 61 | /// The writer. 62 | public abstract void WriteSearchableText(GridContext context, TextWriter writer); 63 | 64 | /// 65 | /// Gets a textual representation of the element - e.g. to be used in Examine. 66 | /// 67 | /// The current grid context. 68 | /// An instance of representing the value of the element. 69 | public string GetSearchableText(GridContext context) { 70 | StringBuilder sb = new(); 71 | using TextWriter writer = new StringWriter(sb); 72 | WriteSearchableText(context, writer); 73 | return sb.ToString(); 74 | } 75 | 76 | #endregion 77 | 78 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridJsonObject.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Umbraco.GridData.Json.Converters; 4 | 5 | namespace Skybrud.Umbraco.GridData.Models; 6 | 7 | /// 8 | /// Class representing an object derived from an instance of . 9 | /// 10 | [JsonConverter(typeof(GridJsonConverter))] 11 | public class GridJsonObject { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// Gets a reference to the underlying instance of . 17 | /// 18 | [JsonIgnore] 19 | public JObject JObject { get; } 20 | 21 | #endregion 22 | 23 | #region Constructors 24 | 25 | /// The underlying instance of . 26 | public GridJsonObject(JObject json) { 27 | JObject = json; 28 | } 29 | 30 | #endregion 31 | 32 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Newtonsoft.Json.Linq; 6 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 7 | using Skybrud.Umbraco.GridData.Factories; 8 | 9 | namespace Skybrud.Umbraco.GridData.Models; 10 | 11 | /// 12 | /// Class representing a row in an Umbraco Grid. 13 | /// 14 | public class GridRow : GridElement { 15 | 16 | #region Properties 17 | 18 | /// 19 | /// Gets a reference to the parent . 20 | /// 21 | public GridSection Section { get; } 22 | 23 | /// 24 | /// Gets the unique ID of the row. 25 | /// 26 | public string Id { get; } 27 | 28 | /// 29 | /// Gets the label of the row. Use to check whether a label has been specified. 30 | /// 31 | public string? Label { get; } 32 | 33 | /// 34 | /// Gets whether a label has been specified for the definition of this row. 35 | /// 36 | public bool HasLabel => string.IsNullOrWhiteSpace(Label) == false; 37 | 38 | /// 39 | /// Gets the name of the row. 40 | /// 41 | public string Name { get; } 42 | 43 | /// 44 | /// Gets an array of all areas in the row. 45 | /// 46 | public IReadOnlyList Areas { get; } 47 | 48 | /// 49 | /// Gets a reference to the previous row. 50 | /// 51 | public GridRow? PreviousRow { get; internal set; } 52 | 53 | /// 54 | /// Gets a reference to the next row. 55 | /// 56 | public GridRow? NextRow { get; internal set; } 57 | 58 | /// 59 | /// Gets whether the row has any areas. 60 | /// 61 | public bool HasAreas => Areas.Count > 0; 62 | 63 | /// 64 | /// Gets the first area of the row. If the row doesn't contain any areas, this property will return null. 65 | /// 66 | public GridArea? FirstArea => Areas.FirstOrDefault(); 67 | 68 | /// 69 | /// Gets the last area of the row. If the row doesn't contain any areas, this property will return null. 70 | /// 71 | public GridArea? LastArea => Areas.LastOrDefault(); 72 | 73 | /// 74 | /// Gets whether at least one area or control within the row is valid. 75 | /// 76 | public override bool IsValid { 77 | get { return Areas.Any(x => x.IsValid); } 78 | } 79 | 80 | #endregion 81 | 82 | #region Constructors 83 | 84 | /// 85 | /// Initializes a new instance based on the specified object, and . 86 | /// 87 | /// An instance of representing the section. 88 | /// The parent section. 89 | /// The factory used for parsing subsequent parts of the grid. 90 | public GridRow(JObject json, GridSection section, IGridFactory factory) : base(json) { 91 | 92 | Section = section; 93 | Id = json.GetString("id")!; 94 | Label = json.GetString("label"); 95 | Name = json.GetString("name")!; 96 | 97 | Areas = json.GetArray("areas", x => factory.CreateGridArea(x, this)) ?? []; 98 | 99 | // Update "PreviousArea" and "NextArea" properties 100 | for (int i = 1; i < Areas.Count; i++) { 101 | Areas[i - 1].NextArea = Areas[i]; 102 | Areas[i].PreviousArea = Areas[i - 1]; 103 | } 104 | 105 | } 106 | 107 | #endregion 108 | 109 | #region Member methods 110 | 111 | /// 112 | /// Returns a list of all nested controls. 113 | /// 114 | public IReadOnlyList GetAllControls() { 115 | return ( 116 | from area in Areas 117 | from control in area.Controls 118 | select control 119 | ).ToArray(); 120 | } 121 | 122 | /// 123 | /// Returns a list of all nested controls with the specified editor . 124 | /// 125 | /// The editor alias of controls to be returned. 126 | public IReadOnlyList GetAllControls(string alias) { 127 | return GetAllControls(x => x.Editor.Alias == alias); 128 | } 129 | 130 | /// 131 | /// Returns a list of all nested controls matching the specified . 132 | /// 133 | /// The predicate (callback function) used for comparison. 134 | public IReadOnlyList GetAllControls(Func predicate) { 135 | return ( 136 | from area in Areas 137 | from control in area.Controls 138 | where predicate(control) 139 | select control 140 | ).ToArray(); 141 | } 142 | 143 | /// 144 | /// Writes a string representation of the row to . 145 | /// 146 | /// The current grid context. 147 | /// The writer. 148 | public override void WriteSearchableText(GridContext context, TextWriter writer) { 149 | foreach (GridArea area in Areas) area.WriteSearchableText(context, writer); 150 | } 151 | 152 | #endregion 153 | 154 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/GridSection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 7 | using Skybrud.Umbraco.GridData.Factories; 8 | 9 | namespace Skybrud.Umbraco.GridData.Models; 10 | 11 | /// 12 | /// Class representing a section in an Umbraco Grid. 13 | /// 14 | public class GridSection : GridJsonObject { 15 | 16 | #region Properties 17 | 18 | /// 19 | /// Gets the section name. 20 | /// 21 | public string Name { get; } 22 | 23 | /// 24 | /// Gets a reference to the parent . 25 | /// 26 | public GridDataModel Model { get; } 27 | 28 | /// 29 | /// Gets the overall column width of the section. 30 | /// 31 | public int Grid { get; } 32 | 33 | /// 34 | /// Gets an array of all rows in the sections. 35 | /// 36 | public IReadOnlyList Rows { get; } 37 | 38 | /// 39 | /// Gets whether the section has any rows. 40 | /// 41 | public bool HasRows => Rows.Count > 0; 42 | 43 | /// 44 | /// Gets the first row of the section. If the section doesn't contain any rows, this property will return null. 45 | /// 46 | public GridRow? FirstRow => Rows.FirstOrDefault(); 47 | 48 | /// 49 | /// Gets the last row of the section. If the section doesn't contain any rows, this property will return null. 50 | /// 51 | public GridRow? LastRow => Rows.LastOrDefault(); 52 | 53 | #endregion 54 | 55 | #region Constructors 56 | 57 | /// 58 | /// Initializes a new instance based on the specified object, model and . 59 | /// 60 | /// An instance of representing the section. 61 | /// The parent grid model. 62 | /// The factory used for parsing subsequent parts of the grid. 63 | public GridSection(JObject json, GridDataModel grid, IGridFactory factory) : base(json) { 64 | 65 | Model = grid; 66 | Grid = json.GetInt32("grid"); 67 | Name = grid.Name; 68 | Rows = json.GetArray("rows", x => factory.CreateGridRow(x, this)) ?? []; 69 | 70 | // Update "PreviousRow" and "NextRow" properties 71 | for (int i = 1; i < Rows.Count; i++) { 72 | Rows[i - 1].NextRow = Rows[i]; 73 | Rows[i].PreviousRow = Rows[i - 1]; 74 | } 75 | 76 | } 77 | 78 | #endregion 79 | 80 | #region Member methods 81 | 82 | /// 83 | /// Writes a string representation of the section to . 84 | /// 85 | /// The current grid context. 86 | /// The writer. 87 | public void WriteSearchableText(GridContext context, TextWriter writer) { 88 | foreach (GridRow row in Rows) row.WriteSearchableText(context, writer); 89 | } 90 | 91 | /// 92 | /// Returns a textual representation of the section - e.g. to be used in Examine. 93 | /// 94 | /// The current grid context. 95 | /// An instance of representing the value of the element. 96 | public string GetSearchableText(GridContext context) { 97 | StringBuilder sb = new(); 98 | using TextWriter writer = new StringWriter(sb); 99 | WriteSearchableText(context, writer); 100 | return sb.ToString(); 101 | } 102 | 103 | #endregion 104 | 105 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlEmbedValue.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Microsoft.AspNetCore.Html; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 7 | using Skybrud.Umbraco.GridData.Json.Converters; 8 | using Umbraco.Extensions; 9 | 10 | namespace Skybrud.Umbraco.GridData.Models.Values; 11 | 12 | /// 13 | /// Class representing the embed value of a control. 14 | /// 15 | [JsonConverter(typeof(GridControlValueStringConverter))] 16 | public class GridControlEmbedValue : GridControlValueBase { 17 | 18 | #region Properties 19 | 20 | /// 21 | /// Gets a string representing the value. 22 | /// 23 | public string Value { get; protected set; } 24 | 25 | /// 26 | /// Gets an instance of representing the text value. 27 | /// 28 | [JsonIgnore] 29 | public IHtmlContent HtmlValue { get; protected set; } 30 | 31 | /// 32 | /// Gets whether the value is valid. For an instance of , this means 33 | /// checking whether the property has a value. 34 | /// 35 | [JsonIgnore] 36 | public override bool IsValid => string.IsNullOrWhiteSpace(Value) == false; 37 | 38 | /// 39 | /// Gets the width of the embed. 40 | /// 41 | [JsonProperty("width")] 42 | public int Width { get; set; } 43 | 44 | /// 45 | /// Gets the height of the embed. 46 | /// 47 | [JsonProperty("height")] 48 | public int Height { get; set; } 49 | 50 | /// 51 | /// Gets the url of the embed. 52 | /// 53 | [JsonProperty("url")] 54 | public string Url { get; set; } 55 | 56 | /// 57 | /// Gets the info of the embed. 58 | /// 59 | [JsonProperty("info")] 60 | public string Info { get; set; } 61 | 62 | /// 63 | /// Gets the preview html of the embed. 64 | /// 65 | [JsonProperty("preview")] 66 | public IHtmlContent Preview => HtmlValue; 67 | 68 | #endregion 69 | 70 | #region Constructors 71 | 72 | /// 73 | /// Initializes a new instance based on the value of the specified grid . 74 | /// 75 | /// An instance of representing the parent grid control. 76 | public GridControlEmbedValue( GridControl control) : base(control) { 77 | 78 | Value = Json.GetString("preview")!; 79 | HtmlValue = new HtmlString(Value); 80 | 81 | Width = Json.GetInt32("width"); 82 | Height = Json.GetInt32("height"); 83 | Url = Json.GetString("url")!; 84 | Info = Json.GetString("info") ?? string.Empty; 85 | 86 | } 87 | 88 | #endregion 89 | 90 | #region Member methods 91 | 92 | /// 93 | /// Gets a string representing the raw value of the control. 94 | /// 95 | /// An instance of . 96 | public override string ToString() { 97 | return HtmlValue.ToHtmlString(); 98 | } 99 | 100 | /// 101 | /// Gets an HTML representing the value of the control. 102 | /// 103 | /// An instance of . 104 | public string ToHtmlString() { 105 | return Value; 106 | } 107 | 108 | /// 109 | /// Writes a string representation of this value to . 110 | /// 111 | /// The current grid context. 112 | /// The writer. 113 | public override void WriteSearchableText(GridContext context, TextWriter writer) { } 114 | 115 | /// 116 | /// Returns a string representation of this value. 117 | /// 118 | /// The current grid context. 119 | /// A string representation of this value. 120 | public override string GetSearchableText(GridContext context) { 121 | StringBuilder sb = new(); 122 | using TextWriter writer = new StringWriter(sb); 123 | WriteSearchableText(context, writer); 124 | return sb.ToString(); 125 | } 126 | 127 | #endregion 128 | 129 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlHtmlValue.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | using Microsoft.AspNetCore.Html; 4 | using Newtonsoft.Json; 5 | using Skybrud.Umbraco.GridData.Json.Converters; 6 | 7 | namespace Skybrud.Umbraco.GridData.Models.Values; 8 | 9 | /// 10 | /// Class representing the HTML value of a control. 11 | /// 12 | [JsonConverter(typeof(GridControlValueStringConverter))] 13 | public class GridControlHtmlValue : GridControlTextValue { 14 | 15 | #region Properties 16 | 17 | /// 18 | /// Gets an instance of representing the text value. 19 | /// 20 | [JsonIgnore] 21 | public IHtmlContent HtmlValue { get; } 22 | 23 | /// 24 | /// Gets whether the value is valid. For an instance of , this means 25 | /// checking whether the specified text is not an empty string. 26 | /// 27 | [JsonIgnore] 28 | public override bool IsValid => !string.IsNullOrWhiteSpace(Regex.Replace(Value, "<(p|/p)>", string.Empty)); 29 | 30 | #endregion 31 | 32 | #region Constructors 33 | 34 | /// 35 | /// Initializes a new instance based on the value of the specified grid . 36 | /// 37 | /// An instance of representing the parent grid control. 38 | public GridControlHtmlValue(GridControl control) : base(control) { 39 | HtmlValue = new HtmlString(Value); 40 | } 41 | 42 | #endregion 43 | 44 | #region Member methods 45 | 46 | /// 47 | /// Writes a string representation of this value to . 48 | /// 49 | /// The current grid context. 50 | /// The writer. 51 | public override void WriteSearchableText(GridContext context, TextWriter writer) { 52 | writer.WriteLine(Regex.Replace(Value, "<.*?>", " ")); 53 | } 54 | 55 | #endregion 56 | 57 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlMacroValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models.Values; 7 | 8 | /// 9 | /// Class representing the macro value of a control. 10 | /// 11 | public class GridControlMacroValue : GridControlValueBase { 12 | 13 | /// 14 | /// Gets the syntax of the macro. 15 | /// 16 | [JsonProperty("syntax")] 17 | public string Syntax { get; set; } 18 | 19 | /// 20 | /// Gets the alias of the macro. 21 | /// 22 | [JsonProperty("macroAlias")] 23 | public string MacroAlias { get; set; } 24 | 25 | /// 26 | /// Gets a dictionary containing the macro parameters. 27 | /// 28 | [JsonProperty("macroParamsDictionary")] 29 | public Dictionary Parameters { get; set; } 30 | 31 | /// 32 | /// Gets whether the value is valid. For an instance of , this means 33 | /// checking whether a macro alias has been specified. 34 | /// 35 | [JsonIgnore] 36 | public override bool IsValid => !string.IsNullOrWhiteSpace(MacroAlias); 37 | 38 | #region Constructors 39 | 40 | /// 41 | /// Initializes a new instance based on the value of the specified grid . 42 | /// 43 | /// An instance of representing the parent grid control. 44 | public GridControlMacroValue(GridControl control) : base(control) { 45 | Syntax = Json.GetString("syntax")!; 46 | MacroAlias = Json.GetString("macroAlias")!; 47 | Parameters = Json.GetObject("macroParamsDictionary")?.ToObject>() ?? new Dictionary(); 48 | } 49 | 50 | #endregion 51 | 52 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlMediaFocalPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models.Values; 7 | 8 | /// 9 | /// Class representing the focal point of a media. 10 | /// 11 | public class GridControlMediaFocalPoint : GridJsonObject { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// The horizontal (X-axis) coordinate of the focal point. 17 | /// 18 | [JsonProperty("left")] 19 | public float Left { get; } 20 | 21 | /// 22 | /// The vertical (Y-axis) coordinate of the focal point. 23 | /// 24 | [JsonProperty("top")] 25 | public float Top { get; } 26 | 27 | #endregion 28 | 29 | #region Constructors 30 | 31 | /// 32 | /// Initializes a new instance based on the specified . 33 | /// 34 | /// An instance of representing the focal point. 35 | protected GridControlMediaFocalPoint(JObject json) : base(json) { 36 | Left = json.GetFloat("left"); 37 | Top = json.GetFloat("top"); 38 | } 39 | 40 | #endregion 41 | 42 | #region Static methods 43 | 44 | /// 45 | /// Gets a focal point from the specified . 46 | /// 47 | /// The instance of to be parsed. 48 | [return: NotNullIfNotNull("json")] 49 | public static GridControlMediaFocalPoint? Parse(JObject? json) { 50 | return json == null ? null : new GridControlMediaFocalPoint(json); 51 | } 52 | 53 | #endregion 54 | 55 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlMediaValue.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Skybrud.Essentials.Json.Newtonsoft.Extensions; 5 | using Umbraco.Cms.Core.Models.PublishedContent; 6 | 7 | namespace Skybrud.Umbraco.GridData.Models.Values; 8 | 9 | /// 10 | /// Class representing the media value of a control. 11 | /// 12 | public class GridControlMediaValue : GridControlValueBase { 13 | 14 | #region Properties 15 | 16 | /// 17 | /// Gets the focal point with information on how the iamge should be cropped. 18 | /// 19 | [JsonProperty("focalPoint")] 20 | public GridControlMediaFocalPoint? FocalPoint { get; protected set; } 21 | 22 | /// 23 | /// Gets the ID of the image. 24 | /// 25 | [JsonProperty("id")] 26 | public int Id { get; protected set; } 27 | 28 | /// 29 | /// Gets the URL of the media. 30 | /// 31 | [JsonProperty("image")] 32 | public string? Image { get; protected set; } 33 | 34 | /// 35 | /// Gets the alt text of the media. 36 | /// 37 | [JsonProperty("altText", NullValueHandling = NullValueHandling.Ignore)] 38 | public string? AlternativeText { get; protected set; } 39 | 40 | /// 41 | /// Gets whether the property has a value. 42 | /// 43 | [JsonIgnore] 44 | [MemberNotNullWhen(true, nameof(AlternativeText))] 45 | public bool HasAlternativeText => !string.IsNullOrWhiteSpace(AlternativeText); 46 | 47 | /// 48 | /// Gets the caption of the media. 49 | /// 50 | [JsonProperty("caption", NullValueHandling = NullValueHandling.Ignore)] 51 | public string? Caption { get; protected set; } 52 | 53 | /// 54 | /// Gets whether the property has a value. 55 | /// 56 | [JsonIgnore] 57 | [MemberNotNullWhen(true, nameof(Caption))] 58 | public bool HasCaption => !string.IsNullOrWhiteSpace(Caption); 59 | 60 | /// 61 | /// Gets whether the value is valid. For an instance of , this means 62 | /// checking whether an image has been selected. The property will however not validate the image against the 63 | /// media cache. 64 | /// 65 | [JsonIgnore] 66 | public override bool IsValid => Id > 0; 67 | 68 | /// 69 | /// Gets a reference to the underlying representing the selected image. 70 | /// 71 | [JsonIgnore] 72 | public IPublishedContent? PublishedImage { get; internal set; } 73 | 74 | #endregion 75 | 76 | #region Constructors 77 | 78 | /// 79 | /// Initializes a new instance based on the value of the specified grid . 80 | /// 81 | /// An instance of representing the parent grid control. 82 | public GridControlMediaValue(GridControl control) : base(control) { 83 | FocalPoint = Json.GetObject("focalPoint", GridControlMediaFocalPoint.Parse); 84 | Id = Json.GetInt32("id"); 85 | Image = Json.GetString("image"); 86 | AlternativeText = Json.GetString("altText"); 87 | Caption = Json.GetString("caption"); 88 | } 89 | 90 | #endregion 91 | 92 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlRichTextValue.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Skybrud.Umbraco.GridData.Json.Converters; 3 | 4 | namespace Skybrud.Umbraco.GridData.Models.Values; 5 | 6 | /// 7 | /// Class representing the rich text value of a control. 8 | /// 9 | [JsonConverter(typeof(GridControlValueStringConverter))] 10 | public class GridControlRichTextValue : GridControlHtmlValue { 11 | 12 | /// 13 | /// Initializes a new instance based on the value of the specified grid . 14 | /// 15 | /// An instance of representing the parent grid control. 16 | public GridControlRichTextValue(GridControl control) : base(control) { } 17 | 18 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlTextValue.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Skybrud.Umbraco.GridData.Json.Converters; 6 | 7 | namespace Skybrud.Umbraco.GridData.Models.Values; 8 | 9 | /// 10 | /// Class representing the text value of a control. 11 | /// 12 | [JsonConverter(typeof(GridControlValueStringConverter))] 13 | public class GridControlTextValue : GridControlValueBase { 14 | 15 | #region Properties 16 | 17 | /// 18 | /// Gets a string representing the value. 19 | /// 20 | public string Value { get; protected set; } 21 | 22 | /// 23 | /// Gets whether the value is valid. For an instance of , this means 24 | /// checking whether the specified text is not an empty string (using ). 25 | /// 26 | public override bool IsValid => !string.IsNullOrWhiteSpace(Value); 27 | 28 | #endregion 29 | 30 | #region Constructors 31 | 32 | /// 33 | /// Initializes a new instance based on the value of the specified grid . 34 | /// 35 | /// An instance of representing the parent grid control. 36 | public GridControlTextValue(GridControl control) : base(control) { 37 | Value = Json.Value() ?? string.Empty; 38 | } 39 | 40 | #endregion 41 | 42 | #region Member methods 43 | 44 | /// 45 | /// Writes a string representation of this value to . 46 | /// 47 | /// The current grid context. 48 | /// The writer. 49 | public override void WriteSearchableText(GridContext context, TextWriter writer) { 50 | writer.WriteLine(Value); 51 | } 52 | 53 | /// 54 | /// Returns a string representation of this value. 55 | /// 56 | /// The current grid context. 57 | /// A string representation of this value. 58 | public override string GetSearchableText(GridContext context) { 59 | StringBuilder sb = new(); 60 | using TextWriter writer = new StringWriter(sb); 61 | WriteSearchableText(context, writer); 62 | return sb.ToString(); 63 | } 64 | 65 | /// 66 | /// Gets a string representing the raw value of the control. 67 | /// 68 | /// An instance of . 69 | public override string ToString() { 70 | return Value; 71 | } 72 | 73 | #endregion 74 | 75 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/GridControlValueBase.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace Skybrud.Umbraco.GridData.Models.Values; 7 | 8 | /// 9 | /// Abstract class with a basic implementation of the interface. 10 | /// 11 | public abstract class GridControlValueBase : IGridControlValue { 12 | 13 | #region Properties 14 | 15 | /// 16 | /// Gets a reference to the underlying instance of the value was parsed from. 17 | /// 18 | [JsonIgnore] 19 | public JToken Json { get; } 20 | 21 | /// 22 | /// Gets a reference to the parent . 23 | /// 24 | [JsonIgnore] 25 | public GridControl Control { get; } 26 | 27 | /// 28 | /// Gets whether the control is valid (e.g. whether it has a value). 29 | /// 30 | [JsonIgnore] 31 | public virtual bool IsValid => true; 32 | 33 | #endregion 34 | 35 | #region Constructors 36 | 37 | /// 38 | /// Initializes a new instance based on the specified object. 39 | /// 40 | /// An instance of representing the value of the control. 41 | /// An instance of representing the parent grid control. 42 | protected GridControlValueBase(JToken json, GridControl control) { 43 | Json = json; 44 | Control = control; 45 | } 46 | 47 | /// 48 | /// Initializes a new instance based on the specified object. 49 | /// 50 | /// An instance of representing the parent grid control. 51 | protected GridControlValueBase(GridControl control) { 52 | Json = control.JObject.GetValue("value")!; 53 | Control = control; 54 | } 55 | 56 | #endregion 57 | 58 | #region Member methods 59 | 60 | /// 61 | /// Writes a string representation of this value to . 62 | /// 63 | /// The current grid context. 64 | /// The writer. 65 | public virtual void WriteSearchableText(GridContext context, TextWriter writer) { } 66 | 67 | /// 68 | /// Returns a string representation of this value. 69 | /// 70 | /// The current grid context. 71 | /// A string representation of this value. 72 | public virtual string GetSearchableText(GridContext context) { 73 | StringBuilder sb = new(); 74 | using TextWriter writer = new StringWriter(sb); 75 | WriteSearchableText(context, writer); 76 | return sb.ToString(); 77 | } 78 | 79 | #endregion 80 | 81 | } 82 | 83 | /// 84 | /// Abstract class with a basic implementation of the interface. 85 | /// 86 | public abstract class GridControlValueBase : GridControlValueBase where TJson : JToken { 87 | 88 | /// 89 | /// Gets a reference to the underlying instance of the value was parsed from. 90 | /// 91 | [JsonIgnore] 92 | public new TJson Json => (TJson) base.Json; 93 | 94 | /// 95 | /// Initializes a new instance based on the specified object. 96 | /// 97 | /// An instance of representing the value of the control. 98 | /// An instance of representing the parent grid control. 99 | protected GridControlValueBase(TJson json, GridControl control) : base(json, control) { } 100 | 101 | /// 102 | /// Initializes a new instance based on the specified object. 103 | /// 104 | /// An instance of representing the parent grid control. 105 | protected GridControlValueBase(GridControl control) : base(control) { } 106 | 107 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Models/Values/IGridControlValue.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Newtonsoft.Json; 3 | 4 | namespace Skybrud.Umbraco.GridData.Models.Values; 5 | 6 | /// 7 | /// Interface describing a grid control value. 8 | /// 9 | public interface IGridControlValue { 10 | 11 | /// 12 | /// Gets a reference to the parent . 13 | /// 14 | [JsonIgnore] 15 | GridControl Control { get; } 16 | 17 | /// 18 | /// Gets whether the value of the control is valid. 19 | /// 20 | [JsonIgnore] 21 | bool IsValid { get; } 22 | 23 | /// 24 | /// Writes a textual representation of this control value to the specified . 25 | /// 26 | /// The current . 27 | /// The writer to write to. 28 | void WriteSearchableText(GridContext context, TextWriter writer); 29 | 30 | /// 31 | /// Gets the value of the control as a searchable text - e.g. to be used in Examine. 32 | /// 33 | /// The current grid context. 34 | /// An instance of with the value as a searchable text. 35 | string GetSearchableText(GridContext context); 36 | 37 | } -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/Skybrud.Umbraco.GridData.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12.0 7 | net8.0 8 | enable 9 | 10 | 11 | 12 | 13.0.0 13 | build$([System.DateTime]::UtcNow.ToString(`yyyyMMddHHmm`)) 14 | Limbo 15 | Anders Bjerner, René Pjengaard 16 | Copyright © $([System.DateTime]::UtcNow.ToString(`yyyy`)) 17 | Skybrud Grid Data 18 | Strongly typed models for the grid in Umbraco. 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Skybrud.Umbraco.GridData 32 | Skybrud, Limbo, Umbraco, Grid, JSON, Umbraco-Marketplace 33 | MIT 34 | https://packages.skybrud.dk/skybrud.umbraco.griddata/ 35 | Limbo.png 36 | git 37 | https://github.com/skybrud/Skybrud.Umbraco.GridData/ 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Skybrud.Umbraco.GridData/ValueConverters/GridValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json.Linq; 3 | using Skybrud.Essentials.Json.Newtonsoft; 4 | using Skybrud.Umbraco.GridData.Factories; 5 | using Skybrud.Umbraco.GridData.Models; 6 | using Umbraco.Cms.Core.Models.PublishedContent; 7 | using Umbraco.Cms.Core.PropertyEditors; 8 | 9 | namespace Skybrud.Umbraco.GridData.ValueConverters; 10 | 11 | /// 12 | /// Property value converter for the Umbraco Grid. 13 | /// 14 | class GridValueConverter : PropertyValueConverterBase { 15 | 16 | private readonly IGridFactory _factory; 17 | 18 | public GridValueConverter(IGridFactory factory) { 19 | _factory = factory; 20 | } 21 | 22 | /// 23 | /// Gets a value indicating whether the converter supports a property type. 24 | /// 25 | /// The property type. 26 | /// A value indicating whether the converter supports a property type. 27 | public override bool IsConverter(IPublishedPropertyType propertyType) { 28 | return propertyType.EditorAlias == "Umbraco.Grid"; 29 | } 30 | 31 | public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { 32 | 33 | if (source is string { Length: > 0 } json && json[0] == '{') { 34 | return JsonUtils.ParseJsonObject(json); 35 | } 36 | 37 | return null; 38 | 39 | } 40 | 41 | public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { 42 | return inter is JObject json ? _factory.CreateGridModel(owner, propertyType, json, preview) : null; 43 | } 44 | 45 | public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { 46 | return PropertyCacheLevel.Snapshot; 47 | } 48 | 49 | /// 50 | /// Gets the type of values returned by the converter. 51 | /// 52 | /// The property type. 53 | /// The CLR type of values returned by the converter. 54 | public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { 55 | return typeof(GridDataModel); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/build/Limbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skybrud/Skybrud.Umbraco.GridData/24e85ae1fc414cc56ea5550da9e1073eeab0e6ac/src/build/Limbo.png --------------------------------------------------------------------------------