Compare commits

...

155 Commits

Author SHA1 Message Date
Kevin Giszewski bdd3c2088c Bump version number to v1.3.0 2014-06-30 10:48:07 -04:00
Kevin Giszewski 80b41e4a5f Fix #168 2014-06-30 10:45:48 -04:00
Kevin Giszewski d60e7ba2f4 Merge branch 'master' into pr/166 2014-06-30 10:39:59 -04:00
Kevin Giszewski 4524320db2 Merge branch 'master' into pr/166 2014-06-30 09:47:10 -04:00
kjac 90ca77c9a9 Add localization to new config item 2014-06-28 12:51:36 +02:00
kjac 8332a841eb Implement #165 - enable/disable fieldsets 2014-06-28 12:42:36 +02:00
kjac d6a13b2335 Merge remote-tracking branch 'upstream/master' 2014-06-28 11:25:59 +02:00
cankoluman 987e5cf470 Issue #AR-8_line_endings: fix build 2014-06-27 14:27:48 +01:00
cankoluman f2e5b83795 Issue #AR-8_line_endings: fix line endings so we match the upstream 2014-06-27 14:16:54 +01:00
cankoluman 066e807847 Merge remote-tracking branch 'origin/dev/AR-6_legacy_nesting' into dev/AR-8_line_endings
Conflicts:
	app/Umbraco/Archetype.Tests/Serialization/UseCases/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/Complex/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/JsonTestModels.cs
	app/Umbraco/Umbraco.Archetype/Extensions/Extensions.cs
	app/Umbraco/Umbraco.Archetype/Properties/VersionInfo.cs
	app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsArchetypeAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsFieldsetAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/SerializationExtensions.cs
	config/meta.json
2014-06-27 12:47:52 +01:00
cankoluman d8378ed6bb Merge remote-tracking branch 'origin/dev/AR-1_serialization' into dev/AR-8_line_endings
Conflicts:
	app/Umbraco/Archetype.Tests/Serialization/UseCases/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/Complex/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/JsonTestModels.cs
	app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj
	app/Umbraco/Umbraco.Archetype/Extensions/Extensions.cs
	app/Umbraco/Umbraco.Archetype/Properties/VersionInfo.cs
	app/Umbraco/Umbraco.Archetype/PropertyConverters/ArchetypeValueConverter.cs
	app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsArchetypeAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsFieldsetAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/SerializationExtensions.cs
2014-06-27 12:42:40 +01:00
kjac 19ee2b890c Merge remote-tracking branch 'upstream/master' 2014-06-27 06:33:15 +02:00
Kevin Giszewski c953f12412 Merge branch 'pr/157' into pr/164 2014-06-26 15:58:31 -04:00
kjac 601f2c0d81 Fix issue #162 + improve nested validation highlighting
The fieldset.isValid property isn't necessarily initialized when the
view looks for it to determine fieldset validity. I've introduced a
validation method that counters this problem.
The validation method also ensures that any validation errors in nested
Archetypes are highlighted on the parent/root Archetype
2014-06-26 14:58:55 +02:00
kjac 283dd8af76 Merge remote-tracking branch 'upstream/master' 2014-06-26 14:23:40 +02:00
Tom Fulton c83942dc5c Update field labels to match new property alias scheme
Only for the first level, since focusing an Archetype isn't really a thing
2014-06-25 22:03:22 -04:00
Tom Fulton a0ff42a84f Merge branch 'master' into fix/unique-aliases
Conflicts:
	app/views/archetype.default.html
2014-06-25 21:49:24 -04:00
Tom Fulton ed97d586b4 Make property aliases even more unique
archetype-property-<umbracoPropertyAlias>-<fieldsetIndex>-<archetypePropertyAlias>-<archetypePropertyIndex>-<and so on...>
2014-06-25 21:46:46 -04:00
Kevin Giszewski 1bd09799b4 Fix for #160 2014-06-25 18:42:41 +02:00
Kevin Giszewski 0b77ddeef1 Fix 'hide property label' 2014-06-25 18:42:40 +02:00
kjac 2b2d0b4b0d Fix code formatting 2014-06-25 18:42:39 +02:00
kjac dc992293e1 Add default value overload to GetValue()
Fix issue #142
2014-06-25 18:42:39 +02:00
kjac 0bc1451a5c Fixing the unit tests
The call to DisposableTimer.DebugDuration() in
ArchetypeValueConverter.ConvertDataToSource() expects the propertyType
parameter to be not null, but the implementation of
ArchetypeValueConverter.ConvertDataToSource() allows the propertyType
parameter to be null. This breaks some of the unit tests.
I've added a null check in the call to DisposableTimer.DebugDuration()
to counter this issue.
2014-06-25 18:42:38 +02:00
Kevin Giszewski f052ed70a9 Merge pull request #161 from imulus/feature/fix-customviews
Fix for #160
2014-06-25 10:29:22 -04:00
Kevin Giszewski 0dedd7fb2d Fix for #160 2014-06-25 10:28:30 -04:00
Kevin Giszewski cb6455c34a Merge branch 'master' into develop 2014-06-25 08:35:07 -04:00
Kevin Giszewski f99ef697db Fix 'hide property label' 2014-06-25 08:34:54 -04:00
Tom Fulton aac015dc0b Refactor property aliases for uniqueness
Old: archetype-property-<umbracoPropertyAlias>-<fieldsetIndex>-<propertyIndex>
New: archetype-property-<umbracoPropertyAlias>-<archetypePropertyAlias>-<fieldsetIndex>

Basically, this adds the archetypePropertyAlias into the mix, to fix a case where you might have the same Archetype nested on a host Archetype twice  (ie box 1 links, box 2 links within the same Archetype - there was no uniqueness)

It also adds in the Property Alias in place of the Property "Index" (unintended side effect, but seems cool :))
2014-06-23 20:58:42 -04:00
Kevin Giszewski f7ef096ba6 Merge branch 'master' into develop 2014-06-20 08:41:49 -04:00
Kevin Giszewski a17e5b5372 Merge branch 'master' into develop 2014-06-20 08:27:04 -04:00
kjac 16a73e0c82 Fix code formatting 2014-06-20 11:06:33 +02:00
cankoluman a56864b731 Issue #AR-6_legacy_nesting: update version counter to match VersionInfo.cs 2014-06-20 09:59:15 +01:00
kjac 2f195dd588 Add default value overload to GetValue()
Fix issue #142
2014-06-20 10:58:21 +02:00
kjac 35309b863c Fixing the unit tests
The call to DisposableTimer.DebugDuration() in
ArchetypeValueConverter.ConvertDataToSource() expects the propertyType
parameter to be not null, but the implementation of
ArchetypeValueConverter.ConvertDataToSource() allows the propertyType
parameter to be null. This breaks some of the unit tests.
I've added a null check in the call to DisposableTimer.DebugDuration()
to counter this issue.
2014-06-20 10:56:29 +02:00
cankoluman da9085c244 Merge remote-tracking branch 'upstream/develop' into dev/AR-6_legacy_nesting
Conflicts:
	app/Umbraco/Archetype.Tests/Serialization/UseCases/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/Complex/ArchetypeJsonConverterTest.cs
	app/Umbraco/Archetype.Tests/Serialization/UseCases/JsonTestModels.cs
	app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj
	app/Umbraco/Umbraco.Archetype/Extensions/Extensions.cs
	app/Umbraco/Umbraco.Archetype/Properties/VersionInfo.cs
	app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsArchetypeAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/AsFieldsetAttribute.cs
	app/Umbraco/Umbraco.Archetype/Serialization/SerializationExtensions.cs
