#17255: Adding a DisplayName property to fields

Work Item: 17255

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2011-12-06 10:44:39 -08:00
parent d695e681f3
commit 8d578fb59e
18 changed files with 395 additions and 95 deletions

View File

@@ -1,5 +1,5 @@
ec573e5476f7e8a5a61593d6393e9985e9484fcc src/Orchard.Web/Modules/Orchard.Forms
d4e8bde4741a16cb341311395b211fcf211d2808 src/Orchard.Web/Modules/Orchard.Projections
0d1100754d594a2977eaab40630f1c46a9e8cf2c src/Orchard.Web/Modules/Orchard.Projections
01b83c05050bb731d9f69256bbe8884d458ea1c9 src/Orchard.Web/Modules/Orchard.Rules
65057c6a5cd71f7994ba9bcbeece50dbb737620e src/Orchard.Web/Modules/Orchard.TaskLease
460f08a0d0befd36a3f7e974d8b782ae3df747e7 src/Orchard.Web/Modules/Orchard.Tokens

View File

@@ -1,8 +1,7 @@
@using Orchard.Utility.Extensions;
@{
string name = Model.Name;
@{
string name = Model.Field.DisplayName;
string value = Model.Value;
}
@if (HasText(name) && HasText(value)) {
<p class="text-field"><span class="name">@name.CamelFriendly():</span> <span class="value">@value</span></p>
<p class="text-field"><span class="name">@name:</span> <span class="value">@value</span></p>
}

View File

@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Routable.Events;
using Orchard.Core.Routable.Models;
using Orchard.Utility.Extensions;
namespace Orchard.Core.Routable.Services {
public class RoutableService : IRoutableService {
@@ -51,20 +50,6 @@ namespace Orchard.Core.Routable.Services {
}
}
public static string RemoveDiacritics(string slug) {
string stFormD = slug.Normalize(NormalizationForm.FormD);
var sb = new StringBuilder();
foreach (char t in stFormD) {
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(t);
if (uc != UnicodeCategory.NonSpacingMark) {
sb.Append(t);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
public void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect {
if ((model.Slug != null && !string.IsNullOrEmpty(model.Slug.Trim())) || string.IsNullOrEmpty(model.Title))
return;
@@ -84,7 +69,7 @@ namespace Orchard.Core.Routable.Services {
slugContext.Slug = slugContext.Slug.Substring(0, 1000);
// dots are not allowed at the begin and the end of routes
slugContext.Slug = RemoveDiacritics(slugContext.Slug.Trim('.').ToLower());
slugContext.Slug = StringExtensions.RemoveDiacritics(slugContext.Slug.Trim('.').ToLower());
}
foreach (ISlugEventHandler slugEventHandler in _slugEventHandlers) {

View File

@@ -10,6 +10,7 @@ using Orchard.Core.Contents.Controllers;
using Orchard.Core.Contents.Settings;
using Orchard.Localization;
using Orchard.UI.Notify;
using Orchard.Utility.Extensions;
namespace Orchard.ContentTypes.Controllers {
public class AdminController : Controller, IUpdateModel {
@@ -63,7 +64,7 @@ namespace Orchard.ContentTypes.Controllers {
ModelState.AddModelError("Name", T("A type with the same Id already exists.").ToString());
}
if (!String.IsNullOrWhiteSpace(viewModel.Name) && !ContentDefinitionService.IsLetter(viewModel.Name[0])) {
if (!String.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter()) {
ModelState.AddModelError("Name", T("The technical name must start with a letter.").ToString());
}
@@ -71,10 +72,6 @@ namespace Orchard.ContentTypes.Controllers {
ModelState.AddModelError("DisplayName", T("A type with the same Display Name already exists.").ToString());
}
if (_contentDefinitionService.GetTypes().Any(t => String.Equals(t.Name, viewModel.Name, StringComparison.OrdinalIgnoreCase))) {
ModelState.AddModelError("Name", T("A type with the same Name already exists.").ToString());
}
if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
return View(viewModel);
@@ -97,6 +94,10 @@ namespace Orchard.ContentTypes.Controllers {
return Json(_contentDefinitionService.GenerateContentTypeNameFromDisplayName(displayName));
}
public ActionResult FieldName(string partName, string displayName) {
return Json(_contentDefinitionService.GenerateFieldNameFromDisplayName(partName, displayName));
}
public ActionResult Edit(string id) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
@@ -147,7 +148,6 @@ namespace Orchard.ContentTypes.Controllers {
return RedirectToAction("List");
}
[HttpPost, ActionName("Edit")]
[FormValueRequired("submit.Delete")]
public ActionResult Delete(string id) {
@@ -371,21 +371,21 @@ namespace Orchard.ContentTypes.Controllers {
var viewModel = new AddFieldViewModel {
Part = partViewModel,
Fields = _contentDefinitionService.GetFields()
Fields = _contentDefinitionService.GetFields().OrderBy(x => x.FieldTypeName)
};
return View(viewModel);
}
[HttpPost, ActionName("AddFieldTo")]
public ActionResult AddFieldToPOST(string id) {
public ActionResult AddFieldToPOST(AddFieldViewModel viewModel, string id) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content part.")))
return new HttpUnauthorizedResult();
var partViewModel = _contentDefinitionService.GetPart(id);
var typeViewModel = _contentDefinitionService.GetType(id);
if (partViewModel == null) {
//id passed in might be that of a type w/ no implicit field
// id passed in might be that of a type w/ no implicit field
if (typeViewModel != null) {
partViewModel = new EditPartViewModel {Name = typeViewModel.Name};
_contentDefinitionService.AddPart(new CreatePartViewModel {Name = partViewModel.Name});
@@ -396,34 +396,137 @@ namespace Orchard.ContentTypes.Controllers {
}
}
var viewModel = new AddFieldViewModel();
if (!TryUpdateModel(viewModel)) {
Services.TransactionManager.Cancel();
return AddFieldTo(id);
viewModel.DisplayName = viewModel.DisplayName ?? String.Empty;
viewModel.DisplayName = viewModel.DisplayName.Trim();
viewModel.Name = viewModel.Name ?? String.Empty;
if (String.IsNullOrWhiteSpace(viewModel.DisplayName)) {
ModelState.AddModelError("DisplayName", T("The Display Name name can't be empty.").ToString());
}
try {
_contentDefinitionService.AddFieldToPart(viewModel.DisplayName, viewModel.FieldTypeName, partViewModel.Name);
if (String.IsNullOrWhiteSpace(viewModel.Name)) {
ModelState.AddModelError("Name", T("The Technical Name can't be empty.").ToString());
}
catch (Exception ex) {
Services.Notifier.Information(T("The \"{0}\" field was not added. {1}", viewModel.DisplayName, ex.Message));
Services.TransactionManager.Cancel();
return AddFieldTo(id);
if (_contentDefinitionService.GetPart(partViewModel.Name).Fields.Any(t => String.Equals(t.Name.Trim(), viewModel.Name.Trim(), StringComparison.OrdinalIgnoreCase))) {
ModelState.AddModelError("Name", T("A field with the same name already exists.").ToString());
}
if (!String.IsNullOrWhiteSpace(viewModel.Name) && !viewModel.Name[0].IsLetter()) {
ModelState.AddModelError("Name", T("The technical name must start with a letter.").ToString());
}
if (!String.Equals(viewModel.Name, viewModel.Name.ToSafeName(), StringComparison.OrdinalIgnoreCase)) {
ModelState.AddModelError("Name", T("The technical name contains invalid characters.").ToString());
}
if (_contentDefinitionService.GetPart(partViewModel.Name).Fields.Any(t => String.Equals(t.DisplayName.Trim(), Convert.ToString(viewModel.DisplayName).Trim(), StringComparison.OrdinalIgnoreCase))) {
ModelState.AddModelError("DisplayName", T("A field with the same Display Name already exists.").ToString());
}
if (!ModelState.IsValid) {
viewModel.Part = partViewModel;
viewModel.Fields = _contentDefinitionService.GetFields();
Services.TransactionManager.Cancel();
return View(viewModel);
}
try {
_contentDefinitionService.AddFieldToPart(viewModel.Name, viewModel.FieldTypeName, partViewModel.Name);
}
catch (Exception ex) {
Services.Notifier.Information(T("The \"{0}\" field was not added. {1}", viewModel.Name, ex.Message));
Services.TransactionManager.Cancel();
return AddFieldTo(id);
}
Services.Notifier.Information(T("The \"{0}\" field has been added.", viewModel.DisplayName));
Services.Notifier.Information(T("The \"{0}\" field has been added.", viewModel.Name));
if (typeViewModel != null)
return RedirectToAction("Edit", new { id });
if (typeViewModel != null) {
return RedirectToAction("Edit", new {id});
}
return RedirectToAction("EditPart", new { id });
}
public ActionResult EditField(string id, string name) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
var partViewModel = _contentDefinitionService.GetPart(id);
if (partViewModel == null) {
return HttpNotFound();
}
var fieldViewModel = partViewModel.Fields.FirstOrDefault(x => x.Name == name);
if(fieldViewModel == null) {
return HttpNotFound();
}
var viewModel = new EditFieldNameViewModel {
Name = fieldViewModel.Name,
DisplayName = fieldViewModel.DisplayName
};
return View(viewModel);
}
[HttpPost, ActionName("EditField")]
[FormValueRequired("submit.Save")]
public ActionResult EditFieldPOST(string id, EditFieldNameViewModel viewModel) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
if (viewModel == null)
return HttpNotFound();
var partViewModel = _contentDefinitionService.GetPart(id);
if (partViewModel == null) {
return HttpNotFound();
}
// prevent null reference exception in validation
viewModel.DisplayName = viewModel.DisplayName ?? String.Empty;
// remove extra spaces
viewModel.DisplayName = viewModel.DisplayName.Trim();
if (String.IsNullOrWhiteSpace(viewModel.DisplayName)) {
ModelState.AddModelError("DisplayName", T("The Display Name name can't be empty.").ToString());
}
if (_contentDefinitionService.GetPart(partViewModel.Name).Fields.Any(t => t.Name != viewModel.Name && String.Equals(t.DisplayName.Trim(), viewModel.DisplayName.Trim(), StringComparison.OrdinalIgnoreCase))) {
ModelState.AddModelError("DisplayName", T("A field with the same Display Name already exists.").ToString());
}
if (!ModelState.IsValid) {
return View(viewModel);
}
var field = _contentDefinitionManager.GetPartDefinition(id).Fields.FirstOrDefault(x => x.Name == viewModel.Name);
if(field == null) {
return HttpNotFound();
}
field.DisplayName = viewModel.DisplayName;
_contentDefinitionManager.StorePartDefinition(partViewModel._Definition);
Services.Notifier.Information(T("Display name changed to {0}.", viewModel.DisplayName));
// redirect to the type editor if a type exists with this name
var typeViewModel = _contentDefinitionService.GetType(id);
if (typeViewModel != null) {
return RedirectToAction("Edit", new { id });
}
return RedirectToAction("EditPart", new { id });
}
public ActionResult RemoveFieldFrom(string id) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content part.")))

View File

@@ -14,6 +14,7 @@
<AssemblyName>Orchard.ContentTypes</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>false</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -57,6 +58,7 @@
<Compile Include="Services\ContentDefinitionService.cs" />
<Compile Include="Services\IContentDefinitionService.cs" />
<Compile Include="ViewModels\AddFieldViewModel.cs" />
<Compile Include="ViewModels\EditFieldNameViewModel.cs" />
<Compile Include="ViewModels\CreatePartViewModel.cs" />
<Compile Include="ViewModels\EditFieldViewModel.cs" />
<Compile Include="ViewModels\EditPartFieldViewModel.cs" />
@@ -115,6 +117,9 @@
<ItemGroup>
<Content Include="web.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Admin\EditField.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
@@ -9,6 +8,7 @@ using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentTypes.ViewModels;
using Orchard.Core.Contents.Extensions;
using Orchard.Localization;
using Orchard.Utility.Extensions;
namespace Orchard.ContentTypes.Services {
public class ContentDefinitionService : IContentDefinitionService {
@@ -72,7 +72,7 @@ namespace Orchard.ContentTypes.Services {
name = GenerateContentTypeNameFromDisplayName(displayName);
}
else {
if(!IsLetter(name[0])) {
if(!name[0].IsLetter()) {
throw new ArgumentException("Content type name must start with a letter", "name");
}
}
@@ -219,7 +219,7 @@ namespace Orchard.ContentTypes.Services {
}
public void AddFieldToPart(string fieldName, string fieldTypeName, string partName) {
fieldName = SafeName(fieldName);
fieldName = fieldName.ToSafeName();
if (string.IsNullOrEmpty(fieldName)) {
throw new OrchardException(T("Fields must have a name containing no spaces or symbols."));
}
@@ -231,35 +231,44 @@ namespace Orchard.ContentTypes.Services {
_contentDefinitionManager.AlterPartDefinition(partName, typeBuilder => typeBuilder.RemoveField(fieldName));
}
private static string SafeName(string name) {
if (string.IsNullOrWhiteSpace(name))
return String.Empty;
var dissallowed = new Regex(@"[/:?#\[\]@!$&'()*+,;=\s\""<>]+");
name = dissallowed.Replace(name, String.Empty);
name = name.Trim();
// don't allow non A-Z chars as first letter, as they are not allowed in prefixes
while(name.Length > 0 && !IsLetter(name[0])) {
name = name.Substring(1);
}
if (name.Length > 128)
name = name.Substring(0, 128);
return name;
}
public static bool IsLetter(char c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
//gratuitously stolen from the RoutableService
public string GenerateContentTypeNameFromDisplayName(string displayName) {
displayName = SafeName(displayName);
displayName = displayName.ToSafeName();
while ( _contentDefinitionManager.GetTypeDefinition(displayName) != null )
while (_contentDefinitionManager.GetTypeDefinition(displayName) != null)
displayName = VersionName(displayName);
return displayName;
}
public string GenerateFieldNameFromDisplayName(string partName, string displayName) {
IEnumerable<ContentPartFieldDefinition> fieldDefinitions;
var part = _contentDefinitionManager.GetPartDefinition(partName);
displayName = displayName.ToSafeName();
if(part == null) {
var type = _contentDefinitionManager.GetTypeDefinition(partName);
if(type == null) {
throw new ArgumentException("The part doesn't exist: " + partName);
}
var typePart = type.Parts.FirstOrDefault(x => x.PartDefinition.Name == partName);
// id passed in might be that of a type w/ no implicit field
if(typePart == null) {
return displayName;
}
else {
fieldDefinitions = typePart.PartDefinition.Fields.ToArray();
}
}
else {
fieldDefinitions = part.Fields.ToArray();
}
while (fieldDefinitions.Any(x => String.Equals(displayName.Trim(), x.Name.Trim(), StringComparison.OrdinalIgnoreCase)))
displayName = VersionName(displayName);
return displayName;

View File

@@ -14,6 +14,7 @@ namespace Orchard.ContentTypes.Services {
void AddPartToType(string partName, string typeName);
void RemovePartFromType(string partName, string typeName);
string GenerateContentTypeNameFromDisplayName(string displayName);
string GenerateFieldNameFromDisplayName(string partName, string displayName);
IEnumerable<EditPartViewModel> GetParts(bool metadataPartsOnly);
EditPartViewModel GetPart(string name);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement.MetaData;
namespace Orchard.ContentTypes.ViewModels {
@@ -7,9 +8,32 @@ namespace Orchard.ContentTypes.ViewModels {
Fields = new List<ContentFieldInfo>();
}
/// <summary>
/// The technical name of the field
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// The display name of the field
/// </summary>
[Required]
public string DisplayName { get; set; }
/// <summary>
/// The selected field type
/// </summary>
[Required]
public string FieldTypeName { get; set; }
/// <summary>
/// The part to add the field to
/// </summary>
public EditPartViewModel Part { get; set; }
/// <summary>
/// List of the available Field types
/// </summary>
public IEnumerable<ContentFieldInfo> Fields { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement.MetaData;
namespace Orchard.ContentTypes.ViewModels {
public class EditFieldNameViewModel {
/// <summary>
/// The technical name of the field
/// </summary>
public string Name { get; set; }
/// <summary>
/// The display name of the field
/// </summary>
[Required]
public string DisplayName { get; set; }
}
}

View File

@@ -12,6 +12,7 @@ namespace Orchard.ContentTypes.ViewModels {
public EditPartFieldViewModel(int index, ContentPartFieldDefinition field) {
Index = index;
Name = field.Name;
DisplayName = field.DisplayName;
FieldDefinition = new EditFieldViewModel(field.FieldDefinition);
Settings = field.Settings;
_Definition = field;
@@ -22,6 +23,7 @@ namespace Orchard.ContentTypes.ViewModels {
public EditPartViewModel Part { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public IEnumerable<TemplateViewModel> Templates { get; set; }
public EditFieldViewModel FieldDefinition { get; set; }
public SettingsDictionary Settings { get; set; }

View File

@@ -1,3 +1,4 @@
@using Orchard.Utility.Extensions
@model Orchard.ContentTypes.ViewModels.AddFieldViewModel
@{
Style.Require("ContentTypesAdmin");
@@ -8,15 +9,61 @@
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<fieldset>
<label for="DisplayName">@T("Name")</label>
<label for="DisplayName">@T("Display Name")</label>
@Html.TextBoxFor(m => m.DisplayName, new {@class = "textMedium", autofocus = "autofocus"})
<span class="hint">@T("Use no spaces or special characters. For example, \"MyCustomField\"")</span>
<span class="hint">@T("Name of the field as it will be displayed in screens.")</span>
<label for="Name">@T("Technical Name")</label>
@Html.TextBoxFor(m => m.Name, new {@class = "text"})
<span class="hint">@T("Technical name of the field.")</span>
</fieldset>
<fieldset>
<label for="FieldTypeName">@T("Field Type")</label>
@Html.DropDownListFor(m => m.FieldTypeName, new SelectList(Model.Fields, "FieldTypeName", "FieldTypeName"))
@Html.DropDownListFor(m => m.FieldTypeName, new SelectList(Model.Fields.Select(x => new { x.FieldTypeName, DisplayName = x.FieldTypeName.CamelFriendly()}), "FieldTypeName", "DisplayName"))
</fieldset>
<fieldset>
<button class="primaryAction" type="submit">@T("Save")</button>
</fieldset>
}
@using(Script.Foot()){
<script type="text/javascript">
//<![CDATA[
$(function(){
var $name = $("#@Html.FieldIdFor(m=>m.Name)");
var $displayName = $("#@Html.FieldIdFor(m=>m.DisplayName)");
var partName = "@Model.Part.Name";
var jsonUrl = "@Url.Action("FieldName", "Admin", new RouteValueDictionary { {"Area","Orchard.ContentTypes"} } )";
var nameAltered;
$name.keypress(function() {
nameAltered = true;
});
var compute = function() {
// stop processing automatically if altered by the user
if(nameAltered) {
return true;
}
$.post(jsonUrl, {
partName: partName,
displayName: $displayName.val(),
__RequestVerificationToken: $("input[name=__RequestVerificationToken]").val()
},
function(data) {
$name.val(data);
},
"json"
);
};
//pull technical name input from tab order
$name.attr("tabindex", -1);
$displayName.keyup(compute);
$displayName.blur(compute);
})
//]]>
</script>
}

View File

@@ -7,8 +7,10 @@
<fieldset>
<label for="DisplayName">@T("Display Name")</label>
@Html.TextBoxFor(m => m.DisplayName, new {@class = "textMedium", autofocus = "autofocus"})
<span class="hint">@T("Name of the type as it will be displayed in screens.")</span>
<label for="Name">@T("Content Type Id")</label>
@Html.TextBoxFor(m => m.Name, new {@class = "text"})
<span class="hint">@T("Technical name of the type.")</span>
</fieldset>
<fieldset>
<button class="primaryAction" type="submit">@T("Create")</button>
@@ -18,24 +20,37 @@
<script type="text/javascript">
//<![CDATA[
$(function(){
//pull slug input from tab order
$("#@Html.FieldIdFor(m=>m.Name)").attr("tabindex",-1);
$("#@Html.FieldIdFor(m=>m.DisplayName)").blur(function(){
var name = $("#@Html.FieldIdFor(m=>m.Name)");
if (name.val()) { return true; }
var displayName = $("#@Html.FieldIdFor(m=>m.DisplayName)").val();
jQuery.post(
"@Url.Action("ContentTypeName","Admin",new RouteValueDictionary{{"Area","Orchard.ContentTypes"}})",
{
displayName: $("#@Html.FieldIdFor(m=>m.DisplayName)").val(),
var $name = $("#@Html.FieldIdFor(m=>m.Name)");
var $displayName = $("#@Html.FieldIdFor(m=>m.DisplayName)");
var jsonUrl = "@Url.Action("ContentTypeName", "Admin", new RouteValueDictionary { {"Area","Orchard.ContentTypes"} } )";
var nameAltered;
$name.keypress(function() {
nameAltered = true;
});
var compute = function() {
// stop processing automatically if altered by the user
if(nameAltered) {
return true;
}
$.post(jsonUrl, {
displayName: $displayName.val(),
__RequestVerificationToken: $("input[name=__RequestVerificationToken]").val()
},
function(data) {
name.val(data);
$name.val(data);
},
"json"
);
})
};
//pull technical name input from tab order
$name.attr("tabindex", -1);
$displayName.keyup(compute);
$displayName.blur(compute);
})
//]]>
</script>

View File

@@ -0,0 +1,21 @@
@using Orchard.Utility.Extensions
@model Orchard.ContentTypes.ViewModels.EditFieldNameViewModel
@{
Style.Require("ContentTypesAdmin");
Layout.Title = T("Edit Field \"{0}\"", Model.Name).ToString();
}
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<fieldset>
<label for="DisplayName">@T("Display Name")</label>
@Html.TextBoxFor(m => m.DisplayName, new {@class = "textMedium", autofocus = "autofocus"})
<span class="hint">@T("Name of the field as it will be displayed in screens.")</span>
@Html.HiddenFor(m => m.Name)
</fieldset>
<fieldset>
<button class="primaryAction" type="submit" name="submit.Save" value="Save">@T("Save")</button>
</fieldset>
}

View File

@@ -1,7 +1,8 @@
@model Orchard.ContentTypes.ViewModels.EditPartFieldViewModel
<fieldset class="manage-field">
<h3>@Model.Name <span>(@Model.FieldDefinition.Name)</span></h3>
<h3>@Model.DisplayName <span>(@Model.FieldDefinition.Name)</span></h3>
<div class="manage">
@Html.ActionLink(T("Edit").Text, "EditField", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }, new { itemprop = "RemoveUrl UnsafeUrl" }) |
@Html.ActionLink(T("Remove").Text, "RemoveFieldFrom", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }, new { itemprop = "RemoveUrl UnsafeUrl" }) @* <- some experimentation *@
</div>
<div class="details">

View File

@@ -4,6 +4,7 @@ using Orchard.ContentManagement.MetaData.Models;
namespace Orchard.ContentManagement {
public class ContentField {
public string Name { get { return PartFieldDefinition.Name; } }
public string DisplayName { get { return PartFieldDefinition.DisplayName; } }
public ContentPartFieldDefinition PartFieldDefinition { get; set; }
public ContentFieldDefinition FieldDefinition { get { return PartFieldDefinition.FieldDefinition; } }

View File

@@ -14,6 +14,11 @@ namespace Orchard.ContentManagement.MetaData.Builders {
return this;
}
public ContentPartFieldDefinitionBuilder WithDisplayName(string displayName) {
_settings[ContentPartFieldDefinition.DisplayNameKey] = displayName;
return this;
}
public abstract string Name { get; }
public abstract string FieldType { get; }

View File

@@ -1,5 +1,9 @@
namespace Orchard.ContentManagement.MetaData.Models {
using Orchard.Utility.Extensions;
namespace Orchard.ContentManagement.MetaData.Models {
public class ContentPartFieldDefinition {
public const string DisplayNameKey = "DisplayName";
public ContentPartFieldDefinition(string name) {
Name = name;
FieldDefinition = new ContentFieldDefinition(null);
@@ -12,6 +16,15 @@
}
public string Name { get; private set; }
public string DisplayName {
get {
// if none is defined, generate one from the technical name
return Settings.ContainsKey(DisplayNameKey) ? Settings[DisplayNameKey] : Name.CamelFriendly();
}
set { Settings[DisplayNameKey] = value; }
}
public ContentFieldDefinition FieldDefinition { get; private set; }
public SettingsDictionary Settings { get; private set; }
}

View File

@@ -1,16 +1,20 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Orchard.Localization;
namespace Orchard.Utility.Extensions {
public static class StringExtensions {
private static readonly Regex humps = new Regex("(?:^[a-zA-Z][^A-Z]*|[A-Z][^A-Z]*)");
private static readonly Regex safe = new Regex(@"[^_\-a-zA-Z]+");
public static string CamelFriendly(this string camel) {
if (string.IsNullOrWhiteSpace(camel))
if (String.IsNullOrWhiteSpace(camel))
return "";
var matches = humps.Matches(camel).OfType<Match>().Select(m => m.Value);
var matches = humps.Matches(camel).OfType<Match>().Select(m => m.Value).ToArray();
return matches.Any()
? matches.Aggregate((a, b) => a + " " + b).TrimStart(' ')
: camel;
@@ -21,7 +25,7 @@ namespace Orchard.Utility.Extensions {
}
public static string Ellipsize(this string text, int characterCount, string ellipsis, bool wordBoundary = false) {
if (string.IsNullOrWhiteSpace(text))
if (String.IsNullOrWhiteSpace(text))
return "";
if (characterCount < 0 || text.Length <= characterCount)
@@ -37,7 +41,7 @@ namespace Orchard.Utility.Extensions {
}
public static string HtmlClassify(this string text) {
if (string.IsNullOrWhiteSpace(text))
if (String.IsNullOrWhiteSpace(text))
return "";
var friendlier = text.CamelFriendly();
@@ -45,20 +49,20 @@ namespace Orchard.Utility.Extensions {
}
public static LocalizedString OrDefault(this string text, LocalizedString defaultValue) {
return string.IsNullOrEmpty(text)
return String.IsNullOrEmpty(text)
? defaultValue
: new LocalizedString(text);
}
public static string RemoveTags(this string html) {
return string.IsNullOrEmpty(html)
return String.IsNullOrEmpty(html)
? ""
: Regex.Replace(html, "<[^<>]*>", "", RegexOptions.Singleline);
}
// not accounting for only \r (e.g. Apple OS 9 carriage return only new lines)
public static string ReplaceNewLinesWith(this string text, string replacement) {
return string.IsNullOrWhiteSpace(text)
return String.IsNullOrWhiteSpace(text)
? ""
: Regex.Replace(text, @"(\r?\n)", replacement, RegexOptions.Singleline);
}
@@ -88,5 +92,52 @@ namespace Orchard.Utility.Extensions {
return Regex.IsMatch(segment, @"^[^/?#[\]@""^{}|`<>\s]+$");
}
/// <summary>
/// Generates a valid technical name.
/// </summary>
/// <remarks>
/// Uses a white list set of chars.
/// </remarks>
public static string ToSafeName(this string name) {
if (String.IsNullOrWhiteSpace(name))
return String.Empty;
name = RemoveDiacritics(name);
name = safe.Replace(name, String.Empty);
name = name.Trim();
// don't allow non A-Z chars as first letter, as they are not allowed in prefixes
while (name.Length > 0 && !IsLetter(name[0])) {
name = name.Substring(1);
}
if (name.Length > 128)
name = name.Substring(0, 128);
return name;
}
/// <summary>
/// Whether the char is a letter between A and Z or not
/// </summary>
public static bool IsLetter(this char c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
public static string RemoveDiacritics(string name) {
string stFormD = name.Normalize(NormalizationForm.FormD);
var sb = new StringBuilder();
foreach (char t in stFormD) {
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(t);
if (uc != UnicodeCategory.NonSpacingMark) {
sb.Append(t);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
}
}