2014-06-20 09:35:13 +01:00
cankoluman 1d8703e211 Issue #AR-6_legacy_nesting: fix issue where legacy nested model deserialization failed after the empty fieldset fix,
- add legacy deserialization test
2014-06-18 11:04:25 +01:00
kjac 0ebf07d0f4 Merge remote-tracking branch 'upstream/master' 2014-06-18 06:25:55 +02:00
Kevin Giszewski fdcd6cc3bf Bump Version to 1.2.1 2014-06-17 16:23:48 -04:00
Kevin Giszewski 59179d0c81 Fix two-way binding 2014-06-17 16:23:38 -04:00
leekelleher 16f8d9fb2a Tidied up namespace references + various whitespace.
(I couldn't help myself; OCD! )
2014-06-17 18:45:18 +01:00
leekelleher cfffa89138 Applied singleton pattern for ArchetypeHelper. 2014-06-17 18:43:58 +01:00
kjac 2b4cc2c429 Merge remote-tracking branch 'upstream/master' 2014-06-17 18:39:34 +02:00
Kevin Giszewski 9c82018903 Merge pull request #147 from leekelleher/feature/customViewPath
Added custom view path to prevalue POCO model
2014-06-17 12:16:35 -04:00
leekelleher 386a1a58fc Set the assembly references not to "copy local"
(This seems to revert when NuGet updates references)
2014-06-17 17:00:26 +01:00
leekelleher 9aaa490eb0 Added the customViewPath property to the POCO model 2014-06-17 16:59:38 +01:00
Kevin Giszewski ec446b6800 Merge branch 'pr/137' into develop 2014-06-17 07:05:06 -04:00
cankoluman 67f21ee4d8 Merge branch 'dev/AR-1_serialization' into stable 2014-06-17 11:17:23 +01:00
cankoluman 0573d99218 Merge branch 'stable' into dev/AR-1_serialization 2014-06-17 11:16:28 +01:00
cankoluman a520d86ec4 Issue #AR-5_empty_json: Handle the case where the archetype json is empty:
{""fieldsets"": []}

Currently this throws an exception. Handle by setting returned model to null.
2014-06-17 11:13:25 +01:00
Kevin Giszewski 3c9b7692f1 Bump the version number 2014-06-16 11:54:49 -04:00
Kevin Giszewski 51223d4ac7 Merge branch 'develop' of github.com:imulus/Archetype into develop 2014-06-16 11:35:57 -04:00
Kevin Giszewski 1d5f91964f Finish Custom Views 2014-06-16 11:35:44 -04:00
Kevin Giszewski 0accac26ee WIP for custom views 2014-06-16 11:06:59 -04:00
kjac 0048330863 Merge branch 'master' into issue131_optimized 2014-06-16 16:43:07 +02:00
kjac d07c16160a Merge remote-tracking branch 'upstream/master' 2014-06-16 16:42:25 +02:00
Kevin Giszewski 6527e66ff3 Add extensions to auto map an ArchetypeModel to a partials of named as alias 2014-06-16 10:00:17 -04:00
Kevin Giszewski 47e71067a1 Merge branch 'pr/137' into develop 2014-06-16 09:11:49 -04:00
Kevin Giszewski 969cc45e9b Merge branch 'pr/107' into develop 2014-06-16 09:10:22 -04:00
Kevin Giszewski 9ff5bfc907 Merge pull request #145 from imulus/master
Sync up dev
2014-06-14 18:27:17 -04:00
Kevin Giszewski 7cdcb940bd Merge pull request #144 from imulus/develop
Develop
2014-06-14 18:25:35 -04:00
Kevin Giszewski 75e0ac5d6d Merge pull request #139 from imulus/fix/133
Send ContentType when using ValueConverters
2014-06-14 18:22:37 -04:00
cankoluman 310771303c Merge branch 'dev/AR-1_serialization' into stable 2014-06-02 14:25:12 +01:00
Kevin Giszewski ba6921fdb9 Update README.md 2014-05-29 17:50:43 -04:00
kjac aafeebfc62 Merge remote-tracking branch 'upstream/master' 2014-05-29 11:16:01 +02:00
Tom Fulton 8624617ad4 Merge branch 'master' into fix/133 2014-05-27 15:03:02 -06:00
Tom Fulton e46e5c279f Merge pull request #136 from leekelleher/feature/issues/134
Adds null reference checks for `GetValue<T>`.
2014-05-27 15:01:57 -06:00
Tom Fulton a9dc79e4ff Ensure new HostContentType isn't persisted 2014-05-27 15:01:21 -06:00
Tom Fulton 485c6cb6a1 When using ValueConverters, pass along the Content Type - fixes #133
* Add new property HostContentType to make Properties aware of their content type
* Adjust Dummy PropertyType to include the Content Type
* Pass in Content Type from ValueConverter to Deserialization helper through new optional parameter
* Populate the property's HostContentType via RetrieveAdditionalProperties
* Make sure HostContentType isn't persisted

Notes: In some cases, the Umbraco Core looks for pt.ContentType.Alias, which didn't exist previously in our Dummy PropertyType.  This would also potentially cause an error if any PVC's do the same.

To fix we now keep track of the Content Type that the Archetype exists on (determined in the ArchetypeValueConverter), and pass this along to our Dummy PropertyType (and thus to PVCs)

Note we made the hostContentType an optional parameter as there's some cases where we won't know the content type (ie ConvertDbToEditor).  This (shouldn't?) be an issue as these cases should never have a reason to call GetValue.
2014-05-27 14:31:23 -06:00
Kevin Giszewski 8e7eda9276 Merge branch 'master' into develop 2014-05-23 16:24:10 -04:00
Kevin Giszewski 96d70bc1a9 Merge branch 'develop' into pr/137 2014-05-23 10:07:34 -04:00
Kevin Giszewski 264208dcc7 Update namespaces 2014-05-22 15:01:55 -04:00
Kevin Giszewski 077ab44dc8 Merge branch 'develop' into pr/107
Conflicts:
	app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj
	app/Umbraco/Umbraco.Archetype/Properties/VersionInfo.cs
2014-05-22 14:19:03 -04:00
Kevin Giszewski 7499395807 Fix WS 2014-05-22 14:17:46 -04:00
Kevin Giszewski d983b75be3 Merge branch 'pr/136' into develop 2014-05-22 14:12:28 -04:00
kjac 381875bc14 Merge branch 'master' into issue131_optimized 2014-05-22 19:40:52 +02:00
kjac 3944da57b1 Fix the validation display
Rewrite the changes from commit 4785c85 to work with validation.
2014-05-22 19:16:26 +02:00
leekelleher 0cbbd1c93e Adds null reference checks for GetValue<T>.
Fixes #134
2014-05-22 14:29:38 +01:00
kjac 1bfab0f8a3 Optimize validation routine
Only validate the property being edited, not the entire fieldset the
property belongs to.
2014-05-21 22:49:15 +02:00
cankoluman b6d2b7e1dd Merge branch 'dev/AR-4_delinter_fixes' into dev/AR-1_serialization 2014-05-21 14:23:26 +01:00
cankoluman 1179147041 Issue #AR-4-delinter_fixes: simplify replace 2014-05-21 14:22:11 +01:00
cankoluman 0cfcaa3364 Issue #AR-4-delinter_fixes: change heuristic for space removal 2014-05-21 14:15:54 +01:00
cankoluman 14777a98dc Issue #AR-4-delinter_fixes: removed buggy escaped json tests 2014-05-20 16:22:47 +01:00
cankoluman a306424162 Issue #AR-4-delinter_fixes: fix issue where escaped new lines were being removed inside value quotes 2014-05-20 15:05:44 +01:00
cankoluman d124622089 Issue #AR-4-delinter_fixes: refactor delinter 2014-05-20 13:07:18 +01:00
cankoluman d9a25d1bb3 Issue #AR-4-delinter_fixes: fix all regexes except white space 2014-05-20 12:18:48 +01:00
cankoluman cda17ac754 Issue #AR-4-delinter_fixes: update remove new line 2014-05-20 10:39:09 +01:00
cankoluman d5329935aa Issue #AR-4-delinter_fixes: fixed all test failures due to jToken navigation errors.
To Do: fix erros due to incorrect space removal
2014-05-20 09:32:01 +01:00
kjac 537abea717 Merge remote-tracking branch 'upstream/master' 2014-05-20 07:13:54 +02:00
kjac c8d9b04a57 Rewrite validation
Rewrite validation to handle
1. Validation on page load
2. Validation on nested Archetypes
3. Validation of numeric property editors (check for NaN)
2014-05-19 19:49:50 +02:00
cankoluman 1913f8bf14 Issue #AR-4-delinter_fixes: create single entry point for deserialization - 15 tests failing now 2014-05-19 16:25:20 +01:00
cankoluman 6f2eaa9859 Issue #AR-4-delinter_fixes: trying to fix delinter regex 2014-05-19 16:08:00 +01:00
cankoluman 31074ec9db Issue #AR-4-delinter_fixes: trying to sort out issue with \r\n being replaced inside values 2014-05-16 17:19:05 +01:00
Kevin Giszewski 32d0d199a3 Add/move dynamic classes for targeting of custom behavior 2014-05-16 13:44:25 +02:00
kjac 715776771a Fix deserialization of RegEx enabled properties
Fix type mismatch for RegEx (introduced in 6e50301 - my bad, sorry)
2014-05-16 13:44:24 +02:00
Kevin Giszewski 4785c85b84 Add/move dynamic classes for targeting of custom behavior 2014-05-15 17:24:34 -04:00
Kevin Giszewski 712ca3cd76 Add/move dynamic classes for targeting of custom behavior 2014-05-15 17:24:14 -04:00
cankoluman 512ecbdb05 Issue #AR-1_serialization: fixes after merge of upstream master 2014-05-15 17:02:06 +01:00
cankoluman 9a97e9102e Merge branch 'master' into dev/AR-1_serialization
Conflicts:
	app/Umbraco/Archetype.Tests/Archetype.Tests.csproj
	app/Umbraco/Umbraco.Archetype.sln
	app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj
	app/Umbraco/Umbraco.Archetype/Extensions/Extensions.cs
	app/Umbraco/Umbraco.Archetype/PropertyConverters/ArchetypeValueConverter.cs
2014-05-15 16:48:08 +01:00
cankoluman e9b2f4aeee Merge remote-tracking branch 'upstream/master' 2014-05-15 16:43:25 +01:00
cankoluman bdb7f247c3 Issue #AR-1_serialization: add delinter to SerializeForPersistence() 2014-05-15 16:23:48 +01:00
cankoluman 08c09a8820 Issue #AR-1_serialization: moved Delinter so serialization namespace,
- fix small issue with space detection in fieldset delinter
2014-05-15 16:13:29 +01:00
cankoluman ac84c35f67 Issue #AR-1_serialization: fix issue where property alias was incorrectly set from type alias 2014-05-15 15:06:41 +01:00
cankoluman bff8bcc562 Issue #AR-1_serialization: fix overflow issue affecting deserialization in recursively nested models 2014-05-15 14:55:12 +01:00
cankoluman 8fc209abc1 Issue #AR-1_serialization: move delinter tests into serialization tests 2014-05-15 12:42:15 +01:00
cankoluman a6a961d52a Issue #AR-1_serialization: added additional unescape model tests 2014-05-15 12:38:11 +01:00
cankoluman b289892727 Issue #AR-1_serialization: initial commit of delinter 2014-05-15 12:25:44 +01:00
Kevin Giszewski 43a7202194 Merge pull request #128 from kjac/RegEx-model-fix
Fix deserialization of RegEx enabled properties
2014-05-14 10:01:37 -04:00
cankoluman 4051734313 Issue #AR-1_serialization: got json escape test to work, added json unescape extension and tests,
To Do: migrate  test code from ArcheTypePropertyEditor if possible.
2014-05-14 12:28:37 +01:00
kjac 9a21ab0ef4 Fix deserialization of RegEx enabled properties
Fix type mismatch for RegEx (introduced in 6e50301 - my bad, sorry)
2014-05-13 22:11:53 +02:00
Kevin Giszewski 46613bbc01 Fix bug with Add button 2014-05-13 12:03:50 -04:00
cankoluman ae242feed6 Issue #AR-1_serialization: add test to try to create escaped json - this still produces unescaped json.
However,
		    internal virtual PropertyEditor GetPropertyEditor(IDataTypeDefinition dtd)
		    {
		        return dtd.Id == 0
                    ? new ArchetypePropertyEditor()
                    : PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
		    }

would produce nested json. But this spec looks incorrect.
2014-05-13 15:44:33 +01:00
cankoluman f1df0f2ad9 Issue #AR-1_serialization: add tests comparing models generated object serializer and archetype.SerializeForPersistence() pathwways.
Note: SerializeForPersistence() does not generate escaped json when used on its own.
All tests passing.
2014-05-13 10:20:12 +01:00
cankoluman 475fe7c81f Issue #AR-1_serialization: fix error in IsConverter when PropertyEditorAlias is null,
- relax property token navigation so that if a nested token is not found, we return null.
2014-05-12 17:15:20 +01:00
cankoluman 9b1b3d1793 Merge branch 'dev/AR-3_ienumerable_support' into dev/AR-1_serialization 2014-05-09 16:58:23 +01:00
cankoluman f1f8b7db22 Merge branch 'dev/AR-1_serialization' into dev/AR-3_ienumerable_support 2014-05-09 16:56:49 +01:00
cankoluman 475bca3c05 Merge branch 'master' into dev/AR-1_serialization 2014-05-09 16:53:51 +01:00
cankoluman 77c501f2dd Merge remote-tracking branch 'upstream/master' 2014-05-09 16:52:55 +01:00
cankoluman 5db9bb972b Issue #AR-3_IEnumerable_support: separate use case tests 2014-05-09 16:51:33 +01:00
cankoluman f931e4a31f Issue #AR-3_IEnumerable_support: fix incorrect hard-coded json, some changes to tests. 2014-05-09 16:42:30 +01:00
cankoluman bc246b947f Issue #AR-3_IEnumerable_support: add nested model regression tests 2014-05-09 15:42:58 +01:00
cankoluman ec642fa69f Issue #AR-3_IEnumerable_support: fix issue with property extraction 2014-05-09 15:27:19 +01:00
cankoluman 1ba1fa2d83 Issue #AR-3_IEnumerable_support: added compound model regression tests 2014-05-09 14:42:31 +01:00
cankoluman 7c02539751 Issue #AR-3_IEnumerable_support: re-factor regression tests 2014-05-09 13:05:04 +01:00
cankoluman bc6fff6f73 Issue #AR-3_IEnumerable_support: regression testing - fixed issue where the default fieldset name was not assigned properly during dynamic model generation 2014-05-09 11:14:39 +01:00
cankoluman 911a74e43e Issue #AR-3_IEnumerable_support: regression testing - fixes to [AsFieldset] handling,
- use ExpandoObject for all dynamically generated models
- add additional JToken parsers
- To Do: deal with multiple item fieldset
2014-05-08 16:56:51 +01:00
cankoluman 934df00281 Issue #AR-3_IEnumerable_support: start on regression tests. 2014-05-07 17:03:08 +01:00
cankoluman df8d3a8888 Issue #AR-3_IEnumerable_support: clean-up 2014-05-07 15:37:57 +01:00
cankoluman e341732f83 Issue #AR-3_IEnumerable_support: move complex model tests to sub-folder 2014-05-07 14:24:35 +01:00
cankoluman 6c5a5c8393 Issue #AR-3_IEnumerable_support: add missing tests 2014-05-07 14:17:14 +01:00
cankoluman 5e23d845d5 Issue #AR-3_IEnumerable_support: apply naming convention to unit tests 2014-05-07 14:11:26 +01:00
cankoluman 11befe727c Issue #AR-3_IEnumerable_support: all tests passing - but branching logic is difficult. 2014-05-07 13:01:07 +01:00
cankoluman ed9ba7d41f Issue #AR-3_IEnumerable_support: add IEnumerable deserialization - tests pass.
However, some regression errors in compex model use case
2014-05-06 16:54:08 +01:00
cankoluman 1ae967e002 Issue #AR-3_IEnumerable_support: add IEnumerable serialization support - Captions serialization tests passing.
- needs cleaning up.
2014-05-06 15:01:50 +01:00
cankoluman 72303c1c83 Issue #AR-3_IEnumerable_support: fix json for simple list model - simple list model passes 2014-05-06 12:13:37 +01:00
cankoluman bc6580995a Issue #AR-3_IEnumerable_support: re-factor tests, add Enumerable tests - all failing as baseline. 2014-05-06 11:39:10 +01:00
cankoluman 52ff09f089 Merge remote-tracking branch 'origin/dev/AR-2_property_value_converters' into dev/AR-1_serialization 2014-05-01 16:51:16 +01:00
cankoluman 34e65ba57e Issue #AR-1_property_value_converters: switched to using Archetype's native GetValue<T> when getting property values,
- added umbracoSettings.config to the test project: conversion of decimal numbers, e.g. '12345.34' triggers
'NormalizeNumberDecimalSeparator' which in turn requires via 'DefaultShortStringHelper' that the umbracoSettings.config file is loaded. We added this to make sure tests deserializing decimal numbers pass.
2014-05-01 16:13:19 +01:00
cankoluman 87da7e1243 Issue #AR-1_serialization: fix tests that were failing due to changes in line-end encodings. 2014-05-01 14:24:26 +01:00
cankoluman ed86255cf0 Merge branch 'master' into dev/AR-1_serialization
Conflicts:
	app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj
	app/Umbraco/Umbraco.Archetype/Properties/VersionInfo.cs
2014-05-01 14:17:39 +01:00
cankoluman 518f68951d Merge remote-tracking branch 'upstream/master' 2014-05-01 14:12:17 +01:00
cankoluman a6b1abaecc Issue #AR-1_serialization: removed ArchetypeJsonConverter type argument - which is no longer needed 2014-04-29 11:47:59 +01:00
cankoluman 258b0859ab Issue #AR-1_serialization: clean up 2014-04-29 11:28:30 +01:00
cankoluman cee7d73b01 Issue #AR-1_serialization: finish tests for complex tree model use case 2014-04-29 11:23:39 +01:00
cankoluman 1e12b82742 Issue #AR-1_serialization: fix deserialization for complex models (i.e. models employing AsFiedlset hinting) 2014-04-29 10:06:22 +01:00
cankoluman 7b5d9b4dd5 Issue #AR-1_serialization: clarify deserialization logic 2014-04-28 14:54:47 +01:00
cankoluman 766ae0a8df Issue #AR-1_serialization: removed hardcoded fieldsets[0].properties restriction 2014-04-28 14:32:34 +01:00
cankoluman 43e1855b2c Issue #AR-1_serialization: fix regression error, added AsFieldsetAttribute to help resolve ambiguous complex models, some renaming 2014-04-28 12:45:30 +01:00
cankoluman cbec133186 Issue #AR-1_serialization: added serialization wrappers, and support for complex model serialization,
- see test examples
2014-04-25 17:34:56 +01:00
cankoluman 23735fdf59 Issue #AR-1_serialization: rename file 2014-04-24 17:03:30 +01:00
cankoluman 8c005b2ec9 Issue #AR-1_serialization: rename test models file 2014-04-24 16:23:28 +01:00
cankoluman bfb1cc6abc Issue #AR-1_serialization: add support for JsonIgnoreAttribute,
- re-factor names,
- move all test strings to separate class
2014-04-24 16:21:52 +01:00
cankoluman ea0d641f25 Issue #AR-1_serialization: add a numerical and date model test,
-  fix errors in deserializing nullable type values
2014-04-24 15:51:04 +01:00
cankoluman 67311836c8 Issue #AR-1_serialization: update json test input 2014-04-24 10:49:04 +01:00
cankoluman 22d596e827 Issue #AR-1_serialization: initial commit of model serializer and tests. 2014-04-24 10:43:48 +01:00
cankoluman b42306e741 Issue #AR-1_serialization: added msbuild (.NET) version, as we need this for VS < 2013,
- updated msbuild module
2014-04-24 10:20:50 +01:00
67 changed files with 4170 additions and 180 deletions
+3 -1
View File
@@ -55,6 +55,7 @@ module.exports = function(grunt) {
'app/controllers/controller.js',
'app/controllers/config.controller.js',
'app/directives/archetypeproperty.js',
'app/directives/archetypecustomview.js',
'app/directives/localize.js',
'app/services/localization.js',
'app/resources/propertyeditor.js'
@@ -66,7 +67,7 @@ module.exports = function(grunt) {
copy: {
html: {
cwd: 'app/views/',
src: ['archetype.html', 'archetype.config.html'],
src: ['archetype.html', 'archetype.default.html', 'archetype.config.html'],
dest: '<%= basePath %>/views',
expand: true
},
@@ -190,6 +191,7 @@ module.exports = function(grunt) {
stdout: true,
verbosity: 'quiet',
maxCpuCount: 4,
version: 4.0,
buildParameters: {
WarningLevel: 2,
NoWarn: 1607
+2
View File
@@ -2,6 +2,8 @@ Archetype
=========
![alt tag](http://imulus.github.io/Archetype/images/logo.png)
![alt tag](http://imulus.github.io/Archetype/images/example1.png)
## Installation
Install the selected <a href='https://github.com/imulus/Archetype/releases'>release</a> through the Umbraco package installer or via <a href='http://www.nuget.org/packages/Archetype/'>NuGet</a>.
@@ -222,11 +222,30 @@
<Compile Include="Models\FieldsetTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PropertyValueConverter\PropertyValueConverterTests.cs" />
<Compile Include="Serialization\Base\ArchetypeJsonConverterTestBase.cs" />
<Compile Include="Serialization\Base\RegressionTestBase.cs" />
<Compile Include="Serialization\Delinter\ArchetypeJsonDelinterTest.cs" />
<Compile Include="Serialization\Regression\ArchetypeJsonConverterLegacySetUpTest.cs" />
<Compile Include="Serialization\Regression\ArchetypeJsonConverterTest.cs" />
<Compile Include="Serialization\Regression\JsonTestLegacyModelSetUp.cs" />
<Compile Include="Serialization\Regression\SerializationTestHelper.cs" />
<Compile Include="Serialization\Regression\JsonTestModels.cs" />
<Compile Include="Serialization\Regression\JsonTestStrings.cs" />
<Compile Include="Serialization\UseCases\ArchetypeJsonConverterTest.cs" />
<Compile Include="Serialization\UseCases\Complex\ArchetypeJsonConverterTest.cs" />
<Compile Include="Serialization\UseCases\Complex\JsonTestModels.cs" />
<Compile Include="Serialization\UseCases\Complex\JsonTestStrings.cs" />
<Compile Include="Serialization\UseCases\Enumerable\ArchetypeJsonConverterTest.cs" />
<Compile Include="Serialization\UseCases\Enumerable\JsonTestModels.cs" />
<Compile Include="Serialization\UseCases\Enumerable\JsonTestStrings.cs" />
<Compile Include="Serialization\UseCases\JsonTestModels.cs" />
<Compile Include="Serialization\UseCases\JsonTestStrings.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="Data\sample-1.json" />
<None Include="packages.config" />
<None Include="SupportingFiles\config\umbracoSettings.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Archetype\Archetype.Umbraco.csproj">
@@ -234,8 +253,16 @@
<Name>Archetype.Umbraco</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>xcopy $(ProjectDir)SupportingFiles\config\* $(TargetDir)config /I /O /X /E /K /D /Y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
@@ -5,7 +5,9 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Archetype.PropertyConverters;
using Archetype.Tests.Serialization.UseCases;
using NUnit.Framework;
using Archetype.Tests.Serialization;
namespace Archetype.Tests.Models
{
@@ -34,6 +36,18 @@ namespace Archetype.Tests.Models
Assert.That(propertyValue == "Box 1 Title");
}
[Test]
public void Can_Get_Fieldset_Property_Default_Value_By_Alias()
{
var converter = new ArchetypeValueConverter();
var result = (Archetype.Models.ArchetypeModel)converter.ConvertDataToSource(null, _sampleJson, false);
var fieldset = result.Fieldsets.First();
var propertyValue = fieldset.GetValue<string>("noSuchProperty", "noSuchProperty default value");
Assert.That(propertyValue == "noSuchProperty default value");
}
[Test]
public void Can_Convert_Property_Value_Types()
{
@@ -47,6 +61,24 @@ namespace Archetype.Tests.Models
Assert.That(fieldset.GetValue<string>("blurb") == "A blurb here");
}
[Test]
public void Can_Convert_Property_Default_Value_Types()
{
var converter = new ArchetypeValueConverter();
var result = (Archetype.Models.ArchetypeModel)converter.ConvertDataToSource(null, _sampleJson, false);
var fieldset = result.Fieldsets.First();
Assert.That(fieldset.GetValue<int>("noSuchInteger", 1234) == 1234);
Assert.That(fieldset.GetValue<bool>("noSuchBoolean", true) == true);
Assert.That(fieldset.GetValue<bool>("noSuchBoolean", false) == false);
Assert.That(fieldset.GetValue<string>("noSuchString", "noSuchString default value") == "noSuchString default value");
var contactDetails = fieldset.GetValue<ContactDetails>("noSuchContact", new ContactDetails { Address = "some street, some city", Name = "someone" });
Assert.That(contactDetails.Address == "some street, some city");
Assert.That(contactDetails.Name == "someone");
}
[Test]
public void Returns_String_When_No_Type_Specified()
{
@@ -0,0 +1,33 @@
using Archetype.PropertyConverters;
using at = Archetype.Models;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.Base
{
public abstract class ArchetypeJsonConverterTestBase
{
public at.ArchetypeModel ConvertModelToArchetype<T>(T model)
{
var converter = new ArchetypeValueConverter();
var json = JsonConvert.SerializeObject(model);
var archetype = (at.ArchetypeModel)converter.ConvertDataToSource(null, json, false);
return archetype;
}
public string ConvertModelToArchetypeJson<T>(T model, Formatting formatting = Formatting.None)
{
return JsonConvert.SerializeObject(model, formatting);
}
public T ConvertArchetypeJsonToModel<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json);
}
public T ConvertModelToArchetypeAndBack<T>(T model)
{
var json = ConvertModelToArchetypeJson(model);
return ConvertArchetypeJsonToModel<T>(json);
}
}
}
@@ -0,0 +1,166 @@
using System.Collections;
using System.Linq;
using System.Reflection;
using Archetype.Serialization;
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
namespace Archetype.Tests.Serialization.Base
{
public abstract class RegressionTestBase : ArchetypeJsonConverterTestBase
{
protected void SimpleModel_Regression_Battery<T>(T model)
{
var archetype = ConvertModelToArchetype(model);
Assert.IsNotNull(archetype);
var archetypeJson = archetype.SerializeForPersistence();
var modelJson = ConvertModelToArchetypeJson(model, Formatting.Indented);
Assert.IsNotNullOrEmpty(modelJson);
var result = ConvertModelToArchetypeAndBack(model);
var resultfromArchetypeJson = ConvertArchetypeJsonToModel<T>(archetypeJson);
Assert.IsInstanceOf<T>(result);
Assert.IsInstanceOf<T>(resultfromArchetypeJson);
AssertAreEqual(model, result);
AssertAreEqual(model, resultfromArchetypeJson);
}
protected void SimpleModels_Regression_Battery<T>(T model)
where T : IList
{
var archetype = ConvertModelToArchetype(model);
Assert.IsNotNull(archetype);
var archetypeJson = archetype.SerializeForPersistence();
var modelJson = ConvertModelToArchetypeJson(model, Formatting.Indented);
Assert.IsNotNullOrEmpty(modelJson);
var result = ConvertModelToArchetypeAndBack(model);
var resultfromArchetypeJson = ConvertArchetypeJsonToModel<T>(archetypeJson);
Assert.IsInstanceOf<T>(result);
Assert.IsInstanceOf<T>(resultfromArchetypeJson);
Assert.AreEqual(model.Count, result.Count);
Assert.AreEqual(model.Count, resultfromArchetypeJson.Count);
foreach (var resultItem in result)
{
var index = result.IndexOf(resultItem);
AssertAreEqual(model[index], resultItem);
}
foreach (var resultItem in resultfromArchetypeJson)
{
var index = resultfromArchetypeJson.IndexOf(resultItem);
AssertAreEqual(model[index], resultItem);
}
}
protected void CompoundModel_Regression_Battery<T>(T model)
{
var archetype = ConvertModelToArchetype(model);
Assert.IsNotNull(archetype);
var archetypeJson = archetype.SerializeForPersistence();
var modelJson = ConvertModelToArchetypeJson(model, Formatting.Indented);
Assert.IsNotNullOrEmpty(modelJson);
var result = ConvertModelToArchetypeAndBack(model);
var resultfromArchetypeJson = ConvertArchetypeJsonToModel<T>(archetypeJson);
Assert.IsInstanceOf<T>(result);
AssertAreEqual(model, result);
Assert.IsInstanceOf<T>(resultfromArchetypeJson);
AssertAreEqual(model, resultfromArchetypeJson);
}
protected void NestedModel_Regression_Battery<T>(T model)
{
CompoundModel_Regression_Battery(model);
}
protected void ComplexModel_Regression_Battery<T>(T model)
{
CompoundModel_Regression_Battery(model);
}
protected void AssertAreEqual<T>(T model, T result)
{
foreach (var propInfo in model.GetSerialiazableProperties())
{
var expected = GetExpectedValue(model, propInfo);
var actual = GetActualValue(result, propInfo);
if (propInfo.PropertyType.Namespace.Equals("System"))
{
Assert.AreEqual(expected, actual);
continue;
}
var list = expected as IList;
if (list != null)
{
foreach (var expectedItem in list)
{
var index = list.IndexOf(expectedItem);
AssertAreEqual(expectedItem, ((IList)actual)[index]);
}
continue;
}
AssertAreEqual(expected, actual);
}
}
protected void EnsureUmbracoContext(Mock<IDataTypeService> dataTypeService)
{
var cacheHelper = CacheHelper.CreateDisabledCacheHelper();
var serviceContext = new ServiceContext(
Mock.Of<IContentService>(),
Mock.Of<IMediaService>(),
Mock.Of<IContentTypeService>(),
dataTypeService.Object,
Mock.Of<IFileService>(),
Mock.Of<ILocalizationService>(),
Mock.Of<IPackagingService>(),
Mock.Of<IEntityService>(),
Mock.Of<IRelationService>(),
Mock.Of<IMemberGroupService>(),
Mock.Of<IMemberTypeService>(),
Mock.Of<IMemberService>(),
Mock.Of<IUserService>(),
Mock.Of<ISectionService>(),
Mock.Of<IApplicationTreeService>(),
Mock.Of<ITagService>(),
Mock.Of<INotificationService>()
);
ApplicationContext.EnsureContext(
new DatabaseContext(Mock.Of<IDatabaseFactory>()), serviceContext, cacheHelper, true);
}
private object GetExpectedValue<T>(T expected, PropertyInfo propInfo)
{
return propInfo.GetValue(expected, null);
}
private object GetActualValue<T>(T actual, PropertyInfo propInfo)
{
var actualProp = actual.GetSerialiazableProperties().SingleOrDefault(pinfo => pinfo.Name.Equals(propInfo.Name));
return actualProp != null
? actualProp.GetValue(actual, null)
: null;
}
}
}
@@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.PropertyEditors;
using Archetype.Serialization;
using Archetype.Tests.Serialization.Base;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Archetype.Tests.Serialization.Delinter
{
[TestFixture]
public class ArchetypeJsonDelinterTest : RegressionTestBase
{
private const string _ESCAPED_JSON = @"{""fieldsets"":[{""properties"":[{""alias"":""slides"",""value"":""2680""}],""alias"":""slides""},{""properties"":[{""alias"":""captions"",""value"":""{\""fieldsets\"":[{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test1 test1\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test2 test2\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test3b test3b\""}],\""alias\"":\""textstringArray\""}]}""}],""alias"":""captions""}]}";
private const string _JSON = @"{""fieldsets"":[{""properties"":[{""alias"":""slides"",""value"":""2680""}],""alias"":""slides""},{""properties"":[{""alias"":""captions"",""value"":{""fieldsets"":[{""properties"":[{""alias"":""textstring"",""value"":""test1 test1""}],""alias"":""textstringArray""},{""properties"":[{""alias"":""textstring"",""value"":""test2 test2""}],""alias"":""textstringArray""},{""properties"":[{""alias"":""textstring"",""value"":""test3b test3b""}],""alias"":""textstringArray""}]}}],""alias"":""captions""}]}";
private const string _ESCAPED_JSON_DEEP_NESTED = @"{""fieldsets"":[{""properties"":[{""alias"":""pages"",""value"":""""},{""alias"":""captions"",""value"":""{\""fieldsets\"":[{\""properties\"":[{\""alias\"":\""captions\"",\""value\"":\""{\\\""fieldsets\\\"":[{\\\""properties\\\"":[{\\\""alias\\\"":\\\""textString\\\"",\\\""value\\\"":\\\""{\\\\\\\""fieldsets\\\\\\\"":[{\\\\\\\""properties\\\\\\\"":[{\\\\\\\""alias\\\\\\\"":\\\\\\\""textString\\\\\\\"",\\\\\\\""value\\\\\\\"":\\\\\\\""\\\\\\\""}],\\\\\\\""alias\\\\\\\"":\\\\\\\""textItem\\\\\\\""}]}\\\""}],\\\""alias\\\"":\\\""textList\\\""}]}\""}],\""alias\"":\""captions\""}]}""}],""alias"":""pages""}]}";
private const string _ESCAPED_JSON_FROM_APP = @"{
""alias"": ""captions"",
""value"": ""{\""fieldsets\"":[{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test1 test1\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test2 test2\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test3b test3b\""}],\""alias\"":\""textstringArray\""}]}""
}";
private SlideShow _referenceModel;
private NestedClass _nestedClass;
[SetUp]
public void SetUp()
{
_referenceModel = new SlideShow()
{
Slides = "2680",
Captions = new Captions()
{
new TextString(){Text = "test1 test1"},
new TextString(){Text = "test2 test2"},
new TextString(){Text = "test3b test3b"}
}
};
_nestedClass = GetNestedClass();
_nestedClass.SlideShow = _referenceModel;
_nestedClass.Nested = GetNestedClass();
_nestedClass.Nested.SlideShow = _referenceModel;
_nestedClass.Nested.Nested = GetNestedClass();
}
[Test]
public void Delinter_RemoveNewLine_ByPasses_Values()
{
const string _JSON_WITH_RESERVED_CHARS = @"{
""alias"": ""captions"",
""value"": ""{ \""fieldsets\"": [
{
\""properties\"":
[
{ \""alias\"": \""textstring\"" , \""value\"":\""test1 \r\n test1\r\n\""}], \""alias\"":\""textstringArray\"" },{\""properties\"": [ { \""alias\"":\""textstring\"",\""value\"":\""test2\r\ntest2\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test3b \""quote\"" \\r\\r\\rtest3\""}],\""alias\"":\""textstringArray\"" } ] }""
}";
var referenceModel = new Captions()
{
new TextString() {Text = @"test1
test1
"},
new TextString() {Text = @"test2
test2"},
new TextString() {Text = @"test3b ""quote"" \r\r\rtest3"}
};
var delintedJson = _JSON_WITH_RESERVED_CHARS.DelintArchetypeJson();
var actualModel = ConvertArchetypeJsonToModel<Captions>(delintedJson);
var t = JToken.Parse(delintedJson).ToString(Formatting.None);
Assert.IsInstanceOf<Captions>(actualModel);
Assert.IsNotNull(actualModel);
Assert.AreEqual(3, actualModel.Count);
foreach (var refItem in referenceModel)
{
var index = referenceModel.IndexOf(refItem);
AssertAreEqual(refItem, actualModel.ElementAt(index));
}
}
[Test]
public void ReferenceJsonAndModel_Equal_ExpectedJsonAndModel()
{
const string expectedJson = _JSON;
var expectedModel = ConvertArchetypeJsonToModel<SlideShow>(expectedJson);
var referenceModel = ConvertModelToArchetypeAndBack(_referenceModel);
Assert.IsInstanceOf<SlideShow>(expectedModel);
Assert.IsInstanceOf<SlideShow>(referenceModel);
Assert.AreEqual(3, expectedModel.Captions.Count);
Assert.AreEqual(3, referenceModel.Captions.Count);
AssertAreEqual(expectedModel, referenceModel);
}
[Test]
public void UnescapedJsonAndModel_Equal_ExpectedJsonAndModel()
{
const string expectedJson = _JSON;
var unescapedJson = _ESCAPED_JSON.DelintArchetypeJson();
var expectedModel = ConvertArchetypeJsonToModel<SlideShow>(expectedJson);
var actualModel = ConvertArchetypeJsonToModel<SlideShow>(unescapedJson);
Assert.IsInstanceOf<SlideShow>(expectedModel);
Assert.IsInstanceOf<SlideShow>(actualModel);
Assert.AreEqual(3, expectedModel.Captions.Count);
Assert.AreEqual(3, actualModel.Captions.Count);
AssertAreEqual(expectedModel, actualModel);
}
[Test]
public void EscapedJson_FromApp_SerializesToModel()
{
var delintedJson = _ESCAPED_JSON_FROM_APP.DelintArchetypeJson();
var referenceModel = new Captions()
{
new TextString() {Text = "test1 test1"},
new TextString() {Text = "test2 test2"},
new TextString() {Text = "test3b test3b"}
};
var actualModel = ConvertArchetypeJsonToModel<Captions>(delintedJson);
Assert.IsInstanceOf<Captions>(actualModel);
Assert.AreEqual(3, actualModel.Count);
foreach (var refItem in referenceModel)
{
var index = referenceModel.IndexOf(refItem);
AssertAreEqual(refItem, actualModel.ElementAt(index));
}
}
[Test]
public void DeepNestedJson_Escapes_Correctly()
{
var propGuid = new Guid();
var dtd = new DataTypeDefinition(-1, String.Empty) { Id = 0 };
var dataTypeService = new Mock<IDataTypeService>();
dataTypeService.Setup(dts => dts.GetDataTypeDefinitionById(Guid.Parse(propGuid.ToString()))).Returns(dtd);
dataTypeService.Setup(dts => dts.GetPreValuesCollectionByDataTypeId(dtd.Id)).Returns(new PreValueCollection(new Dictionary<string, PreValue>()));
var propValueEditor = new Mock<ArchetypePropertyEditor.ArchetypePropertyValueEditor>(new PropertyValueEditor());
propValueEditor.Setup(pe => pe.GetPropertyEditor(dtd)).Returns(new ArchetypePropertyEditor());
var archetype = ConvertModelToArchetype(_nestedClass);
foreach (var prop in archetype.Fieldsets.SelectMany(fs => fs.Properties))
{
prop.DataTypeGuid = propGuid.ToString();
if (prop.Value.ToString().Contains("fieldsets"))
prop.PropertyEditorAlias = Constants.PropertyEditorAlias;
}
var archetypeJson = JsonConvert.SerializeObject(archetype);
Assert.IsNotNullOrEmpty(archetypeJson);
EnsureUmbracoContext(dataTypeService);
var propEditor = new ArchetypePropertyEditor.ArchetypePropertyValueEditor(new PropertyValueEditor());
var convertedJson = propEditor.ConvertEditorToDb(new ContentPropertyData(JObject.FromObject(archetype),
new PreValueCollection(new Dictionary<string, PreValue>()), new Dictionary<string, object>()),
archetypeJson);
Assert.IsNotNull(convertedJson);
var delintedJson = convertedJson.ToString().DelintArchetypeJson();
Assert.IsNotNullOrEmpty(delintedJson);
var modelfromArchetypeJson = ConvertArchetypeJsonToModel<NestedClass>(archetypeJson);
var modelfromConvertedJson = ConvertArchetypeJsonToModel<NestedClass>(delintedJson);
Assert.IsInstanceOf<NestedClass>(modelfromArchetypeJson);
Assert.IsInstanceOf<NestedClass>(modelfromConvertedJson);
Assert.AreEqual(3, modelfromArchetypeJson.SlideShow.Captions.Count);
Assert.AreEqual(3, modelfromConvertedJson.SlideShow.Captions.Count);
Assert.AreEqual(3, modelfromArchetypeJson.Nested.SlideShow.Captions.Count);
Assert.AreEqual(3, modelfromConvertedJson.Nested.SlideShow.Captions.Count);
AssertAreEqual(modelfromArchetypeJson, modelfromConvertedJson);
}
#region private methods
private NestedClass GetNestedClass()
{
return new NestedClass();
}
#endregion
#region classes
[AsArchetype("slides")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SlideShow
{
[JsonProperty("slides")]
public string Slides { get; set; }
[AsFieldset]
[JsonProperty("captions")]
public Captions Captions { get; set; }
}
[AsArchetype("textstringArray")] /* Note: when inheriting from a list, archetype alias is not used */
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Captions : List<TextString>
{
}
[AsArchetype("textstringArray")] /* due to hard coded string calues above, must have same archetype alias as list class */
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextString
{
[JsonProperty("textstring")]
public String Text { get; set; }
}
[AsArchetype("nestedClass")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class NestedClass
{
[JsonProperty("nestedClass")]
public NestedClass Nested { get; set; }
[JsonProperty("slides")]
public SlideShow SlideShow { get; set; }
}
#endregion
}
}
@@ -0,0 +1,25 @@
using System.Linq;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Archetype.Tests.Serialization.Regression
{
[TestFixture]
public class ArchetypeJsonConverterLegacySetUpTest
{
[Test]
public void Deserialize_SlideShow_FromArchetype()
{
var result = JsonConvert.DeserializeObject<SlideShow>(JsonTestStrings._SLIDES_JSON);
Assert.NotNull(result);
Assert.IsInstanceOf<SlideShow>(result);
Assert.AreEqual("1,2,3,4,5", result.Slides);
Assert.AreEqual("Test 1", result.Captions.TextStringArray.ElementAt(0).Text);
Assert.AreEqual("Test 2", result.Captions.TextStringArray.ElementAt(1).Text);
Assert.AreEqual("Test 3", result.Captions.TextStringArray.ElementAt(2).Text);
Assert.AreEqual("Test 4", result.Captions.TextStringArray.ElementAt(3).Text);
}
}
}
@@ -0,0 +1,150 @@
using Archetype.Tests.Serialization.Base;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Archetype.Tests.Serialization.Regression
{
[TestFixture]
public class ArchetypeJsonConverterTest : RegressionTestBase
{
private SerializationTestHelper _testHelper;
[SetUp]
public void SetUp()
{
_testHelper = new SerializationTestHelper();
}
[Test]
public void EmptyArray_Returns_Null()
{
var archetypeJson = @"{""fieldsets"": []}";
var result = ConvertArchetypeJsonToModel<SimpleModel>(archetypeJson);
Assert.IsNull(result);
}
[Test]
public void SimpleModel_Regression_Battery()
{
var simpleModel = _testHelper.GetModel<SimpleModel>();
SimpleModel_Regression_Battery(simpleModel);
var json = ConvertModelToArchetypeJson(simpleModel, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._SIMPLE_JSON, json);
}
[Test]
public void SimpleModelWithFieldsets_Regression_Battery()
{
var simpleModel = _testHelper.GetModel<SimpleModelWithFieldsets>();
SimpleModel_Regression_Battery(simpleModel);
}
[Test]
public void SimpleModelWithMixedFieldsets_Regression_Battery()
{
var simpleModel = _testHelper.GetModel<SimpleModelWithMixedFieldsets>();
SimpleModel_Regression_Battery(simpleModel);
}
[Test]
public void SimpleModels_Regression_Battery()
{
var simpleModels = _testHelper.GetModel<SimpleModels>();
SimpleModels_Regression_Battery(simpleModels);
}
[Test]
public void SimpleModelsWithFieldsets_Regression_Battery()
{
var simpleModels = _testHelper.GetModel<SimpleModelsWithFieldsets>();
SimpleModels_Regression_Battery(simpleModels);
}
[Test]
public void SimpleModelsWithMixedFieldsets_Regression_Battery()
{
var simpleModels = _testHelper.GetModel<SimpleModelsWithMixedFieldsets>();
SimpleModels_Regression_Battery(simpleModels);
}
[Test]
public void CompoundModel_Regression_Battery()
{
var compoundModel = _testHelper.GetModel<CompoundModel>();
CompoundModel_Regression_Battery(compoundModel);
}
[Test]
public void CompoundModelWithMixedFieldsetVariant1_Regression_Battery()
{
var compoundModel = _testHelper.GetModel<CompoundModelWithMixedFieldsetVariant1>();
CompoundModel_Regression_Battery(compoundModel);
}
[Test]
public void CompoundModelWithMixedFieldsetVariant2_Regression_Battery()
{
var compoundModel = _testHelper.GetModel<CompoundModelWithMixedFieldsetVariant2>();
CompoundModel_Regression_Battery(compoundModel);
}
[Test]
public void CompoundModelWithFieldset_Regression_Battery()
{
var compoundModel = _testHelper.GetModel<CompoundModelWithFieldset>();
CompoundModel_Regression_Battery(compoundModel);
}
[Test]
public void CompoundModelWithList_Regression_Battery()
{
var compoundModelWithList = _testHelper.GetModel<CompoundModelWithList>();
CompoundModel_Regression_Battery(compoundModelWithList);
}
[Test]
public void CompoundModelWithMixedFieldsetWithList_Regression_Battery()
{
var compoundModelWithList = _testHelper.GetModel<CompoundModelWithMixedFieldsetWithList>();
CompoundModel_Regression_Battery(compoundModelWithList);
}
[Test]
public void CompoundModelWithFieldsetWithList_Regression_Battery()
{
var compoundModelWithList = _testHelper.GetModel<CompoundModelWithFieldsetWithList>();
CompoundModel_Regression_Battery(compoundModelWithList);
}
[Test]
public void NestedModel_Regression_Battery()
{
var nestedModel = _testHelper.GetModel<NestedModel>();
NestedModel_Regression_Battery(nestedModel);
}
[Test]
public void NestedModelWithMixedFieldset_Regression_Battery()
{
var nestedModel = _testHelper.GetModel<NestedModelWithMixedFieldset>();
NestedModel_Regression_Battery(nestedModel);
}
[Test]
public void NestedModelWithFieldset_Regression_Battery()
{
var nestedModel = _testHelper.GetModel<NestedModelWithFieldset>();
NestedModel_Regression_Battery(nestedModel);
}
[Test]
public void ComplexModel_Regression_Battery()
{
var complexModel = _testHelper.GetModel<ComplexModel>();
NestedModel_Regression_Battery(complexModel);
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using Archetype.Serialization;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.Regression
{
[AsArchetype("textstringArray")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextString
{
[JsonProperty("textstring")]
public String Text { get; set; }
}
[AsArchetype("textstringArray")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextStringArray : List<TextString>
{
}
[AsArchetype("captions")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Captions
{
[JsonProperty("captions")]
public TextStringArray TextStringArray { get; set; }
}
[AsArchetype("slides")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SlideShow
{
[JsonProperty("slides")]
public string Slides { get; set; }
[AsFieldset]
[JsonProperty("captions")]
public Captions Captions { get; set; }
}
}
@@ -0,0 +1,257 @@
using System;
using System.Collections.Generic;
using Archetype.Serialization;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.Regression
{
[AsArchetype("simpleModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModel
{
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
[JsonProperty("nullableInteger")]
public int? NullableId { get; set; }
[JsonProperty("double")]
public double Amount { get; set; }
[JsonProperty("nullableDouble")]
public double? NullableAmount { get; set; }
[JsonProperty("dateTime")]
public DateTime DateOne { get; set; }
[JsonProperty("nullableDateTime")]
public DateTime? DateTwo { get; set; }
}
[AsArchetype("simpleModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModelWithFieldsets
{
[AsFieldset]
[JsonProperty("text")]
public string Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
[AsFieldset]
[JsonProperty("nullableInteger")]
public int? NullableId { get; set; }
[AsFieldset]
[JsonProperty("double")]
public double Amount { get; set; }
[AsFieldset]
[JsonProperty("nullableDouble")]
public double? NullableAmount { get; set; }
[AsFieldset]
[JsonProperty("dateTime")]
public DateTime DateOne { get; set; }
[AsFieldset]
[JsonProperty("nullableDateTime")]
public DateTime? DateTwo { get; set; }
}
[AsArchetype("simpleModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModelWithMixedFieldsets
{
[AsFieldset]
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
[AsFieldset]
[JsonProperty("nullableInteger")]
public int? NullableId { get; set; }
[JsonProperty("double")]
public double Amount { get; set; }
[AsFieldset]
[JsonProperty("nullableDouble")]
public double? NullableAmount { get; set; }
[AsFieldset]
[JsonProperty("dateTime")]
public DateTime DateOne { get; set; }
public DateTime? DateTwo { get; set; }
}
[AsArchetype("simpleModels")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModels : List<SimpleModel>
{
}
[AsArchetype("simpleModels")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModelsWithFieldsets : List<SimpleModelWithFieldsets>
{
}
[AsArchetype("simpleModels")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SimpleModelsWithMixedFieldsets : List<SimpleModelWithMixedFieldsets>
{
}
[AsArchetype("compoundModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModel
{
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModelWithMixedFieldset")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithMixedFieldsetVariant1
{
[AsFieldset]
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModelWithMixedFieldset")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithMixedFieldsetVariant2
{
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[AsFieldset]
[JsonProperty("text")]
public String Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithFieldset
{
[AsFieldset]
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[AsFieldset]
[JsonProperty("text")]
public String Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModelWithList")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithList
{
[JsonProperty("simpleModelList")]
public List<SimpleModel> SimpleModelList { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModelWithList")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithMixedFieldsetWithList
{
[JsonProperty("simpleModelList")]
public List<SimpleModel> SimpleModelList { get; set; }
[AsFieldset]
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("compoundModelWithList")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWithFieldsetWithList
{
[AsFieldset]
[JsonProperty("simpleModelList")]
public List<SimpleModel> SimpleModelList { get; set; }
[AsFieldset]
[JsonProperty("text")]
public String Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("nestedModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class NestedModel
{
[JsonProperty("compoundModel")]
public CompoundModel CompoundModel { get; set; }
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("nestedModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class NestedModelWithMixedFieldset
{
[JsonProperty("compoundModel")]
public CompoundModel CompoundModel { get; set; }
[AsFieldset]
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("nestedModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class NestedModelWithFieldset
{
[AsFieldset]
[JsonProperty("compoundModel")]
public CompoundModel CompoundModel { get; set; }
[AsFieldset]
[JsonProperty("simpleModel")]
public SimpleModel SimpleModel { get; set; }
[AsFieldset]
[JsonProperty("text")]
public String Text { get; set; }
[AsFieldset]
[JsonProperty("integer")]
public int Id { get; set; }
}
[AsArchetype("complexModel")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class ComplexModel
{
[JsonProperty("compoundModels")]
public CompoundModelWrapper CompoundModelWrapper { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
}
[AsArchetype("compoundModelWrapper")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompoundModelWrapper
{
[JsonProperty("compoundModels")]
public List<CompoundModel> CompoundModels { get; set; }
[JsonProperty("text")]
public String Text { get; set; }
}
}
@@ -0,0 +1,124 @@
namespace Archetype.Tests.Serialization.Regression
{
public class JsonTestStrings
{
public const string _SIMPLE_JSON = @"{
""fieldsets"": [
{
""alias"": ""simpleModel"",
""properties"": [
{
""alias"": ""text"",
""value"": ""Test \r\nText \""quote\"" \r\n""
},
{
""alias"": ""integer"",
""value"": ""123""
},
{
""alias"": ""nullableInteger"",
""value"": """"
},
{
""alias"": ""double"",
""value"": ""5.67""
},
{
""alias"": ""nullableDouble"",
""value"": """"
},
{
""alias"": ""dateTime"",
""value"": ""01/01/2000 00:00:00""
},
{
""alias"": ""nullableDateTime"",
""value"": """"
}
]
}
]
}";
public const string _ESCAPED_JSON =
@"{
""fieldsets"":[
{
""properties"":[
{
""alias"":""pages"",
""value"":""2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453""
},
{
""alias"":""captions"",
""value"":""{\""fieldsets\"":[{\""properties\"":[{\""alias\"":\""captions\"",\""value\"":\""{\\\""fieldsets\\\"":[{\\\""properties\\\"":[{\\\""alias\\\"":\\\""textString\\\"",\\\""value\\\"":\\\""{\\\\\\\""fieldsets\\\\\\\"":[{\\\\\\\""properties\\\\\\\"":[{\\\\\\\""alias\\\\\\\"":\\\\\\\""textString\\\\\\\"",\\\\\\\""value\\\\\\\"":\\\\\\\""First Page\\\\\\\"",\\\\\\\""propertyEditorAlias\\\\\\\"":\\\\\\\""Umbraco.Textbox\\\\\\\"",\\\\\\\""dataTypeId\\\\\\\"":-88,\\\\\\\""dataTypeGuid\\\\\\\"":\\\\\\\""0cc0eba1-9960-42c9-bf9b-60e150b429ae\\\\\\\""}],\\\\\\\""alias\\\\\\\"":\\\\\\\""textItem\\\\\\\""}]}\\\"",\\\""propertyEditorAlias\\\"":\\\""Imulus.Archetype\\\"",\\\""dataTypeId\\\"":3764,\\\""dataTypeGuid\\\"":\\\""496cfe35-448f-4b59-9c75-050798afdce4\\\""}],\\\""alias\\\"":\\\""textList\\\""},{\\\""properties\\\"":[{\\\""alias\\\"":\\\""textString\\\"",\\\""value\\\"":\\\""{\\\\\\\""fieldsets\\\\\\\"":[{\\\\\\\""properties\\\\\\\"":[{\\\\\\\""alias\\\\\\\"":\\\\\\\""textString\\\\\\\"",\\\\\\\""value\\\\\\\"":\\\\\\\""Second Page\\\\\\\"",\\\\\\\""propertyEditorAlias\\\\\\\"":\\\\\\\""Umbraco.Textbox\\\\\\\"",\\\\\\\""dataTypeId\\\\\\\"":-88,\\\\\\\""dataTypeGuid\\\\\\\"":\\\\\\\""0cc0eba1-9960-42c9-bf9b-60e150b429ae\\\\\\\""}],\\\\\\\""alias\\\\\\\"":\\\\\\\""textItem\\\\\\\""}]}\\\"",\\\""propertyEditorAlias\\\"":\\\""Imulus.Archetype\\\"",\\\""dataTypeId\\\"":3764,\\\""dataTypeGuid\\\"":\\\""496cfe35-448f-4b59-9c75-050798afdce4\\\""}],\\\""alias\\\"":\\\""textList\\\""},{\\\""properties\\\"":[{\\\""alias\\\"":\\\""textString\\\"",\\\""value\\\"":\\\""{\\\\\\\""fieldsets\\\\\\\"":[{\\\\\\\""properties\\\\\\\"":[{\\\\\\\""alias\\\\\\\"":\\\\\\\""textString\\\\\\\"",\\\\\\\""value\\\\\\\"":\\\\\\\""Third Page\\\\\\\"",\\\\\\\""propertyEditorAlias\\\\\\\"":\\\\\\\""Umbraco.Textbox\\\\\\\"",\\\\\\\""dataTypeId\\\\\\\"":-88,\\\\\\\""dataTypeGuid\\\\\\\"":\\\\\\\""0cc0eba1-9960-42c9-bf9b-60e150b429ae\\\\\\\""}],\\\\\\\""alias\\\\\\\"":\\\\\\\""textItem\\\\\\\""}]}\\\"",\\\""propertyEditorAlias\\\"":\\\""Imulus.Archetype\\\"",\\\""dataTypeId\\\"":3764,\\\""dataTypeGuid\\\"":\\\""496cfe35-448f-4b59-9c75-050798afdce4\\\""}],\\\""alias\\\"":\\\""textList\\\""},{\\\""properties\\\"":[{\\\""alias\\\"":\\\""textString\\\"",\\\""value\\\"":\\\""{\\\\\\\""fieldsets\\\\\\\"":[{\\\\\\\""properties\\\\\\\"":[{\\\\\\\""alias\\\\\\\"":\\\\\\\""textString\\\\\\\"",\\\\\\\""value\\\\\\\"":\\\\\\\""Fourth Page\\\\\\\"",\\\\\\\""propertyEditorAlias\\\\\\\"":\\\\\\\""Umbraco.Textbox\\\\\\\"",\\\\\\\""dataTypeId\\\\\\\"":-88,\\\\\\\""dataTypeGuid\\\\\\\"":\\\\\\\""0cc0eba1-9960-42c9-bf9b-60e150b429ae\\\\\\\""}],\\\\\\\""alias\\\\\\\"":\\\\\\\""textItem\\\\\\\""}]}\\\"",\\\""propertyEditorAlias\\\"":\\\""Imulus.Archetype\\\"",\\\""dataTypeId\\\"":3764,\\\""dataTypeGuid\\\"":\\\""496cfe35-448f-4b59-9c75-050798afdce4\\\""}],\\\""alias\\\"":\\\""textList\\\""}]}\"",\""propertyEditorAlias\"":\""Imulus.Archetype\"",\""dataTypeId\"":3765,\""dataTypeGuid\"":\""4ed439b3-0ee7-447a-a64e-b2b486b86379\""}],\""alias\"":\""captions\""}]}""
}
],
""alias"":""pages""
}
]
}";
public const string _SLIDES_JSON =
@"{
""fieldsets"": [
{
""alias"": ""captions"",
""properties"": [
{
""alias"": ""captions"",
""value"": {
""fieldsets"": [
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Test 1""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Test 2""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Test 3""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Test 4""
}
]
}
]
}
}
]
},
{
""alias"": ""slides"",
""properties"": [
{
""alias"": ""slides"",
""value"": ""1,2,3,4,5""
}
]
}
]
}";
}
}
@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
namespace Archetype.Tests.Serialization.Regression
{
public class SerializationTestHelper
{
public T GetModel<T>()
{
try
{
return (T)GetType().GetMethod("Get" + typeof(T).Name).Invoke(this, null);
}
catch
{
return default(T);
}
}
public SimpleModel GetSimpleModel()
{
return new SimpleModel
{
Amount = 5.67,
NullableAmount = null,
DateOne = new DateTime(2000, 1, 1),
DateTwo = null,
Id = 123,
NullableId = null,
Text = @"Test
Text ""quote""
"
};
}
public SimpleModelWithFieldsets GetSimpleModelWithFieldsets()
{
return new SimpleModelWithFieldsets
{
Amount = 5.67,
NullableAmount = null,
DateOne = new DateTime(2000, 1, 1),
DateTwo = null,
Id = 123,
NullableId = null,
Text = "Test Text"
};
}
public SimpleModelWithMixedFieldsets GetSimpleModelWithMixedFieldsets()
{
return new SimpleModelWithMixedFieldsets
{
Amount = 5.67,
NullableAmount = null,
DateOne = new DateTime(2000, 1, 1),
DateTwo = null,
Id = 123,
NullableId = null,
Text = "Test Text"
};
}
public SimpleModels GetSimpleModels()
{
return new SimpleModels
{
GetSimpleModel(),
GetSimpleModel(),
GetSimpleModel()
};
}
public SimpleModelsWithFieldsets GetSimpleModelsWithFieldsets()
{
return new SimpleModelsWithFieldsets
{
GetSimpleModelWithFieldsets(),
GetSimpleModelWithFieldsets(),
GetSimpleModelWithFieldsets()
};
}
public SimpleModelsWithMixedFieldsets GetSimpleModelsWithMixedFieldsets()
{
return new SimpleModelsWithMixedFieldsets
{
GetSimpleModelWithMixedFieldsets(),
GetSimpleModelWithMixedFieldsets(),
GetSimpleModelWithMixedFieldsets()
};
}
public CompoundModel GetCompoundModel()
{
return new CompoundModel
{
Id = -1,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(CompoundModel).Name
};
}
public CompoundModelWithMixedFieldsetVariant1 GetCompoundModelWithMixedFieldsetVariant1()
{
return new CompoundModelWithMixedFieldsetVariant1
{
Id = -1,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(CompoundModelWithMixedFieldsetVariant1).Name
};
}
public CompoundModelWithMixedFieldsetVariant2 GetCompoundModelWithMixedFieldsetVariant2()
{
return new CompoundModelWithMixedFieldsetVariant2
{
Id = -1,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(CompoundModelWithMixedFieldsetVariant2).Name
};
}
public CompoundModelWithFieldset GetCompoundModelWithFieldset()
{
return new CompoundModelWithFieldset
{
Id = -1,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(CompoundModelWithFieldset).Name
};
}
public CompoundModelWithList GetCompoundModelWithList()
{
return new CompoundModelWithList
{
Id = 10,
SimpleModelList = GetModel<SimpleModels>(),
Text = typeof(CompoundModelWithList).Name
};
}
public CompoundModelWithMixedFieldsetWithList GetCompoundModelWithMixedFieldsetWithList()
{
return new CompoundModelWithMixedFieldsetWithList
{
Id = 11,
SimpleModelList = GetModel<SimpleModels>(),
Text = typeof(CompoundModelWithMixedFieldsetWithList).Name
};
}
public CompoundModelWithFieldsetWithList GetCompoundModelWithFieldsetWithList()
{
return new CompoundModelWithFieldsetWithList
{
Id = 12,
SimpleModelList = GetModel<SimpleModels>(),
Text = typeof(CompoundModelWithFieldsetWithList).Name
};
}
public NestedModel GetNestedModel()
{
return new NestedModel
{
CompoundModel = GetModel<CompoundModel>(),
Id = 53,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(NestedModel).Name
};
}
public NestedModelWithMixedFieldset GetNestedModelWithMixedFieldset()
{
return new NestedModelWithMixedFieldset
{
CompoundModel = GetModel<CompoundModel>(),
Id = 54,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(NestedModelWithMixedFieldset).Name
};
}
public NestedModelWithFieldset GetNestedModelWithFieldset()
{
return new NestedModelWithFieldset
{
CompoundModel = GetModel<CompoundModel>(),
Id = 55,
SimpleModel = GetModel<SimpleModel>(),
Text = typeof(NestedModelWithFieldset).Name
};
}
public CompoundModelWrapper GetCompoundModelWrapper()
{
return new CompoundModelWrapper
{
CompoundModels = new List<CompoundModel>
{
GetModel<CompoundModel>(),
GetModel<CompoundModel>(),
GetModel<CompoundModel>()
},
Text = typeof(CompoundModelWrapper).Name
};
}
public ComplexModel GetComplexModel()
{
return new ComplexModel
{
CompoundModelWrapper = GetModel<CompoundModelWrapper>(),
Text = typeof(ComplexModel).Name
};
}
}
}
@@ -0,0 +1,343 @@
using System;
using Archetype.Tests.Serialization.Base;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Archetype.Tests.Serialization.UseCases
{
[TestFixture]
public class ArchetypeJsonConverterTest : ArchetypeJsonConverterTestBase
{
private ContactDetails _contactDetails;
private CompanyDetails _companyDetails;
private AnnualStatement _annualStatement;
private MergerDetails _mergerDetails;
private UrlPicker _webSite;
[SetUp]
public void SetUp()
{
_webSite = new UrlPicker
{
Url = "http://test.com",
Title = "The Test Company",
OpenInNewWindow = true
};
_mergerDetails = new MergerDetails
{
MergerDate = new DateTime(2014,6,7,8,9,10),
MergerValue = 12345676890.12m
};
_contactDetails = new ContactDetails
{
Address = "Test Address",
Email = "test@test.com",
Fax = "000",
Mobile = "000",
Name = "Test",
Telephone = "111",
WebSite = _webSite
};
_companyDetails = new CompanyDetails
{
Region = "Test Region",
ContactDetails = _contactDetails
};
_annualStatement = new AnnualStatement
{
FiscalYearStart = new DateTime(2013, 9, 1),
FiscalYearEnd = new DateTime(2014, 8, 31),
TotalShares = 345678,
Sales = 123456700.89,
Profit = 1123456.78m
};
}
#region serialization tests
[Test]
public void Convert_MergerDetailsModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_mergerDetails, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._MERGER_DETAILS_JSON, result);
}
[Test]
public void Convert_ContactDetailsModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_contactDetails, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._CONTACT_DETAILS_JSON, result);
}
[Test]
public void Convert_CompanyDetailsModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_companyDetails, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._COMPANY_DETAILS_JSON, result);
}
[Test]
public void Convert_AnnualStatementModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_annualStatement, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._ANNUAL_STATEMENT_JSON, result);
}
[Test]
public void NullValues_Serialize_To_Empty_String()
{
var result = ConvertModelToArchetypeJson(new ContactDetails());
Assert.AreEqual(JsonTestStrings._NULL_VALUES_JSON, result);
}
[Test]
public void Convert_ContactDetailsModel_To_Archetype()
{
var model = new ContactDetails
{
Address = "addr",
Email = "email"
};
Assert.NotNull(ConvertModelToArchetype(model));
}
[Test]
public void Convert_AnnualStatementModel_To_Archetype()
{
Assert.NotNull(ConvertModelToArchetype(_annualStatement));
}
[Test]
public void Convert_AllContactDetailsModel_To_Archetype()
{
var item1 = new ContactDetails
{
Address = "addr",
Email = "email"
};
var item2 = new ContactDetails
{
Address = "addr1",
Name = "email2"
};
var model = new AllContactDetails
{
UserDetails = item1,
AdminDetails = item2
};
Assert.NotNull(ConvertModelToArchetype(model));
}
[Test]
public void Convert_ContactDetailsListModel_To_Archetype()
{
var item1 = new ContactDetails
{
Address = "addr",
Email = "email"
};
var item2 = new ContactDetails
{
Address = "addr1",
Name = "email2"
};
var model = new ContactDetailsList
{
item1,
item2
};
Assert.NotNull(ConvertModelToArchetype(model));
}
[Test]
public void Convert_CompanyDetailsModel_To_Archetype()
{
var model = new CompanyDetails
{
Region = "Test Region",
ContactDetails = new ContactDetails
{
Address = "addr1",
Name = "email2"
}
};
Assert.NotNull(ConvertModelToArchetype(model));
}
#endregion
#region deserialization tests
[Test]
public void Convert_ArchetypeJson_To_ContactDetailsModel()
{
var result = ConvertArchetypeJsonToModel<ContactDetails>(JsonTestStrings._CONTACT_DETAILS_JSON);
Assert.NotNull(result);
Assert.IsInstanceOf<ContactDetails>(result);
Assert.AreEqual("Test Address", result.Address);
Assert.AreEqual("test@test.com", result.Email);
Assert.AreEqual("000", result.Fax);
Assert.AreEqual("000", result.Mobile);
Assert.AreEqual("Test", result.Name);
Assert.AreEqual("111", result.Telephone);
Assert.AreEqual(_webSite.Content, result.WebSite.Content);
Assert.AreEqual(_webSite.Media, result.WebSite.Media);
Assert.AreEqual(_webSite.OpenInNewWindow, result.WebSite.OpenInNewWindow);
Assert.AreEqual(_webSite.Title, result.WebSite.Title);
Assert.AreEqual(_webSite.Url, result.WebSite.Url);
}
[Test]
public void Convert_ArchetypeJson_To_AnnualStatementModel()
{
var result = ConvertArchetypeJsonToModel<AnnualStatement>(JsonTestStrings._ANNUAL_STATEMENT_JSON);
Assert.NotNull(result);
Assert.IsInstanceOf<AnnualStatement>(result);
Assert.AreEqual(new DateTime(2013, 9, 1), result.FiscalYearStart);
Assert.AreEqual(new DateTime(2014, 8, 31), result.FiscalYearEnd);
Assert.IsNull(result.DividendPaymentDate);
Assert.AreEqual(345678, result.TotalShares);
Assert.AreEqual(123456700.89, result.Sales);
Assert.AreEqual(1123456.78m, result.Profit);
}
[Test]
public void Convert_ArchetypeJson_To_AnnualStatementModel_NullableDate_HasValue()
{
var annualStatement = new AnnualStatement
{
FiscalYearStart = new DateTime(2013, 9, 1),
FiscalYearEnd = new DateTime(2014, 8, 31),
DividendPaymentDate = new DateTime(2014,9,15,10,15,30),
TotalShares = 345678,
Sales = 123456700.89,
Profit = 1123456.78m
};
var result = ConvertModelToArchetypeAndBack(annualStatement);
Assert.NotNull(result);
Assert.IsInstanceOf<AnnualStatement>(result);
Assert.AreEqual(annualStatement.FiscalYearStart, result.FiscalYearStart);
Assert.AreEqual(annualStatement.FiscalYearEnd, result.FiscalYearEnd);
Assert.AreEqual(annualStatement.DividendPaymentDate, result.DividendPaymentDate);
Assert.AreEqual(annualStatement.TotalShares, result.TotalShares);
Assert.AreEqual(annualStatement.Sales, result.Sales);
Assert.AreEqual(annualStatement.Profit, result.Profit);
}
[Test]
public void Convert_ArchetypeJson_To_AllContactDetailsModel()
{
var item1 = new ContactDetails
{
Address = "addr",
Email = "email"
};
var item2 = new ContactDetails
{
Address = "addr1",
Name = "email2"
};
var model = new AllContactDetails
{
UserDetails = item1,
AdminDetails = item2
};
var result = ConvertModelToArchetypeAndBack(model);
Assert.NotNull(result);
Assert.IsInstanceOf<AllContactDetails>(result);
Assert.AreEqual(item1.Address, result.UserDetails.Address);
Assert.AreEqual(item1.Email, result.UserDetails.Email);
Assert.AreEqual(item2.Address, result.AdminDetails.Address);
Assert.AreEqual(item2.Name, result.AdminDetails.Name);
}
[Test]
public void Convert_ArchetypeJson_To_ContactDetailsListModel()
{
var item1 = new ContactDetails
{
Address = "addr",
Email = "email"
};
var item2 = new ContactDetails
{
Address = "addr1",
Name = "email2"
};
var model = new ContactDetailsList
{
item1,
item2
};
var result = ConvertModelToArchetypeAndBack(model);
Assert.NotNull(result);
Assert.IsInstanceOf<ContactDetailsList>(result);
Assert.AreEqual(result.Count, 2);
Assert.AreEqual(item1.Address, result[0].Address);
Assert.AreEqual(item1.Email, result[0].Email);
Assert.AreEqual(item2.Address, result[1].Address);
Assert.AreEqual(item2.Name, result[1].Name);
}
[Test]
public void Convert_ArchetypeJson_To_CompanyDetailsModel()
{
var model = new CompanyDetails
{
Region = "Test Region",
ContactDetails = new ContactDetails
{
Address = "addr1",
Name = "email2"
}
};
var result = ConvertModelToArchetypeAndBack(model);
Assert.NotNull(result);
Assert.IsInstanceOf<CompanyDetails>(result);
Assert.AreEqual(model.Region, result.Region);
Assert.AreEqual(model.ContactDetails.Address, result.ContactDetails.Address);
Assert.AreEqual(model.ContactDetails.Name, result.ContactDetails.Name);
}
#endregion
}
}
@@ -0,0 +1,206 @@
using System.Linq;
using Archetype.Tests.Serialization.Base;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Archetype.Tests.Serialization.UseCases.Complex
{
[TestFixture]
public class ArchetypeJsonConverterTest : ArchetypeJsonConverterTestBase
{
private TextList _textList;
private Captions _captions;
private PageDetails _pageDetails;
private SlideShow _slides;
private Seo _seo;
private TextPageList _textPages;
private PageList _pages;
[SetUp]
public void SetUp()
{
InitComplexModels();
InitComplexTreeModel();
}
#region complex nested model tests
[Test]
public void Convert_PageDetailsModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_pageDetails, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._PAGE_DETAILS_JSON, result);
}
[Test]
public void Convert_PageDetailsModel_To_Archetype()
{
Assert.NotNull(ConvertModelToArchetype(_pageDetails));
}
[Test]
public void Convert_ArchetypeJson_To_PageDetailsModel()
{
var result = ConvertArchetypeJsonToModel<PageDetails>(JsonTestStrings._PAGE_DETAILS_JSON);
Assert.NotNull(result);
Assert.IsInstanceOf<PageDetails>(result);
Assert.AreEqual("2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453", result.Pages);
Assert.AreEqual("First Page", result.Captions.TextStringArray.ElementAt(0).TextString);
Assert.AreEqual("Second Page", result.Captions.TextStringArray.ElementAt(1).TextString);
Assert.AreEqual("Third Page", result.Captions.TextStringArray.ElementAt(2).TextString);
Assert.AreEqual("Fourth Page", result.Captions.TextStringArray.ElementAt(3).TextString);
}
[Test]
public void Convert_PageDetailsModel_To_Archetype_AndBack()
{
var result = ConvertModelToArchetypeAndBack(_pageDetails);
Assert.NotNull(result);
Assert.IsInstanceOf<PageDetails>(result);
Assert.AreEqual(_pageDetails.Pages, result.Pages);
Assert.AreEqual(_pageDetails.Captions.TextStringArray.ElementAt(0).TextString, result.Captions.TextStringArray.ElementAt(0).TextString);
Assert.AreEqual(_pageDetails.Captions.TextStringArray.ElementAt(1).TextString, result.Captions.TextStringArray.ElementAt(1).TextString);
Assert.AreEqual(_pageDetails.Captions.TextStringArray.ElementAt(2).TextString, result.Captions.TextStringArray.ElementAt(2).TextString);
Assert.AreEqual(_pageDetails.Captions.TextStringArray.ElementAt(3).TextString, result.Captions.TextStringArray.ElementAt(3).TextString);
}
#endregion
#region complex nested tree model tests
[Test]
public void Convert_PagesModel_To_Archetype()
{
Assert.NotNull(ConvertModelToArchetype(_pages));
}
[Test]
public void Convert_PagesModel_To_ArchetypeJson()
{
var result = ConvertModelToArchetypeJson(_pages, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._PAGES_JSON, result);
}
[Test]
public void Convert_ArchetypeJson_To_PagesModel()
{
var result = ConvertArchetypeJsonToModel<PageList>(JsonTestStrings._PAGES_JSON);
Assert.NotNull(result);
Assert.IsInstanceOf<PageList>(result);
Assert.AreEqual("1,2,3,4,5,6,7,8", result.Pages.ElementAt(0).Media.Slides);
Assert.AreEqual("1,2,3,4,5,6,7,8", result.Pages.ElementAt(1).Media.Slides);
Assert.AreEqual("1,2,3,4,5,6,7,8", result.Pages.ElementAt(2).Media.Slides);
Assert.AreEqual("Test Meta Title", result.Pages.ElementAt(0).Seo.MetaTitle);
Assert.AreEqual("Test Meta Description", result.Pages.ElementAt(0).Seo.MetaDescription);
Assert.AreEqual("Test Meta Title", result.Pages.ElementAt(1).Seo.MetaTitle);
Assert.AreEqual("Test Meta Description", result.Pages.ElementAt(1).Seo.MetaDescription);
Assert.AreEqual("Test Meta Title", result.Pages.ElementAt(2).Seo.MetaTitle);
Assert.AreEqual("Test Meta Description", result.Pages.ElementAt(2).Seo.MetaDescription);
Assert.AreEqual("Home Page Body", result.Pages.ElementAt(0).BodyText);
Assert.AreEqual("Home Page", result.Pages.ElementAt(0).PageTitle);
Assert.AreEqual("About us Page Body", result.Pages.ElementAt(1).BodyText);
Assert.AreEqual("About us Page", result.Pages.ElementAt(1).PageTitle);
Assert.AreEqual("Contact us Page Body", result.Pages.ElementAt(2).BodyText);
Assert.AreEqual("Contact us Page", result.Pages.ElementAt(2).PageTitle);
}
[Test]
public void Convert_PagesModel_To_Archetype_AndBack()
{
var result = ConvertModelToArchetypeAndBack(_pages);
Assert.NotNull(result);
Assert.IsInstanceOf<PageList>(result);
Assert.AreEqual(_pages.Pages.ElementAt(0).Media.Slides, result.Pages.ElementAt(0).Media.Slides);
Assert.AreEqual(_pages.Pages.ElementAt(1).Media.Slides, result.Pages.ElementAt(1).Media.Slides);
Assert.AreEqual(_pages.Pages.ElementAt(2).Media.Slides, result.Pages.ElementAt(2).Media.Slides);
Assert.AreEqual(_pages.Pages.ElementAt(0).Seo.MetaTitle, result.Pages.ElementAt(0).Seo.MetaTitle);
Assert.AreEqual(_pages.Pages.ElementAt(0).Seo.MetaDescription, result.Pages.ElementAt(0).Seo.MetaDescription);
Assert.AreEqual(_pages.Pages.ElementAt(1).Seo.MetaTitle, result.Pages.ElementAt(1).Seo.MetaTitle);
Assert.AreEqual(_pages.Pages.ElementAt(1).Seo.MetaDescription, result.Pages.ElementAt(1).Seo.MetaDescription);
Assert.AreEqual(_pages.Pages.ElementAt(2).Seo.MetaTitle, result.Pages.ElementAt(2).Seo.MetaTitle);
Assert.AreEqual(_pages.Pages.ElementAt(2).Seo.MetaDescription, result.Pages.ElementAt(2).Seo.MetaDescription);
Assert.AreEqual(_pages.Pages.ElementAt(0).BodyText, result.Pages.ElementAt(0).BodyText);
Assert.AreEqual(_pages.Pages.ElementAt(0).PageTitle, result.Pages.ElementAt(0).PageTitle);
Assert.AreEqual(_pages.Pages.ElementAt(1).BodyText, result.Pages.ElementAt(1).BodyText);
Assert.AreEqual(_pages.Pages.ElementAt(1).PageTitle, result.Pages.ElementAt(1).PageTitle);
Assert.AreEqual(_pages.Pages.ElementAt(2).BodyText, result.Pages.ElementAt(2).BodyText);
Assert.AreEqual(_pages.Pages.ElementAt(2).PageTitle, result.Pages.ElementAt(2).PageTitle);
}
#endregion
#region utility methods
private void InitComplexModels()
{
_textList = new TextList
{
new TextItem{TextString = "First Page"},
new TextItem{TextString = "Second Page"},
new TextItem{TextString = "Third Page"},
new TextItem{TextString = "Fourth Page"}
};
_captions = new Captions {TextStringArray = _textList};
_pageDetails = new PageDetails
{
Captions = _captions,
Pages = "2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453"
};
}
private void InitComplexTreeModel()
{
_slides = new SlideShow{Slides = "1,2,3,4,5,6,7,8"};
_seo = new Seo{MetaDescription = "Test Meta Description", MetaTitle = "Test Meta Title"};
_textPages = new TextPageList
{
new TextPage()
{
PageTitle = "Home Page",
BodyText = "Home Page Body",
Media = _slides,
Seo = _seo
},
new TextPage()
{
PageTitle = "About us Page",
BodyText = "About us Page Body",
Media = _slides,
Seo = _seo
},
new TextPage()
{
PageTitle = "Contact us Page",
BodyText = "Contact us Page Body",
Media = _slides,
Seo = _seo
}
};
_pages = new PageList
{
Pages = _textPages
};
}
#endregion
}
}
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using Archetype.Serialization;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.UseCases.Complex
{
#region complex nested model
[AsArchetype("pages")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class PageDetails
{
[JsonProperty("pages")]
public string Pages { get; set; }
[AsFieldset]
[JsonProperty("captions")]
public Captions Captions { get; set; }
}
[AsArchetype("captions")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Captions
{
[JsonProperty("captions")]
public TextList TextStringArray { get; set; }
}
[AsArchetype("textstringArray")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextList : List<TextItem>
{
}
[AsArchetype("textstringArray")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextItem
{
[JsonProperty("textstring")]
public string TextString { get; set; }
}
#endregion
#region complex nested tree model
public abstract class PageBase
{
public string PageTitle { get; set; }
public string BodyText { get; set; }
}
[AsArchetype("slideShow")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class SlideShow
{
public string Slides { get; set; }
}
[AsArchetype("seo")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Seo
{
public string MetaTitle { get; set; }
public string MetaDescription { get; set; }
}
[AsArchetype("TextPage")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextPage : PageBase
{
[AsFieldset]
public SlideShow Media { get; set; }
[AsFieldset]
public Seo Seo { get; set; }
}
[AsArchetype("TextPageList")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class TextPageList : List<TextPage>
{
}
[AsArchetype("Pages")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class PageList
{
public TextPageList Pages { get; set; }
}
#endregion
}
@@ -0,0 +1,245 @@
namespace Archetype.Tests.Serialization.UseCases.Complex
{
public class JsonTestStrings
{
public const string _PAGE_DETAILS_JSON =
@"{
""fieldsets"": [
{
""alias"": ""captions"",
""properties"": [
{
""alias"": ""captions"",
""value"": {
""fieldsets"": [
{
""alias"": ""captions"",
""properties"": [
{
""alias"": ""captions"",
""value"": {
""fieldsets"": [
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""First Page""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Second Page""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Third Page""
}
]
},
{
""alias"": ""textstringArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Fourth Page""
}
]
}
]
}
}
]
}
]
}
}
]
},
{
""alias"": ""pages"",
""properties"": [
{
""alias"": ""pages"",
""value"": ""2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453""
}
]
}
]
}";
public const string _PAGES_JSON =
@"{
""fieldsets"": [
{
""alias"": ""Pages"",
""properties"": [
{
""alias"": ""Pages"",
""value"": {
""fieldsets"": [
{
""alias"": ""TextPage"",
""properties"": [
{
""alias"": ""Media"",
""value"": {
""fieldsets"": [
{
""alias"": ""slideShow"",
""properties"": [
{
""alias"": ""Slides"",
""value"": ""1,2,3,4,5,6,7,8""
}
]
}
]
}
},
{
""alias"": ""Seo"",
""value"": {
""fieldsets"": [
{
""alias"": ""seo"",
""properties"": [
{
""alias"": ""MetaTitle"",
""value"": ""Test Meta Title""
},
{
""alias"": ""MetaDescription"",
""value"": ""Test Meta Description""
}
]
}
]
}
},
{
""alias"": ""PageTitle"",
""value"": ""Home Page""
},
{
""alias"": ""BodyText"",
""value"": ""Home Page Body""
}
]
},
{
""alias"": ""TextPage"",
""properties"": [
{
""alias"": ""Media"",
""value"": {
""fieldsets"": [
{
""alias"": ""slideShow"",
""properties"": [
{
""alias"": ""Slides"",
""value"": ""1,2,3,4,5,6,7,8""
}
]
}
]
}
},
{
""alias"": ""Seo"",
""value"": {
""fieldsets"": [
{
""alias"": ""seo"",
""properties"": [
{
""alias"": ""MetaTitle"",
""value"": ""Test Meta Title""
},
{
""alias"": ""MetaDescription"",
""value"": ""Test Meta Description""
}
]
}
]
}
},
{
""alias"": ""PageTitle"",
""value"": ""About us Page""
},
{
""alias"": ""BodyText"",
""value"": ""About us Page Body""
}
]
},
{
""alias"": ""TextPage"",
""properties"": [
{
""alias"": ""Media"",
""value"": {
""fieldsets"": [
{
""alias"": ""slideShow"",
""properties"": [
{
""alias"": ""Slides"",
""value"": ""1,2,3,4,5,6,7,8""
}
]
}
]
}
},
{
""alias"": ""Seo"",
""value"": {
""fieldsets"": [
{
""alias"": ""seo"",
""properties"": [
{
""alias"": ""MetaTitle"",
""value"": ""Test Meta Title""
},
{
""alias"": ""MetaDescription"",
""value"": ""Test Meta Description""
}
]
}
]
}
},
{
""alias"": ""PageTitle"",
""value"": ""Contact us Page""
},
{
""alias"": ""BodyText"",
""value"": ""Contact us Page Body""
}
]
}
]
}
}
]
}
]
}";
}
}
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.Models;
using Archetype.PropertyConverters;
using Archetype.Tests.Serialization.Base;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Archetype.Tests.Serialization.UseCases.Enumerable
{
[TestFixture]
public class ArchetypeJsonConverterTest : ArchetypeJsonConverterTestBase
{
private Feedbacks _feedbacks;
private Captions _captions;
[SetUp]
public void SetUp()
{
_feedbacks = new Feedbacks
{
new Feedback{Testimonial = "Testimonial 1"},
new Feedback{Testimonial = "Testimonial 2"},
new Feedback{Testimonial = "Testimonial 3"}
};
_captions = new Captions()
{
TextArray = new List<Text>
{
new Text{TextString = "Caption 1"},
new Text{TextString = "Caption 2"},
new Text{TextString = "Caption 3"}
}
};
}
#region Feedback - single fieldset with multiple items
[Test]
public void Convert_FeedbackModel_To_Archetype()
{
var converter = new ArchetypeValueConverter();
var json = JsonConvert.SerializeObject(_feedbacks);
var archetype = (ArchetypeModel)converter.ConvertDataToSource(null, json, false);
Assert.NotNull(archetype);
}
[Test]
public void Convert_FeedbackModel_To_ArchetypeJson()
{
var json = ConvertModelToArchetypeJson(_feedbacks, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._FEEDBACK_JSON, json);
}
[Test]
public void Convert_ArchetypeJson_To_FeedbackModel()
{
var result = ConvertArchetypeJsonToModel<Feedbacks>(JsonTestStrings._FEEDBACK_JSON);
Assert.NotNull(result);
Assert.AreEqual(3, result.Count);
foreach (var feedback in result)
{
Assert.AreEqual(String.Format("Testimonial {0}", result.IndexOf(feedback) + 1),
feedback.Testimonial);
}
}
[Test]
public void Convert_FeedbackModel_To_Archetype_AndBack()
{
var result = ConvertModelToArchetypeAndBack(_feedbacks);
Assert.NotNull(result);
Assert.AreEqual(3, result.Count);
foreach (var feedback in result)
{
var index = result.IndexOf(feedback);
Assert.AreEqual(_feedbacks.ElementAt(index).Testimonial, feedback.Testimonial);
}
}
#endregion
#region Captions - a root fieldset which contains another fieldset list
[Test]
public void Convert_CaptionsModel_To_Archetype()
{
var converter = new ArchetypeValueConverter();
var json = JsonConvert.SerializeObject(_captions);
var archetype = (ArchetypeModel)converter.ConvertDataToSource(null, json, false);
Assert.NotNull(archetype);
}
[Test]
public void Convert_CaptionsModel_To_ArchetypeJson()
{
var json = ConvertModelToArchetypeJson(_captions, Formatting.Indented);
Assert.AreEqual(JsonTestStrings._CAPTIONS_JSON, json);
}
[Test]
public void Convert_ArchetypeJson_To_CaptionsModel()
{
var result = ConvertArchetypeJsonToModel<Captions>(JsonTestStrings._CAPTIONS_JSON);
Assert.NotNull(result);
Assert.NotNull(result.TextArray);
Assert.AreEqual(3, result.TextArray.Count);
foreach (var caption in result.TextArray)
{
Assert.AreEqual(String.Format("Caption {0}", result.TextArray.IndexOf(caption) + 1),
caption.TextString);
}
}
[Test]
public void Convert_CaptionsModel_To_Archetype_AndBack()
{
var result = ConvertModelToArchetypeAndBack(_captions);
Assert.NotNull(result);
Assert.NotNull(result.TextArray);
Assert.AreEqual(3, result.TextArray.Count);
foreach (var caption in result.TextArray)
{
var index = result.TextArray.IndexOf(caption);
Assert.AreEqual(_captions.TextArray.ElementAt(index).TextString, caption.TextString);
}
}
#endregion
}
}
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using Archetype.Serialization;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.UseCases.Enumerable
{
#region Feedback - single fieldset with multiple items
[AsArchetype("feedbacks")] /* Note: when inheriting from a list, archetype alias is not used */
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Feedbacks : List<Feedback>
{
}
[AsArchetype("feedback")] /* Must have same archetype alias as list class */
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Feedback
{
[JsonProperty("testimonial")]
public String Testimonial { get; set; }
}
#endregion
#region Captions - a root fieldset which contains another fieldset list
[AsArchetype("captions")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Captions
{
[JsonProperty("captions")]
public List<Text> TextArray { get; set; }
}
[AsArchetype("textArray")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class Text
{
[JsonProperty("textstring")]
public String TextString { get; set; }
}
#endregion
[AsArchetype("aboutUs")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class AboutUs
{
[AsFieldset]
[JsonProperty("contacts")]
public IEnumerable<ContactDetails> Contacts { get; set; }
[AsFieldset]
[JsonProperty("testimonials")]
public IEnumerable<String> Testimonials { get; set; }
}
}
@@ -0,0 +1,86 @@
namespace Archetype.Tests.Serialization.UseCases.Enumerable
{
public class JsonTestStrings
{
public const string _ABOUT_US_JSON =
@"";
public const string _FEEDBACK_JSON =
@"{
""fieldsets"": [
{
""alias"": ""feedback"",
""properties"": [
{
""alias"": ""testimonial"",
""value"": ""Testimonial 1""
}
]
},
{
""alias"": ""feedback"",
""properties"": [
{
""alias"": ""testimonial"",
""value"": ""Testimonial 2""
}
]
},
{
""alias"": ""feedback"",
""properties"": [
{
""alias"": ""testimonial"",
""value"": ""Testimonial 3""
}
]
}
]
}";
public const string _CAPTIONS_JSON =
@"{
""fieldsets"": [
{
""alias"": ""captions"",
""properties"": [
{
""alias"": ""captions"",
""value"": {
""fieldsets"": [
{
""alias"": ""textArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Caption 1""
}
]
},
{
""alias"": ""textArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Caption 2""
}
]
},
{
""alias"": ""textArray"",
""properties"": [
{
""alias"": ""textstring"",
""value"": ""Caption 3""
}
]
}
]
}
}
]
}
]
}";
}
}
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using Archetype.Serialization;
using Newtonsoft.Json;
namespace Archetype.Tests.Serialization.UseCases
{
#region json test Models
[AsArchetype("urlPicker")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class UrlPicker
{
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("content")]
public int? Content { get; set; }
[JsonProperty("media")]
public int? Media { get; set; }
[JsonProperty("openInNewWindow")]
public bool OpenInNewWindow { get; set; }
}
[AsArchetype("contactDetails")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class ContactDetails
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("telephone")]
public string Telephone { get; set; }
[JsonProperty("mobile")]
public string Mobile { get; set; }
[JsonProperty("fax")]
public string Fax { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("webSite")]
public UrlPicker WebSite { get; set; }
}
[AsArchetype("companyDetails")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class CompanyDetails
{
[JsonProperty("region")]
public string Region { get; set; }
[JsonProperty("contactDetails")]
public ContactDetails ContactDetails { get; set; }
}
[AsArchetype("allContactDetails")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class AllContactDetails
{
public ContactDetails UserDetails { get; set; }
public ContactDetails AdminDetails { get; set; }
}
[AsArchetype("contactDetailsList")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class ContactDetailsList : List<ContactDetails>
{
}
[AsArchetype("annualStatement")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class AnnualStatement
{
public DateTime FiscalYearStart { get; set; }
public DateTime FiscalYearEnd { get; set; }
public DateTime? DividendPaymentDate { get; set; }
public Int32 TotalShares { get; set; }
public Double Sales { get; set; }
public Decimal Profit { get; set; }
}
[AsArchetype("mergerDetails")]
[JsonConverter(typeof(ArchetypeJsonConverter))]
public class MergerDetails
{
public DateTime MergerDate { get; set; }
[JsonIgnore]
public decimal MergerValue { get; set; }
}
#endregion
}
@@ -0,0 +1,207 @@
namespace Archetype.Tests.Serialization.UseCases
{
public class JsonTestStrings
{
public const string _MERGER_DETAILS_JSON =
@"{
""fieldsets"": [
{
""alias"": ""mergerDetails"",
""properties"": [
{
""alias"": ""MergerDate"",
""value"": ""07/06/2014 08:09:10""
}
]
}
]
}";
public const string _ANNUAL_STATEMENT_JSON =
@"{
""fieldsets"": [
{
""alias"": ""annualStatement"",
""properties"": [
{
""alias"": ""FiscalYearStart"",
""value"": ""01/09/2013 00:00:00""
},
{
""alias"": ""FiscalYearEnd"",
""value"": ""31/08/2014 00:00:00""
},
{
""alias"": ""DividendPaymentDate"",
""value"": """"
},
{
""alias"": ""TotalShares"",
""value"": ""345678""
},
{
""alias"": ""Sales"",
""value"": ""123456700.89""
},
{
""alias"": ""Profit"",
""value"": ""1123456.78""
}
]
}
]
}";
public const string _CONTACT_DETAILS_JSON =
@"{
""fieldsets"": [
{
""alias"": ""contactDetails"",
""properties"": [
{
""alias"": ""name"",
""value"": ""Test""
},
{
""alias"": ""address"",
""value"": ""Test Address""
},
{
""alias"": ""telephone"",
""value"": ""111""
},
{
""alias"": ""mobile"",
""value"": ""000""
},
{
""alias"": ""fax"",
""value"": ""000""
},
{
""alias"": ""email"",
""value"": ""test@test.com""
},
{
""alias"": ""webSite"",
""value"": {
""fieldsets"": [
{
""alias"": ""urlPicker"",
""properties"": [
{
""alias"": ""title"",
""value"": ""The Test Company""
},
{
""alias"": ""url"",
""value"": ""http://test.com""
},
{
""alias"": ""content"",
""value"": """"
},
{
""alias"": ""media"",
""value"": """"
},
{
""alias"": ""openInNewWindow"",
""value"": ""1""
}
]
}
]
}
}
]
}
]
}";
public const string _COMPANY_DETAILS_JSON =
@"{
""fieldsets"": [
{
""alias"": ""companyDetails"",
""properties"": [
{
""alias"": ""region"",
""value"": ""Test Region""
},
{
""alias"": ""contactDetails"",
""value"": {
""fieldsets"": [
{
""alias"": ""contactDetails"",
""properties"": [
{
""alias"": ""name"",
""value"": ""Test""
},
{
""alias"": ""address"",
""value"": ""Test Address""
},
{
""alias"": ""telephone"",
""value"": ""111""
},
{
""alias"": ""mobile"",
""value"": ""000""
},
{
""alias"": ""fax"",
""value"": ""000""
},
{
""alias"": ""email"",
""value"": ""test@test.com""
},
{
""alias"": ""webSite"",
""value"": {
""fieldsets"": [
{
""alias"": ""urlPicker"",
""properties"": [
{
""alias"": ""title"",
""value"": ""The Test Company""
},
{
""alias"": ""url"",
""value"": ""http://test.com""
},
{
""alias"": ""content"",
""value"": """"
},
{
""alias"": ""media"",
""value"": """"
},
{
""alias"": ""openInNewWindow"",
""value"": ""1""
}
]
}
]
}
}
]
}
]
}
}
]
}
]
}";
public const string _NULL_VALUES_JSON = @"{""fieldsets"":[{""alias"":""contactDetails"",""properties"":[{""alias"":""name"",""value"":""""},{""alias"":""address"",""value"":""""},{""alias"":""telephone"",""value"":""""},{""alias"":""mobile"",""value"":""""},{""alias"":""fax"",""value"":""""},{""alias"":""email"",""value"":""""},{""alias"":""webSite"",""value"":""""}]}]}";
}
}
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<!--
umbracoSettings.config configuration documentation can be found here:
http://our.umbraco.org/documentation/using-umbraco/config-files/umbracoSettings/
Many of the optional settings are not explicitly listed here
but can be found in the online documentation.
-->
<content>
<errors>
<error404>1</error404>
<!--
<error404>
<errorPage culture="default">1</errorPage>
<errorPage culture="en-US">200</errorPage>
</error404>
-->
</errors>
<notifications>
<!-- the email that should be used as from mail when umbraco sends a notification -->
<email>your@email.here</email>
</notifications>
<!-- Show property descriptions in editing view "icon|text|none" -->
<PropertyContextHelpOption>text</PropertyContextHelpOption>
<!-- The html injected into a (x)html page if Umbraco is running in preview mode -->
<PreviewBadge><![CDATA[<a id="umbracoPreviewBadge" style="position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{1}/preview/previewModeBadge.png') no-repeat;" href="{0}/endPreview.aspx?redir={2}"><span style="display:none;">In Preview Mode - click to end</span></a>]]></PreviewBadge>
<!-- Cache cycle of Media and Member data fetched from the umbraco.library methods -->
<!-- In seconds. 0 will disable cache -->
<UmbracoLibraryCacheDuration>1800</UmbracoLibraryCacheDuration>
<!-- How Umbraco should handle errors during macro execution. Can be one of the following values:
- inline - show an inline error within the macro but allow the page to continue rendering. Historial Umbraco behaviour.
- silent - Silently suppress the error and do not render the offending macro.
- throw - Throw an exception which can be caught by the global error handler defined in Application_OnError. If no such
error handler is defined then you'll see the Yellow Screen Of Death (YSOD) error page.
Note the error can also be handled by the umbraco.macro.Error event, where you can log/alarm with your own code and change the behaviour per event. -->
<MacroErrors>inline</MacroErrors>
<!-- These file types will not be allowed to be uploaded via the upload control for media and content -->
<disallowedUploadFiles>ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd</disallowedUploadFiles>
<!-- Defines the default document type property used when adding properties in the back-office (if missing or empty, defaults to Textstring -->
<defaultDocumentTypeProperty>Textstring</defaultDocumentTypeProperty>
</content>
<security>
<!-- set to true to auto update login interval (and there by disabling the lock screen -->
<keepUserLoggedIn>false</keepUserLoggedIn>
<!-- change in 4.8: Disabled users are now showed dimmed and last in the tree. If you prefer not to display them set this to true -->
<hideDisabledUsersInBackoffice>false</hideDisabledUsersInBackoffice>
</security>
<requestHandler>
<!-- this will ensure that urls are unique when running with multiple root nodes -->
<useDomainPrefixes>false</useDomainPrefixes>
<!-- this will add a trailing slash (/) to urls when in directory url mode -->
<addTrailingSlash>true</addTrailingSlash>
</requestHandler>
<templates>
<!-- To switch the default rendering engine to MVC, change this value from WebForms to Mvc -->
<defaultRenderingEngine>Mvc</defaultRenderingEngine>
</templates>
<scheduledTasks>
<!-- add tasks that should be called with an interval (seconds) -->
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
</scheduledTasks>
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
<distributedCall enable="false">
<!-- the id of the user who's making the calls -->
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
<user>0</user>
<servers>
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
<!-- <server>127.0.0.1</server>-->
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
</servers>
</distributedCall>
<!--
web.routing
@trySkipIisCustomErrors
Tries to skip IIS custom errors.
Starting with IIS 7.5, this must be set to true for Umbraco 404 pages to show. Else, IIS will take
over and render its build-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors.
The default value is false, for backward compatibility reasons, which means that IIS _will_ take
over, and _prevent_ Umbraco 404 pages to show.
@internalRedirectPreservesTemplate
By default as soon as we're not displaying the initial document, we reset the template set by the
finder or by the alt. template. Set this option to true to preserve the template set by the finder
or by the alt. template, in case of an internal redirect.
(false by default, and in fact should remain false unless you know what you're doing)
-->
<web.routing
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false">
</web.routing>
</settings>
+9 -1
View File
@@ -1,6 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<configSections>
<sectionGroup name="umbracoConfiguration">
<section name="settings" type="Umbraco.Core.Configuration.UmbracoSettings.UmbracoSettingsSection, Umbraco.Core" requirePermission="false" />
</sectionGroup>
</configSections>
<umbracoConfiguration>
<settings configSource="config\umbracoSettings.config" />
</umbracoConfiguration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -42,6 +42,7 @@
</Reference>
<Reference Include="businesslogic">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\businesslogic.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ClientDependency.Core">
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
@@ -53,9 +54,11 @@
</Reference>
<Reference Include="cms">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\cms.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="controls">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\controls.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="CookComputing.XmlRpcV2">
<HintPath>..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll</HintPath>
@@ -63,6 +66,7 @@
</Reference>
<Reference Include="Examine">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\Examine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="HtmlAgilityPack">
<HintPath>..\packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll</HintPath>
@@ -74,15 +78,19 @@
</Reference>
<Reference Include="ImageProcessor">
<HintPath>..\packages\ImageProcessor.1.9.0.0\lib\ImageProcessor.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImageProcessor.Web">
<HintPath>..\packages\ImageProcessor.Web.3.2.3.0\lib\net45\ImageProcessor.Web.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="interfaces">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\interfaces.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="log4net">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\log4net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lucene.Net">
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
@@ -90,9 +98,11 @@
</Reference>
<Reference Include="Microsoft.ApplicationBlocks.Data">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\Microsoft.ApplicationBlocks.Data.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Web.Helpers">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\Microsoft.Web.Helpers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>False</Private>
@@ -116,15 +126,16 @@
</Reference>
<Reference Include="SQLCE4Umbraco">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\SQLCE4Umbraco.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<Private>False</Private>
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<Private>False</Private>
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
@@ -173,36 +184,47 @@
<Reference Include="System.Xml" />
<Reference Include="TidyNet">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\TidyNet.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Umbraco.Core">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\Umbraco.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco.DataLayer">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.DataLayer.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco.editorControls">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.editorControls.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco.MacroEngines">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.MacroEngines.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco.providers">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.providers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Umbraco.Web.UI">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\Umbraco.Web.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="umbraco.XmlSerializers">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\umbraco.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UmbracoExamine">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\UmbracoExamine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UrlRewritingNet.UrlRewriter">
<HintPath>..\packages\UmbracoCms.Core.7.1.2\lib\UrlRewritingNet.UrlRewriter.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -220,7 +242,12 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\VersionInfo.cs" />
<Compile Include="PropertyConverters\ArchetypeValueConverter.cs" />
<Compile Include="Serialization\ArchetypeJsonDelinter.cs" />
<Compile Include="Serialization\AsArchetypeAttribute.cs" />
<Compile Include="PropertyEditors\ArchetypePropertyEditor.cs" />
<Compile Include="Serialization\ArchetypeJsonConverter.cs" />
<Compile Include="Serialization\AsFieldsetAttribute.cs" />
<Compile Include="Serialization\SerializationExtensions.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@@ -4,14 +4,18 @@ using Archetype.Models;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
namespace Archetype.Extensions
{
public class ArchetypeHelper
{
protected JsonSerializerSettings _jsonSettings;
protected ApplicationContext _app;
protected ApplicationContext _app;
private static readonly ArchetypeHelper _instance = new ArchetypeHelper();
internal static ArchetypeHelper Instance { get { return _instance; } }
internal ArchetypeHelper()
{
@@ -43,10 +47,10 @@ namespace Archetype.Extensions
catch
{
return new Models.ArchetypeModel();
}
}
}
internal Models.ArchetypeModel DeserializeJsonToArchetype(string sourceJson, int dataTypeId)
internal Models.ArchetypeModel DeserializeJsonToArchetype(string sourceJson, int dataTypeId, PublishedContentType hostContentType = null)
{
try
{
@@ -56,7 +60,7 @@ namespace Archetype.Extensions
{
// Get list of configured properties and their types and map them to the deserialized archetype model
var preValue = GetArchetypePreValueFromDataTypeId(dataTypeId);
RetrieveAdditionalProperties(ref archetype, preValue);
RetrieveAdditionalProperties(ref archetype, preValue, hostContentType);
}
catch (Exception ex)
{
@@ -70,8 +74,8 @@ namespace Archetype.Extensions
}
}
private ArchetypePreValue GetArchetypePreValueFromDataTypeId(int dataTypeId)
{
private ArchetypePreValue GetArchetypePreValueFromDataTypeId(int dataTypeId)
{
return _app.ApplicationCache.RuntimeCache.GetCacheItem(
Constants.CacheKey_PreValueFromDataTypeId + dataTypeId,
() =>
@@ -88,7 +92,7 @@ namespace Archetype.Extensions
return config;
}) as ArchetypePreValue;
}
}
private ArchetypePreValue GetArchetypePreValueFromPreValuesCollection(PreValueCollection dataTypePreValues)
{
@@ -97,10 +101,9 @@ namespace Archetype.Extensions
return preValue;
}
private IDataTypeDefinition GetDataTypeByGuid(Guid guid)
{
return (IDataTypeDefinition) ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
return (IDataTypeDefinition)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
Constants.CacheKey_DataTypeByGuid + guid,
() => _app.Services.DataTypeService.GetDataTypeDefinitionById(guid));
}
@@ -110,7 +113,7 @@ namespace Archetype.Extensions
/// </summary>
/// <param name="archetype">The Archetype to add the additional metadata to</param>
/// <param name="preValue">The configuration of the Archetype</param>
private void RetrieveAdditionalProperties(ref Models.ArchetypeModel archetype, ArchetypePreValue preValue)
private void RetrieveAdditionalProperties(ref Models.ArchetypeModel archetype, ArchetypePreValue preValue, PublishedContentType hostContentType = null)
{
foreach (var fieldset in preValue.Fieldsets)
{
@@ -120,11 +123,12 @@ namespace Archetype.Extensions
foreach (var property in fieldset.Properties)
{
var propertyAlias = property.Alias;
foreach ( var propertyInst in fieldsetInst.Properties.Where(x => x.Alias == propertyAlias))
foreach (var propertyInst in fieldsetInst.Properties.Where(x => x.Alias == propertyAlias))
{
propertyInst.DataTypeGuid = property.DataTypeGuid.ToString();
propertyInst.DataTypeId = GetDataTypeByGuid(property.DataTypeGuid).Id;
propertyInst.PropertyEditorAlias = property.PropertyEditorAlias;
propertyInst.HostContentType = hostContentType;
}
}
}
@@ -146,6 +150,5 @@ namespace Archetype.Extensions
}
}
}
}
}
}
@@ -1,9 +1,12 @@
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Web;
using Archetype.Models;
@@ -23,5 +26,59 @@ namespace Archetype.Extensions
{
return prop.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditorAlias);
}
public static IHtmlString RenderArchetypePartials(this HtmlHelper htmlHelper, ArchetypeModel archetypeModel)
{
return RenderPartials(htmlHelper, archetypeModel, null, null);
}
public static IHtmlString RenderArchetypePartials(this HtmlHelper htmlHelper, ArchetypeModel archetypeModel, string partialPath)
{
return RenderPartials(htmlHelper, archetypeModel, partialPath, null);
}
public static IHtmlString RenderArchetypePartials(this HtmlHelper htmlHelper, ArchetypeModel archetypeModel, ViewDataDictionary viewDataDictionary)
{
return RenderPartials(htmlHelper, archetypeModel, null, viewDataDictionary);
}
public static IHtmlString RenderArchetypePartials(this HtmlHelper htmlHelper, ArchetypeModel archetypeModel, string partialPath, ViewDataDictionary viewDataDictionary)
{
return RenderPartials(htmlHelper, archetypeModel, partialPath, viewDataDictionary);
}
private static IHtmlString RenderPartials(HtmlHelper htmlHelper, ArchetypeModel archetypeModel, string partialPath, ViewDataDictionary viewDataDictionary)
{
var context = HttpContext.Current;
if (archetypeModel == null || context == null)
{
return new HtmlString("");
}
var sb = new StringBuilder();
var pathToPartials = "~/Views/Partials/Archetype/";
if (!string.IsNullOrEmpty(partialPath))
{
pathToPartials = partialPath;
}
foreach (var fieldsetModel in archetypeModel)
{
var partial = pathToPartials + fieldsetModel.Alias + ".cshtml";
if (System.IO.File.Exists(context.Server.MapPath(partial)))
{
sb.AppendLine(htmlHelper.Partial(partial, fieldsetModel, viewDataDictionary).ToString());
}
else
{
LogHelper.Info<ArchetypeModel>(string.Format("The partial for {0} could not be found. Please create a partial with that name or rename your alias.", context.Server.MapPath(partial)));
}
}
return new HtmlString(sb.ToString());
}
}
}
@@ -10,6 +10,9 @@ namespace Archetype.Models
[JsonProperty("alias")]
public string Alias { get; set; }
[JsonProperty("disabled")]
public bool Disabled { get; set; }
[JsonProperty("properties")]
public IEnumerable<ArchetypePropertyModel> Properties;
@@ -29,12 +32,36 @@ namespace Archetype.Models
{
var property = GetProperty(propertyAlias);
if (property == null || string.IsNullOrEmpty(property.Value.ToString()))
if (IsEmptyProperty(property))
{
return default(T);
}
return property.GetValue<T>();
}
// issue 142: support default T value supplied by caller
// this code would look nicer if the two GetValue<T>() methods had one common implementation.
// however, this would require GetValue<T>(string propertyAlias) to call the common implementation
// with a default(T) value, which could in theory result in a performance hit, if T for some reason
// is costly to instantiate.
public T GetValue<T>(string propertyAlias, T defaultValue)
{
var property = GetProperty(propertyAlias);
if (IsEmptyProperty(property))
{
return defaultValue;
}
return property.GetValue<T>();
}
private bool IsEmptyProperty(ArchetypePropertyModel property)
{
return (property == null || property.Value == null || string.IsNullOrEmpty(property.Value.ToString()));
}
public bool HasProperty(string propertyAlias)
{
return GetProperty(propertyAlias) != null;
@@ -43,7 +70,7 @@ namespace Archetype.Models
public bool HasValue(string propertyAlias)
{
var property = GetProperty(propertyAlias);
if (property == null)
if (property == null || property.Value == null)
return false;
return !string.IsNullOrEmpty(property.Value.ToString());
@@ -57,4 +84,4 @@ namespace Archetype.Models
#endregion
}
}
}
@@ -1,7 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using Archetype.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -20,7 +21,7 @@ namespace Archetype.Models
public IEnumerator<ArchetypeFieldsetModel> GetEnumerator()
{
return this.Fieldsets.GetEnumerator();
return this.Fieldsets.Where(f => f.Disabled == false).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
@@ -31,14 +32,14 @@ namespace Archetype.Models
internal string SerializeForPersistence()
{
var json = JObject.Parse(JsonConvert.SerializeObject(this));
var propertiesToRemove = new String[] { "propertyEditorAlias", "dataTypeId", "dataTypeGuid" };
var propertiesToRemove = new String[] { "propertyEditorAlias", "dataTypeId", "dataTypeGuid", "hostContentType" };
json.Descendants().OfType<JProperty>()
.Where(p => propertiesToRemove.Contains(p.Name))
.ToList()
.ForEach(x => x.Remove());
return json.ToString(Formatting.None);
return json.ToString(Formatting.None).DelintArchetypeJson();
}
}
}
@@ -20,6 +20,9 @@ namespace Archetype.Models
[JsonProperty("enableCollapsing")]
public bool EnableCollapsing { get; set; }
[JsonProperty("enableDisabling")]
public bool EnableDisabling { get; set; }
[JsonProperty("hideFieldsetControls")]
public bool HideFieldsetControls { get; set; }
@@ -44,6 +47,9 @@ namespace Archetype.Models
[JsonProperty("customJsPath")]
public string CustomJsPath { get; set; }
[JsonProperty("customViewPath")]
public string CustomViewPath { get; set; }
[JsonProperty("developerMode")]
public bool DeveloperMode { get; set; }
}
@@ -33,6 +33,6 @@ namespace Archetype.Models
public bool Required { get; set; }
[JsonProperty("regEx")]
public bool RegEx { get; set; }
public string RegEx { get; set; }
}
}
@@ -25,6 +25,9 @@ namespace Archetype.Models
[JsonProperty("dataTypeGuid")]
internal string DataTypeGuid { get; set; }
[JsonProperty("hostContentType")]
internal PublishedContentType HostContentType { get; set; }
public T GetValue<T>()
{
@@ -80,7 +83,7 @@ namespace Archetype.Models
private PublishedPropertyType CreateDummyPropertyType(int dataTypeId, string propertyEditorAlias)
{
return new PublishedPropertyType(null,
return new PublishedPropertyType(this.HostContentType,
new PropertyType(new DataTypeDefinition(-1, propertyEditorAlias)
{
Id = dataTypeId
@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
[assembly: AssemblyVersion("1.0")]
[assembly: AssemblyFileVersion("1.0-beta")]
[assembly: AssemblyVersion("1.3.0")]
[assembly: AssemblyFileVersion("1.3.0-beta")]
@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.Extensions;
using Archetype.Extensions;
using Archetype.Models;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Archetype.PropertyConverters
namespace Archetype.PropertyConverters
{
[PropertyValueType(typeof(Models.ArchetypeModel))]
[PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)]
@@ -24,12 +20,13 @@ namespace Archetype.PropertyConverters
public override bool IsConverter(PublishedPropertyType propertyType)
{
return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditorAlias);
return !String.IsNullOrEmpty(propertyType.PropertyEditorAlias)
&& propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditorAlias);
}
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
{
var defaultValue = new Models.ArchetypeModel();
var defaultValue = new ArchetypeModel();
if (source == null)
return defaultValue;
@@ -39,10 +36,14 @@ namespace Archetype.PropertyConverters
if (!sourceString.DetectIsJson())
return defaultValue;
var archetype = new ArchetypeHelper().DeserializeJsonToArchetype(source.ToString(),
(propertyType != null ? propertyType.DataTypeId : -1));
using (var timer = DisposableTimer.DebugDuration<ArchetypeValueConverter>(string.Format("ConvertDataToSource ({0})", propertyType != null ? propertyType.PropertyTypeAlias : "null")))
{
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(sourceString,
(propertyType != null ? propertyType.DataTypeId : -1),
(propertyType != null ? propertyType.ContentType : null));
return archetype;
return archetype;
}
}
}
}
@@ -1,23 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.Extensions;
using ClientDependency.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Logging;
namespace Archetype.PropertyEditors
{
[PropertyEditorAsset(ClientDependencyType.Javascript, "/App_Plugins/Archetype/js/archetype.js")]
[PropertyEditor("Imulus.Archetype", "Archetype", "/App_Plugins/Archetype/views/archetype.html",
ValueType = "JSON")]
[PropertyEditor("Imulus.Archetype", "Archetype", "/App_Plugins/Archetype/views/archetype.html", ValueType = "JSON")]
public class ArchetypePropertyEditor : PropertyEditor
{
#region Pre Value Editor
@@ -59,7 +56,7 @@ namespace Archetype.PropertyEditors
if (property.Value == null || property.Value.ToString() == "")
return string.Empty;
var archetype = new ArchetypeHelper().DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
@@ -88,7 +85,7 @@ namespace Archetype.PropertyEditors
if (property.Value == null || property.Value.ToString() == "")
return string.Empty;
var archetype = new ArchetypeHelper().DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
@@ -116,7 +113,7 @@ namespace Archetype.PropertyEditors
if (editorValue.Value == null || editorValue.Value.ToString() == "")
return string.Empty;
var archetype = new ArchetypeHelper().DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);
foreach (var fieldset in archetype.Fieldsets)
{
@@ -126,8 +123,8 @@ namespace Archetype.PropertyEditors
{
var dtd = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(Guid.Parse(propDef.DataTypeGuid));
var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);
var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary<string, object>());
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary<string, object>());
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, propDef.Value);
}
catch (Exception ex)
@@ -138,7 +135,17 @@ namespace Archetype.PropertyEditors
}
return archetype.SerializeForPersistence();
}
}
internal virtual PropertyEditor GetPropertyEditor(IDataTypeDefinition dtd)
{
if (dtd.Id != 0)
return PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
return dtd.PropertyEditorAlias.Equals(Constants.PropertyEditorAlias)
? new ArchetypePropertyEditor()
: (PropertyEditor)new TextboxPropertyEditor();
}
}
#endregion
@@ -0,0 +1,585 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Archetype.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Archetype.Serialization
{
public class ArchetypeJsonConverter : JsonConverter
{
private const string _ROOT_FS_ALIAS = "rootFs";
#region public methods
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var models = GenerateModels(value);
if (models.Count < 1)
return;
if (models.Count == 1 && models[0] == null)
return;
var jObj = SerializeModelToFieldset(models);
writer.WriteRaw(ApplyFormatting(jObj.ToString(), writer.Formatting));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jToken = JToken.ReadFrom(reader);
if (jToken == null)
return null;
return DeserializeJson(objectType, GetDelintedJToken(jToken));
}
public override bool CanConvert(Type objectType)
{
return IsTypeArchetypeDatatype(objectType);
}
#endregion
#region private methods - deserialization
private JToken GetDelintedJToken(JToken jToken)
{
return JToken.Parse(jToken.ToString(Formatting.None).DelintArchetypeJson());
}
private object DeserializeJson(Type objectType, JToken jToken)
{
var obj = Activator.CreateInstance(objectType);
if (null != obj as IEnumerable<object>)
{
JToken enumerableToken;
if (TryParseJTokenAsEnumerable(jToken, out enumerableToken)
|| (jToken.SelectToken("value") != null && TryParseJTokenAsEnumerable(jToken["value"], out enumerableToken)))
return DeserializeEnumerableObject(obj, enumerableToken);
}
return null == jToken as JArray
? DeserializeObject(obj, jToken)
: PopulateProperties(obj, jToken);
}
private object DeserializeEnumerableObject(object obj, JToken jToken)
{
var model = obj as IEnumerable<object>;
var fsToken = jToken;
var itemType = model.GetType().GetGenericArguments().FirstOrDefault();
if (itemType == null)
{
itemType = model.GetType().BaseType.GetGenericArguments().First();
fsToken = fsToken["fieldsets"];
}
foreach (var fs in fsToken.Where(fs => fs["alias"].ToString().Equals(GetFieldsetName(itemType))))
{
var item = DeserializeJson(itemType, fs["properties"]);
obj.GetType().GetMethod("Add").Invoke(obj, new[] { item });
}
return obj;
}
private object DeserializeObject(object obj, JToken jToken)
{
if (jToken.SelectToken("value") != null
&& String.IsNullOrEmpty(jToken.SelectToken("value").ToString()))
return obj;
var properties = GetSerialiazableProperties(obj).ToList();
var asFieldset = properties.Where(HasAsFieldsetAttribute).ToList();
foreach (var propInfo in asFieldset)
{
var propAlias = GetJsonPropertyName(propInfo);
var fsJToken = propInfo.PropertyType.Namespace.Equals("System")
? GetFieldsetJTokenFromAlias(propAlias, jToken)
: GetFieldsetJTokenFromPropertyAlias(propInfo, jToken);
var propJToken = GetPropertyJToken(fsJToken, propAlias);
if (propJToken == null)
continue;
var propValue = GetPropertyValue(propInfo, propJToken);
propInfo.SetValue(obj, propValue);
}
if (properties.All(HasAsFieldsetAttribute))
return obj;
var objToken = GetFieldsetJTokenFromTypeAlias(obj.GetType(), jToken);
if (objToken == null)
return obj;
if (objToken.SelectToken("properties") != null)
return PopulateProperties(obj, objToken["properties"]);
if ((objToken is JArray) && (objToken as JArray).Count <= 0)
return null;
if (!(objToken is JArray))
return PopulateProperties(obj, new JArray(objToken));
var defaultFsProperties = properties.Where(pInfo => !HasAsFieldsetAttribute(pInfo)).ToList();
foreach (var property in defaultFsProperties)
{
var propJToken = ParseJTokenFromItems(objToken, GetJsonPropertyName(property));
PopulateProperty(obj, propJToken["properties"], property);
}
return PopulateProperties(obj, new JArray(objToken));
}
private JToken GetPropertyJToken(JToken fsJToken, string propAlias)
{
if (fsJToken == null)
return null;
//make recursive
if (fsJToken.SelectToken("properties") != null)
return GetPropertyAliasJToken(propAlias, fsJToken["properties"]);
var nestedPropJToken = fsJToken
.SingleOrDefault(p => p.SelectToken("alias").ToString().Equals(propAlias));
return nestedPropJToken != null ? GetPropertyAliasJToken(propAlias, nestedPropJToken["properties"]) : null;
}
private JToken GetFieldsetJTokenFromTypeAlias(Type objType, JToken jToken)
{
var objAlias = GetFieldsetName(objType);
return GetFieldsetJTokenFromAlias(objAlias, jToken);
}
private JToken GetFieldsetJTokenFromPropertyAlias(PropertyInfo propInfo, JToken jToken)
{
var objAlias = GetJsonPropertyName(propInfo);
return GetFieldsetJTokenFromAlias(objAlias, jToken);
}
private JToken GetFieldsetJTokenFromAlias(string objAlias, JToken jToken)
{
JToken propJToken;
if (TryParseJTokenFromNamedFieldset(jToken, objAlias, out propJToken))
return propJToken;
if (TryParseJTokenFromNestedFieldset(jToken, objAlias, out propJToken))
return propJToken;
if (TryParseJTokenFromDefaultFieldset(jToken, out propJToken))
return propJToken;
return propJToken
?? ParseJTokenFromItem(jToken, objAlias);
}
private object PopulateProperties(object obj, JToken jToken)
{
var properties = GetSerialiazableProperties(obj);
foreach (var propertyInfo in properties)
{
PopulateProperty(obj, jToken, propertyInfo);
}
return obj;
}
private void PopulateProperty(object obj, JToken jToken, PropertyInfo propertyInfo)
{
var propAlias = GetJsonPropertyName(propertyInfo);
var propJToken = GetPropertyAliasJToken(propAlias, jToken);
if (propJToken == null)
return;
var propValue = GetPropertyValue(propertyInfo, propJToken);
propertyInfo.SetValue(obj, propValue);
}
private JToken GetPropertyAliasJToken(string propAlias, JToken jToken)
{
return jToken.SingleOrDefault(p => p.SelectToken("alias").ToString().Equals(propAlias));
}
private object GetPropertyValue(PropertyInfo propertyInfo, JToken propJToken)
{
return IsTypeArchetypeDatatype(propertyInfo.PropertyType)
? DeserializeJson(propertyInfo.PropertyType, propJToken)
: IsTypeIEnumerableArchetypeDatatype(propertyInfo.PropertyType)
? DeserializeJson(propertyInfo.PropertyType, propJToken["value"].SelectToken("fieldsets"))
: GetDeserializedPropertyValue(propJToken, propertyInfo.PropertyType);
}
private JToken ParseJTokenFromItem(JToken jToken, string itemAlias)
{
return (jToken.SelectToken("alias") != null && jToken["alias"].ToString().Equals(itemAlias))
? jToken
: null;
}
private JToken ParseJTokenFromItems(JToken jToken, string itemAlias)
{
return jToken.SingleOrDefault(jT => ParseJTokenFromItem(jT, itemAlias) != null);
}
private bool TryParseJTokenFromNamedFieldset(JToken jToken, string objAlias, out JToken resultToken)
{
resultToken = null;
if (jToken.SelectToken("fieldsets") == null)
return false;
resultToken = jToken["fieldsets"].SingleOrDefault(p => p.SelectToken("alias").ToString().Equals(objAlias));
return resultToken != null;
}
private bool TryParseJTokenFromNestedFieldset(JToken jToken, string objAlias, out JToken resultToken)
{
resultToken = null;
if (jToken.SelectToken("value") == null || !jToken["value"].HasValues
|| jToken["value"]["fieldsets"] == null)
return false;
resultToken = jToken["value"]["fieldsets"].SingleOrDefault(p => p.SelectToken("alias").ToString().Equals(objAlias));
return resultToken != null;
}
private bool TryParseJTokenFromDefaultFieldset(JToken jToken, out JToken resultToken)
{
resultToken = jToken.SelectToken("fieldsets");
return resultToken != null;
}
private bool TryParseJTokenAsEnumerable(JToken jToken, out JToken resultToken)
{
resultToken = null;
//To Do: Strange newtonsoft behaviour
var fsToken = jToken.Parent != null
? jToken.Parent.Children().First() is JArray
? jToken.Parent.Children().First()
: jToken.Parent.Children().First()["fieldsets"]
: jToken.SelectToken("fieldsets");
var jTokenEnumerable = jToken != null
&& fsToken != null && fsToken is JArray;
if (jTokenEnumerable)
resultToken = jToken;
return jTokenEnumerable;
}
#endregion
#region private methods - serialization
private string ApplyFormatting(string json, Formatting formatting)
{
return JToken.Parse(json).ToString(formatting);
}
private IList GenerateModels(object value)
{
var models = value as IList;
if (null != models)
return models;
var properties = GetSerialiazableProperties(value).ToList();
if (!PropertyLayoutHasFieldsets(properties))
return new List<object>() { value };
models = new List<object>();
var fieldsetModels = properties.Where(HasAsFieldsetAttribute)
.Select(pInfo =>
{
var fsModel = GetDynamicModel(GetJsonPropertyName(pInfo));
fsModel.Add(GetJsonPropertyName(pInfo), pInfo.GetValue(value));
return fsModel;
});
models = ((IEnumerable<object>)models).Concat(fieldsetModels).ToList();
var remainingProps = properties.Where(pInfo => !HasAsFieldsetAttribute(pInfo)).ToList();
if (!remainingProps.Any())
return models;
var dynamicModel = GetDynamicModel(GetFieldsetName(value.GetType()));
foreach (var pInfo in properties.Where(pInfo => !HasAsFieldsetAttribute(pInfo)))
{
dynamicModel.Add(GetJsonPropertyName(pInfo), pInfo.GetValue(value));
}
models.Add(dynamicModel);
return models;
}
private IDictionary<string, object> GetDynamicModel(string rootFsAlias)
{
var dynamicModel = new ExpandoObject() as IDictionary<string, object>;
dynamicModel.Add(_ROOT_FS_ALIAS, rootFsAlias);
return dynamicModel;
}
private JObject SerializeModelToFieldset(IEnumerable models)
{
var jObj = new JObject
{
{
"fieldsets",
new JArray(new JRaw(SerializeModels(models)))
}
};
return jObj;
}
private IEnumerable SerializeModels(IEnumerable models)
{
var fieldsetJson = (from object model in models where null != model select SerializeModel(model)).ToList();
return String.Join(",", fieldsetJson);
}
private string SerializeModel(object value)
{
if (value == null)
return null;
var jObj = IsExpandoObject(value)
? GetJObjectFromExpandoObject(value as IDictionary<string, object>)
: GetJObject(value);
var fieldsetJson = new StringBuilder();
var fieldsetWriter = new StringWriter(fieldsetJson);
using (var jsonWriter = new JsonTextWriter(fieldsetWriter))
{
jObj.WriteTo(jsonWriter);
}
return fieldsetJson.ToString();
}
private bool PropertyLayoutHasFieldsets(IEnumerable<PropertyInfo> properties)
{
return properties.Any(HasAsFieldsetAttribute);
}
private static bool HasAsFieldsetAttribute(PropertyInfo pInfo)
{
return pInfo
.GetCustomAttributes(typeof(AsFieldsetAttribute), true).Length > 0;
}
private bool IsExpandoObject(object value)
{
return value.GetType().Name.Equals(typeof (ExpandoObject).Name);
}
private JObject GetJObjectFromExpandoObject(IDictionary<string, object> expandoObj)
{
var jObj = new JObject
{
{
"alias",
new JValue(expandoObj[_ROOT_FS_ALIAS])
}
};
var fsProperties = new List<JObject>();
foreach (var item in expandoObj.SkipWhile(i => i.Key.Equals(_ROOT_FS_ALIAS)))
{
var property = item;
var alias = property.Key;
var value = property.Value;
fsProperties.Add(new JObject
{
new JProperty("alias", alias),
new JProperty("value", value != null ? JToken.FromObject(value) : value)
});
}
jObj.Add("properties", new JRaw(JsonConvert.SerializeObject(fsProperties)));
return jObj;
}
private JObject GetJObject(object obj)
{
var jObj = new JObject
{
{
"alias",
new JValue(GetFieldsetName(obj.GetType()))
}
};
var properties = GetProperties(obj);
var fsProperties = new List<JObject>();
foreach (var propertyInfo in properties)
{
var fsProperty = new JObject();
var jProperty = new JProperty("alias", GetJsonPropertyName(propertyInfo));
fsProperty.Add(jProperty);
var propValue = propertyInfo.GetIndexParameters().Length == 0
? propertyInfo.GetValue(obj, null)
: obj as string;
fsProperty.Add(
new JProperty("value", GetJPropertyValue(propValue)));
fsProperties.Add(fsProperty);
}
jObj.Add("properties", new JRaw(JsonConvert.SerializeObject(fsProperties)));
return jObj;
}
private object GetJPropertyValue(object value)
{
return IsValueArchetypeDatatype(value)
? new JRaw(JsonConvert.SerializeObject(value))
: IsValueIEnumerableArchetypeDatatype(value)
? new JRaw(SerializeModelToFieldset(value as IEnumerable))
: new JValue(GetSerializedPropertyValue(value));
}
private IEnumerable<PropertyInfo> GetProperties(object obj)
{
if (obj.GetType() != typeof(string))
return GetSerialiazableProperties(obj);
var indexer = ((DefaultMemberAttribute)obj.GetType()
.GetCustomAttributes(typeof(DefaultMemberAttribute), true)[0]).MemberName;
return new List<PropertyInfo> { obj.GetType().GetProperty(indexer) };
}
private IEnumerable<PropertyInfo> GetSerialiazableProperties(object obj)
{
return obj.GetSerialiazableProperties();
}
private string GetJsonPropertyName(PropertyInfo property)
{
var attributes = property.GetCustomAttributes(true);
var jsonPropAttribute = (JsonPropertyAttribute)attributes.FirstOrDefault(attr => attr is JsonPropertyAttribute);
return jsonPropAttribute != null ? jsonPropAttribute.PropertyName : property.Name;
}
private string GetFieldsetName(Type type)
{
var attributes = type.GetCustomAttributes(true);
var archetypeDatatypeAttribute = (AsArchetypeAttribute)attributes.FirstOrDefault(attr => attr is AsArchetypeAttribute);
return archetypeDatatypeAttribute != null ? archetypeDatatypeAttribute.FieldsetName : type.Name;
}
private string GetSerializedPropertyValue(object propValue)
{
if (propValue == null)
return String.Empty;
if (propValue is bool)
return (bool)propValue ? GetSerializedPropertyValue(1) : GetSerializedPropertyValue(0);
return String.Format("{0}", propValue);
}
private object GetDeserializedPropertyValue(JToken jToken, Type type)
{
return String.IsNullOrEmpty(jToken.ToString())
? GetDefault(type)
: GetTypedValue(jToken, type);
}
private object GetTypedValue(JToken jToken, Type type)
{
var property = JsonConvert.DeserializeObject<ArchetypePropertyModel>(jToken.ToString());
var method = typeof(ArchetypePropertyModel).GetMethod("GetValue");
var getValue = method.MakeGenericMethod(type);
return getValue.Invoke(property, null);
}
private object GetDefault(Type type)
{
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
#endregion
#region private methods - common
private bool IsValueIEnumerableArchetypeDatatype(object value)
{
if (value as IEnumerable != null && value.GetType().IsGenericType)
return IsTypeArchetypeDatatype(value.GetType().GetGenericArguments()[0]);
return false;
}
private bool IsTypeIEnumerableArchetypeDatatype(Type type)
{
if (typeof(IEnumerable).IsAssignableFrom(type) && type.IsGenericType)
return IsTypeArchetypeDatatype(type.GetGenericArguments()[0]);
return false;
}
private bool IsValueArchetypeDatatype(object value)
{
return value != null &&
IsTypeArchetypeDatatype(value.GetType());
}
private bool IsTypeArchetypeDatatype(Type type)
{
return type.GetCustomAttributes(typeof(AsArchetypeAttribute), true).Length > 0;
}
#endregion
}
}
@@ -0,0 +1,105 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Archetype.Serialization
{
public enum DelinterStep
{
RemoveNewLine = 0,
RemoveWhiteSpace,
UnescapeLabels,
UnescapeAlias,
UnescapeValues,
FixNestedFieldsets
}
public enum DelinterAction
{
Remove = 0,
RemoveWhiteSpace,
UnescapeLabels,
UnescapeValues,
FixNestedFieldsets
}
public class ArchetypeJsonDelinter
{
public IDictionary<DelinterStep, DelinterAction> Pipeline { get; private set; }
public IDictionary<DelinterStep, Regex> Tokens { get; private set; }
public IDictionary<DelinterAction, Func<string, Regex, string>> Actions { get; private set; }
public ArchetypeJsonDelinter()
{
Init();
}
public string Execute(string input)
{
var buffer = input;
foreach (var step in Pipeline.Keys)
{
var action = Pipeline[step];
buffer = Actions[action](buffer, Tokens[step]);
}
return buffer;
}
private void Init()
{
Tokens = new Dictionary<DelinterStep, Regex>
{
{DelinterStep.RemoveNewLine, new Regex(@"(?<=\,){0,1}(\\+r\\+n)(?=\\*?""(alias|value|properties|fieldsets)\\*?"":\{*?(\\*|""|\[))|(\r|\n)+|(\\+r\\+n)(?=\s*?(\{|\}|\]))|(\\+r\\n+)(?=\s+\\*?""(alias|value|properties|fieldsets))")},
{DelinterStep.RemoveWhiteSpace, new Regex(@"(""\S+?"":)(\s+?)("")|([\{\[\}\],]|(?<!\\)"")(\s+)([\{\[\}\],]|(?<!\\)"")")},
{DelinterStep.UnescapeLabels, new Regex(@"\\+""(fieldsets|properties|alias|value)\\+"":(\s*)")},
{DelinterStep.UnescapeAlias, new Regex(@"""(alias)"":\\+""(.*?)\\+""")},
{DelinterStep.UnescapeValues, new Regex(@"""(value)"":\\+""(.*?)\\+""(?=\s*?\})")},
{DelinterStep.FixNestedFieldsets, new Regex(@"""(value)"":\s*?""\{\s*?(""fieldsets""[\S\s]+?)}""")}
};
Actions = new Dictionary<DelinterAction, Func<string, Regex, string>>
{
{DelinterAction.Remove, (input, pattern) =>
RecursiveReplace(input, pattern, match => String.Empty)
},
{DelinterAction.RemoveWhiteSpace, (input, pattern) =>
RecursiveReplace(input.Trim(), pattern, match =>
String.Format("{0}{1}{2}{3}", match.Groups[1].Value,
match.Groups[3].Value, match.Groups[4].Value, match.Groups[6].Value))
},
{DelinterAction.UnescapeLabels, (input, pattern) =>
RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":", match.Groups[1].Value))
},
{DelinterAction.UnescapeValues, (input, pattern) =>
RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":""{1}""", match.Groups[1].Value, match.Groups[2].Value))
},
{DelinterAction.FixNestedFieldsets, (input, pattern) =>
RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":{{{1}}}", match.Groups[1].Value, match.Groups[2].Value))
}
};
Pipeline = new Dictionary<DelinterStep, DelinterAction>
{
{DelinterStep.RemoveNewLine, DelinterAction.Remove},
{DelinterStep.UnescapeLabels, DelinterAction.UnescapeLabels},
{DelinterStep.UnescapeAlias, DelinterAction.UnescapeValues},
{DelinterStep.UnescapeValues, DelinterAction.UnescapeValues},
{DelinterStep.FixNestedFieldsets, DelinterAction.FixNestedFieldsets},
{DelinterStep.RemoveWhiteSpace, DelinterAction.RemoveWhiteSpace},
};
}
private string RecursiveReplace(string input, Regex pattern, Func<Match, string> matchFunc)
{
var buffer = input;
while (pattern.IsMatch(buffer))
{
buffer = pattern.Replace(buffer, match => matchFunc(match));
}
return buffer;
}
}
}
@@ -0,0 +1,14 @@
using System;
namespace Archetype.Serialization
{
public class AsArchetypeAttribute : Attribute
{
public string FieldsetName { get; set; }
public AsArchetypeAttribute(string fieldsetname)
{
FieldsetName = fieldsetname;
}
}
}
@@ -0,0 +1,8 @@
using System;
namespace Archetype.Serialization
{
public class AsFieldsetAttribute : Attribute
{
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Archetype.Extensions;
using Newtonsoft.Json;
using Umbraco.Core.Models;
using Umbraco.Web;
namespace Archetype.Serialization
{
public static class SerializationExtensions
{
//temporary
public static string GetPropertyDataValue(this IPublishedContent contentPage, string propertyAlias)
{
if (contentPage == null || !contentPage.HasProperty(propertyAlias))
return null;
return (string)contentPage.Properties.Single(p => p.PropertyTypeAlias == propertyAlias).DataValue;
}
public static IEnumerable<PropertyInfo> GetSerialiazableProperties(this object obj)
{
if (obj == null)
return new List<PropertyInfo>();
return obj.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(prop => !Attribute.IsDefined(prop, typeof(JsonIgnoreAttribute)));
}
public static T GetModelFromArchetype<T>(this IPublishedContent content,
string cmsFieldAlias, bool returnInstanceIfNull = false)
where T : class, new()
{
var archetypeJson = content.GetPropertyDataValue(cmsFieldAlias);
return JsonConvert.DeserializeObject<T>(archetypeJson) ??
(returnInstanceIfNull ? Activator.CreateInstance<T>() : default(T));
}
public static string DelintArchetypeJson(this string input)
{
if (!input.DetectIsJson())
return String.Empty;
var delinter = new ArchetypeJsonDelinter();
return delinter.Execute(input);
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio
//define empty items
var newPropertyModel = '{"alias": "", "remove": false, "collapse": false, "label": "", "helpText": "", "dataTypeGuid": "0cc0eba1-9960-42c9-bf9b-60e150b429ae", "value": ""}';
var newFieldsetModel = '{"alias": "", "remove": false, "collapse": false, "labelTemplate": "", "icon": "", "label": "", "properties": [' + newPropertyModel + ']}';
var defaultFieldsetConfigModel = JSON.parse('{"showAdvancedOptions": false, "startWithAddButton": false, "hideFieldsetToolbar": false, "enableMultipleFieldsets": false, "hideFieldsetControls": false, "hidePropertyLabel": false, "maxFieldsets": null, "enableCollapsing": true, "fieldsets": [' + newFieldsetModel + ']}');
var defaultFieldsetConfigModel = JSON.parse('{"showAdvancedOptions": false, "startWithAddButton": false, "hideFieldsetToolbar": false, "enableMultipleFieldsets": false, "hideFieldsetControls": false, "hidePropertyLabel": false, "maxFieldsets": null, "enableCollapsing": true, "enableDisabling": true, "fieldsets": [' + newFieldsetModel + ']}');
//ini the model
$scope.model.value = $scope.model.value || defaultFieldsetConfigModel;
+36
View File
@@ -11,6 +11,10 @@
//ini the model
$scope.model.value = $scope.model.value || getDefaultModel($scope.model.config);
// store the umbraco property alias to help generate unique IDs. Hopefully there's a better way to get this in the future :)
$scope.umbracoHostPropertyAlias = $scope.$parent.$parent.model.alias;
init();
//helper to get $eval the labelTemplate
@@ -83,6 +87,12 @@
}
}
$scope.enableDisable = function (fieldset) {
fieldset.disabled = !fieldset.disabled;
// explicitly set the form as dirty when manipulating the enabled/disabled state of a fieldset
$scope.setDirty();
}
//helpers for determining if a user can do something
$scope.canAdd = function ()
{
@@ -108,6 +118,11 @@
return countVisible() > 1;
}
//helper that returns if an item can be disabled
$scope.canDisable = function () {
return $scope.model.config.enableDisabling;
}
//helpers for determining if the add button should be shown
$scope.showAddButton = function () {
return $scope.model.config.startWithAddButton
@@ -263,6 +278,27 @@
return (typeof property == 'undefined') ? true : property.isValid;
}
//helper to lookup validity when given a fieldset
$scope.getFieldsetValidity = function (fieldset) {
if (fieldset.isValid == false) {
return false;
}
// recursive validation of nested fieldsets
var nestedFieldsetsValid = true;
_.each(fieldset.properties, function (property) {
if (property != null && property.value != null && property.propertyEditorAlias == "Imulus.Archetype") {
_.each(property.value.fieldsets, function (inner) {
if ($scope.getFieldsetValidity(inner) == false) {
nestedFieldsetsValid = false;
}
});
}
});
return nestedFieldsetsValid;
}
// helper to force the current form into the dirty state
$scope.setDirty = function () {
if($scope.form) {
+22
View File
@@ -0,0 +1,22 @@
angular.module("umbraco.directives").directive('archetypeCustomView', function ($compile, $http) {
var linker = function (scope, element, attrs) {
var view = "/App_plugins/Archetype/views/archetype.default.html";
if(scope.model.config.customViewPath) {
view = config.customViewPath;
}
$http.get(view).then(function(data) {
element.html(data.data).show();
$compile(element.contents())(scope);
});
}
return {
restrict: "A",
replace: true,
link: linker
}
});
+96 -44
View File
@@ -1,5 +1,5 @@
angular.module("umbraco.directives").directive('archetypeProperty', function ($compile, $http, archetypePropertyEditorResource, umbPropEditorHelper, $timeout, $rootScope, $q) {
function getFieldsetByAlias(fieldsets, alias)
{
return _.find(fieldsets, function(fieldset){
@@ -55,7 +55,12 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
var config = null;
var alias = configFieldsetModel.properties[scope.propertyConfigIndex].alias;
var defaultValue = configFieldsetModel.properties[scope.propertyConfigIndex].value;
var umbracoPropertyAlias = scope.umbracoPropertyAlias;
var propertyAlias = getUniquePropertyAlias(scope);
propertyAliasParts = [];
// initialize container for invalid fieldset property identifiers (store on ngModelCtrl to separate Archetype validations, e.g. when there two Archetype properties on the same document)
if(ngModelCtrl.invalidProperties == null) {
ngModelCtrl.invalidProperties = [];
}
//try to convert the defaultValue to a JS object
defaultValue = jsonOrString(defaultValue, scope.archetypeConfig.developerMode, "defaultValue");
@@ -81,48 +86,97 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
var mergedConfig = _.extend(defaultConfigObj, config);
loadView(pathToView, mergedConfig, defaultValue, alias, umbracoPropertyAlias, scope, element, ngModelCtrl);
loadView(pathToView, mergedConfig, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, validateProperty);
});
});
ngModelCtrl.$parsers.push(validate);
ngModelCtrl.$formatters.push(validate);
function validate(renderModel){
var valid = true;
_.each(renderModel, function(fieldset){
fieldset.isValid = true;
_.each(fieldset.properties, function(property){
property.isValid = true;
var propertyConfig = getPropertyByAlias(configFieldsetModel, property.alias);
if (propertyConfig) {
if(propertyConfig.required && (property.value == null || property.value === "")) {
fieldset.isValid = false;
property.isValid = false;
valid = false;
}
// issue 116: RegEx validate property value
// Only validate the property value if anything has been entered - RegEx is considered a supplement to "required".
if (valid == true && propertyConfig.regEx && property.value) {
var regEx = new RegExp(propertyConfig.regEx);
if (regEx.test(property.value) == false) {
fieldset.isValid = false;
property.isValid = false;
valid = false;
}
}
}
});
scope.$on("formSubmitting", function (ev, args) {
// "hard" validate to highlight any erroneous entries
_.each(scope.fieldset.properties, function (property) {
validateProperty(scope.fieldset, property, true);
});
});
ngModelCtrl.$setValidity('validation', valid);
return renderModel;
scope.$on("formSubmitted", function (ev, args) {
// reset the nested fieldset validation state after submit
ngModelCtrl.invalidProperties = [];
});
// need to pass the property fieldset here to clear any invalid state of the fieldset when validating a single fieldset property
// - it's the Umbraco way to hide the invalid state when altering an invalid property, even if the new value isn't valid either
function validateProperty(fieldset, property, markAsInvalid) {
var valid = true;
fieldset.isValid = true;
property.isValid = true;
var propertyConfig = getPropertyByAlias(configFieldsetModel, property.alias);
if (propertyConfig) {
// use property.value !== property.value to check for NaN values on numeric inputs
if (propertyConfig.required && (property.value == null || property.value === "" || property.value !== property.value)) {
valid = false;
}
// issue 116: RegEx validate property value
// Only validate the property value if anything has been entered - RegEx is considered a supplement to "required".
if (valid == true && propertyConfig.regEx && property.value) {
var regEx = new RegExp(propertyConfig.regEx);
if (regEx.test(property.value) == false) {
valid = false;
}
}
// only mark the property as invalid when doing a "hard" validation
if (valid == false && markAsInvalid == true) {
property.isValid = false;
}
}
// handle nested fieldset validation by storing the identifier of all invalid fieldset properties
var fieldsetIdentifier = scope.umbracoPropertyAlias + "_" + scope.fieldsetIndex;
var propertyIdentifier = fieldsetIdentifier + "_" + property.alias;
var propertyIdentifierIndex = ngModelCtrl.invalidProperties.indexOf(propertyIdentifier);
if (valid == false) {
if (propertyIdentifierIndex == -1) {
ngModelCtrl.invalidProperties.push(propertyIdentifier);
}
}
else {
if (propertyIdentifierIndex != -1) {
ngModelCtrl.invalidProperties.splice(propertyIdentifierIndex, 1);
}
}
if (markAsInvalid) {
// mark the entire fieldset as invalid if there are any invalid properties in the fieldset, otherwise mark it as valid
fieldset.isValid =
_.find(ngModelCtrl.invalidProperties, function (item) {
return item.indexOf(fieldsetIdentifier) == 0
}) == null;
}
// set invalid state if one or more fieldsets contain invalid properties
ngModelCtrl.$setValidity('validation', ngModelCtrl.invalidProperties.length == 0);
}
}
function loadView(view, config, defaultValue, alias, umbracoPropertyAlias, scope, element, ngModelCtrl) {
var propertyAliasParts = [];
var getUniquePropertyAlias = function (currentScope) {
if (currentScope.hasOwnProperty('fieldsetIndex') && currentScope.hasOwnProperty('property') && currentScope.hasOwnProperty('propertyConfigIndex'))
{
var currentPropertyAlias = "f" + currentScope.fieldsetIndex + "-" + currentScope.property.alias + "-p" + currentScope.propertyConfigIndex;
propertyAliasParts.push(currentPropertyAlias);
}
else if (currentScope.hasOwnProperty('isPreValue')) // Crappy way to identify this is the umbraco property scope
{
var umbracoPropertyAlias = currentScope.$parent.$parent.property.alias; // Crappy way to get the umbraco host alias once we identify its scope
propertyAliasParts.push(umbracoPropertyAlias);
}
if (currentScope.$parent)
getUniquePropertyAlias(currentScope.$parent);
return _.unique(propertyAliasParts).reverse().join("-");
};
function loadView(view, config, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, validateProperty) {
if (view)
{
$http.get(view).success(function (data) {
@@ -151,17 +205,15 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
scope.model.config = config;
//some items need an alias
scope.model.alias = "archetype-property-" + umbracoPropertyAlias + "-" + scope.fieldsetIndex + "-" + scope.propertyConfigIndex;
scope.model.alias = "archetype-property-" + propertyAlias;
//watch for changes since there is no two-way binding with the local model.value
scope.$watch('model.value', function (newValue, oldValue) {
scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties[renderModelPropertyIndex].value = newValue;
// don't set the current value twice - this will keep Umbraco from prompting to discard changes immediately after saving
if (newValue != oldValue) {
//trigger the validation pipeline
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
}
// call validation method for the property when the value changes
// use "soft" validation to mimic the default umbraco validation style (show error highlights on submit, not while entering data)
validateProperty(scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex], scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties[renderModelPropertyIndex], false);
});
element.html(data).show();
@@ -171,7 +223,7 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
var def = $q.defer();
def.resolve(true);
$rootScope.$apply();
}, 500)
}, 500);
}
});
}
@@ -180,7 +232,7 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
return {
require: "^ngModel",
restrict: "E",
rep1ace: true,
replace: true,
link: linker,
scope: {
property: '=',
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Egenskaber",
"labelTemplate": "Label skabelon",
"select": "Vælg",
"icon": "Ikon"
"icon": "Ikon",
"enableDisabling": "Aktivér fieldset-deaktivering?",
"enableDisablingDescription": "Tillad brugere at aktivere/deaktivere individuelle fieldsets."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+3 -1
View File
@@ -31,5 +31,7 @@
"properties": "Properties",
"labelTemplate": "Label Template",
"select": "Select",
"icon": "Icon"
"icon": "Icon",
"enableDisabling": "Enable Fieldset Disabling?",
"enableDisablingDescription": "Allows fieldsets to be individually enabled/disabled."
}
+14
View File
@@ -4,6 +4,10 @@
.dropdown-menu {
top: auto;
}
.controls-no-label {
padding: 10px;
}
}
.archetypeEditor fieldset{
@@ -78,6 +82,9 @@
span {
display: inline-block;
}
&.dimmed {
color: #d9d9d9;
}
}
}
@@ -109,6 +116,9 @@
&.handle {
cursor: move;
}
&.dimmed {
color: #d9d9d9;
}
}
}
}
@@ -295,6 +305,10 @@
span {
color: #343434;
}
&.dimmed span {
color: #d9d9d9;
}
}
}
.archetypeEditor .archetypePropertyError{
+9 -1
View File
@@ -106,9 +106,13 @@
<input type="checkbox" id="archetypeAdvancedOptionsMultipleFieldsets" ng-model="archetypeConfigRenderModel.enableMultipleFieldsets"/>
</div>
<div>
<label for="archetypeAdvancedOptionsCollapsing"><archetype-localize key="enableCollapsing">Enable Collapsing?</archetype-localize><small><archetype-localize key="enableCollapsingDescription">Allows multiple types of fieldsets within this archetype.</archetype-localize></small></label>
<label for="archetypeAdvancedOptionsCollapsing"><archetype-localize key="enableCollapsing">Enable Collapsing?</archetype-localize><small><archetype-localize key="enableCollapsingDescription">Enable fieldset collapsing.</archetype-localize></small></label>
<input type="checkbox" id="archetypeAdvancedOptionsCollapsing" ng-model="archetypeConfigRenderModel.enableCollapsing"/>
</div>
<div>
<label for="archetypeAdvancedOptionsDisabling"><archetype-localize key="enableDisabling">Enable Fieldset Disabling?</archetype-localize><small><archetype-localize key="enableDisablingDescription">Allows fieldsets to be individually enabled/disabled.</archetype-localize></small></label>
<input type="checkbox" id="archetypeAdvancedOptionsDisabling" ng-model="archetypeConfigRenderModel.enableDisabling" />
</div>
<div>
<label for="archetypeAdvancedOptionsFieldsetToolbar"><archetype-localize key="hideFieldsetToolbar">Hide Fieldset Toolbar?</archetype-localize><small><archetype-localize key="hideFieldsetToolbarDescription">Hides the fieldset toolbar that appears when more than one fieldset model appears.</archetype-localize></small></label>
<input type="checkbox" id="archetypeAdvancedOptionsFieldsetToolbar" ng-model="archetypeConfigRenderModel.hideFieldsetToolbar"/>
@@ -125,6 +129,10 @@
<label for="archetypeAdvancedOptionsCustomJs"><archetype-localize key="jsFile">JS File</archetype-localize><small><archetype-localize key="jsFileDescription">(Optional) Enter a path for a custom JS file to be included on the page.</archetype-localize></small></label>
<input type="text" id="archetypeAdvancedOptionsCustomJs" ng-model="archetypeConfigRenderModel.customJsPath"/>
</div>
<div>
<label for="archetypeAdvancedOptionsCustomView"><archetype-localize key="viewFile">View File</archetype-localize><small><archetype-localize key="viewFileDescription">(Optional) Enter a path for a custom view file to be used to render this Archetype.</archetype-localize></small></label>
<input type="text" id="archetypeAdvancedOptionsCustomView" ng-model="archetypeConfigRenderModel.customViewPath"/>
</div>
<div>
<label for="archetypeAdvancedOptionsDeveloperMode"><archetype-localize key="toggleDeveloperMode">Toggle Developer Mode</archetype-localize><small><archetype-localize key="toggleDeveloperModeDescription">Toggle Developer Mode</archetype-localize></small></label>
<input type="checkbox" id="archetypeAdvancedOptionsDeveloperMode" ng-model="archetypeConfigRenderModel.developerMode"/>
+63
View File
@@ -0,0 +1,63 @@
<div class="archetypeEditor ng-class:model.config.customCssClass">
<textarea class="archetypeDeveloperModel" ng-show="model.config.developerMode" ng-model="model.value"></textarea>
<ul ui-sortable="sortableOptions" ng-model="model.value.fieldsets" ng-show="!showAddButton()">
<li ng-repeat="fieldset in model.value.fieldsets">
<fieldset ng-class="[fieldset.alias, (getFieldsetValidity(fieldset) == false ? 'archetypeFieldsetError' : '')]" ng-init="fieldsetConfigModel = getConfigFieldsetByAlias(fieldset.alias)">
<div class="archetypeFieldsetLabel" ng-class="{enableCollapsing: model.config.enableCollapsing}">
<div ng-click="focusFieldset(fieldset)" class="label-sub module-label">
<span class="caret" ng-hide="fieldset.collapse || !model.config.enableCollapsing"></span>
<span class="caret caret-right" ng-show="fieldset.collapse && model.config.enableCollapsing"></span>
<label ng-class="{dimmed: fieldset.disabled}">
<i class="fieldsetIcon icon ng-class:fieldsetConfigModel.icon"></i>
<span ng-bind="getFieldsetTitle(fieldsetConfigModel, $index)"></span>
</label>
</div>
<div class="archetypeEditorControls label-sub" ng-hide="model.config.hideFieldsetControls">
<i class="icon icon-add dropdown-toggle" ng-show="canAdd()" data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(fieldset.alias, $index) : return"></i>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, $parent.$index)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
<i class="icon icon-remove" ng-click="removeRow($index)" ng-show="canRemove()"></i>
<i class="icon icon-power" ng-class="{dimmed: fieldset.disabled}" ng-click="enableDisable(fieldset)" ng-show="canDisable()"></i>
<i class="icon icon-navigation handle" ng-show="canSort()"></i>
</div>
</div>
<div class="archetypeCollapser animate-hide" ng-hide="isCollapsed(fieldset)">
<form class="form-inline">
<div ng-class="[property.alias, (getPropertyValidity($parent.$index, property.alias) === false ? 'archetypePropertyError' : '')]" class="archetypeProperty control-group" ng-repeat="property in fieldsetConfigModel.properties">
<label ng-hide="archetypeConfig.hidePropertyLabels == '1'" class="control-label" for="archetype-property-{{umbracoHostPropertyAlias}}-f{{$parent.$index}}-{{property.alias}}-p{{$index}}">
<span>{{property.label}}</span>
<div class="archetypeFieldsetHelpText" ng-show="property.helpText">
<small>{{property.helpText}}</small>
</div>
</label>
<div class="controls">
<archetype-property class="archetypeEditor" property="property" fieldset-index="$parent.$index" fieldset="fieldset" archetype-config="model.config" property-config-index="$index" archetype-render-model="model.value" umbraco-form = "form"></archetype-property>
</div>
</div>
</form>
</div>
</fieldset>
</li>
</ul>
<div ng-show="showAddButton()">
<a data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(model.config.fieldsets[0].alias, 0) : return" prevent-default>
<i class="icon icon-add dropdown-toggle"></i>
<localize key="general_add" class="archetypeAddButtonText">Add</localize>
</a>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, 0)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
</div>
</div>
+1 -62
View File
@@ -1,62 +1 @@
<div class="archetypeEditor ng-class:model.config.customCssClass" ng-controller="Imulus.ArchetypeController">
<textarea class="archetypeDeveloperModel" ng-show="model.config.developerMode" ng-model="model.value"></textarea>
<ul ui-sortable="sortableOptions" ng-model="model.value.fieldsets" ng-show="!showAddButton()">
<li ng-repeat="fieldset in model.value.fieldsets">
<fieldset ng-class="{archetypeFieldsetError: !fieldset.isValid}" ng-init="fieldsetConfigModel = getConfigFieldsetByAlias(fieldset.alias)">
<div class="archetypeFieldsetLabel" ng-class="{enableCollapsing: model.config.enableCollapsing}">
<div ng-click="focusFieldset(fieldset)" class="label-sub module-label">
<span class="caret" ng-hide="fieldset.collapse || !model.config.enableCollapsing"></span>
<span class="caret caret-right" ng-show="fieldset.collapse && model.config.enableCollapsing"></span>
<label>
<i class="fieldsetIcon icon ng-class:fieldsetConfigModel.icon"></i>
<span ng-bind="getFieldsetTitle(fieldsetConfigModel, $index)"></span>
</label>
</div>
<div class="archetypeEditorControls label-sub" ng-hide="model.config.hideFieldsetControls">
<i class="icon icon-add dropdown-toggle" ng-show="canAdd()" data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(fieldset.alias, $index) : return"></i>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, $parent.$index)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
<i class="icon icon-remove" ng-click="removeRow($index)" ng-show="canRemove()"></i>
<i class="icon icon-navigation handle" ng-show="canSort()"></i>
</div>
</div>
<div class="archetypeCollapser animate-hide" ng-hide="isCollapsed(fieldset)">
<form class="form-inline">
<div ng-class="{archetypePropertyError: getPropertyValidity($parent.$index, property.alias) === false}" class="archetypeProperty control-group" ng-repeat="property in fieldsetConfigModel.properties">
<label ng-hide="archetypeConfig.hidePropertyLabels == '1'" class="control-label" for="archetype-property-{{model.alias}}-{{$parent.$index}}-{{$index}}">
<span>{{property.label}}</span>
<div class="archetypeFieldsetHelpText" ng-show="property.helpText">
<small>{{property.helpText}}</small>
</div>
</label>
<div class="controls">
<archetype-property class="archetypeEditor ng-class:property.alias" property="property" fieldset-index="$parent.$index" fieldset="fieldset" archetype-config="model.config" property-config-index="$index" archetype-render-model="model.value" umbraco-property-alias="model.alias" umbraco-form = "form"></archetype-property>
</div>
</div>
</form>
</div>
</fieldset>
</li>
</ul>
<div ng-show="showAddButton()">
<a data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(fieldset.alias, 0) : return" prevent-default>
<i class="icon icon-add dropdown-toggle"></i>
<localize key="general_add" class="archetypeAddButtonText">Add</localize>
</a>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, 0)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
</div>
</div>
<div archetype-custom-view ng-controller="Imulus.ArchetypeController"></div>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Archetype",
"version": "1.0-beta",
"version": "1.3.0-beta",
"url": "http://github.com/imulus/archetype/",
"author": "Imulus",
"authorUrl": "http://imulus.com/",
+3
View File
@@ -27,5 +27,8 @@
"repository": {
"type": "git",
"url": "https://github.com/imulus/Archetype.git"
},
"devDependencies": {
"grunt-msbuild": "^0.1.12"
}
}