Adding Media Gallery Field

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2013-02-01 11:55:35 -08:00
parent a5763400f3
commit 5c1d767a95
13 changed files with 537 additions and 1 deletions

View File

@@ -0,0 +1,109 @@
using System;
using Orchard.Fields.Settings;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Fields.Fields;
using Orchard.Fields.ViewModels;
using Orchard.Localization;
using Orchard.Services;
using Orchard.Utility.Extensions;
namespace Orchard.Fields.Drivers {
public class MediaGalleryFieldDriver : ContentFieldDriver<MediaGalleryField> {
private readonly IJsonConverter _jsonConverter;
public MediaGalleryFieldDriver(IJsonConverter jsonConverter) {
_jsonConverter = jsonConverter;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
private static string GetPrefix(MediaGalleryField field, ContentPart part) {
return part.PartDefinition.Name + "." + field.Name;
}
private static string GetDifferentiator(MediaGalleryField field, ContentPart part) {
return field.Name;
}
protected override DriverResult Display(ContentPart part, MediaGalleryField field, string displayType, dynamic shapeHelper) {
return Combined(
ContentShape("Fields_MediaGallery", GetDifferentiator(field, part), () => shapeHelper.Fields_ContentPicker()),
ContentShape("Fields_MediaGallery_SummaryAdmin", GetDifferentiator(field, part), () => shapeHelper.Fields_MediaGallery_SummaryAdmin())
);
}
protected override DriverResult Editor(ContentPart part, MediaGalleryField field, dynamic shapeHelper) {
return ContentShape("Fields_MediaGallery_Edit", GetDifferentiator(field, part),
() => {
var model = new MediaGalleryFieldViewModel {
Field = field,
Items = field.Items,
SelectedItems = field.SelectedItems
};
model.SelectedItems = string.Concat(",", field.Items);
return shapeHelper.EditorTemplate(TemplateName: "Fields/MediaGallery.Edit", Model: model, Prefix: GetPrefix(field, part));
});
}
protected override DriverResult Editor(ContentPart part, MediaGalleryField field, IUpdateModel updater, dynamic shapeHelper) {
var model = new MediaGalleryFieldViewModel();
updater.TryUpdateModel(model, GetPrefix(field, part), null, null);
var settings = field.PartFieldDefinition.Settings.GetModel<MediaGalleryFieldSettings>();
if (String.IsNullOrEmpty(model.SelectedItems)) {
field.SelectedItems = "[]";
}
else {
field.SelectedItems = model.SelectedItems;
}
var allItems = _jsonConverter.Deserialize<MediaGalleryItem[]>(field.SelectedItems);
if (settings.Required && allItems.Length == 0) {
updater.AddModelError("SelectedItems", T("The field {0} is mandatory", field.Name.CamelFriendly()));
}
if (!settings.Multiple && allItems.Length > 1) {
updater.AddModelError("SelectedItems", T("The field {0} doesn't accept multiple media items", field.Name.CamelFriendly()));
}
return Editor(part, field, shapeHelper);
}
//protected override void Importing(ContentPart part, Fields.MediaGalleryField field, ImportContentContext context) {
// var contentItemIds = context.Attribute(field.FieldDefinition.Name + "." + field.Name, "ContentItems");
// if (contentItemIds != null) {
// field.Ids = contentItemIds.Split(',')
// .Select(context.GetItemFromSession)
// .Select(contentItem => contentItem.Id).ToArray();
// }
// else {
// field.Ids = new int[0];
// }
//}
//protected override void Exporting(ContentPart part, Fields.MediaGalleryField field, ExportContentContext context) {
// if (field.Ids.Any()) {
// var contentItemIds = field.Ids
// .Select(x => _contentManager.Get(x))
// .Select(x => _contentManager.GetItemMetadata(x).Identity.ToString())
// .ToArray();
// context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("ContentItems", string.Join(",", contentItemIds));
// }
//}
protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Items"), T("A Json serialized list of the media."));
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Utilities;
using Orchard.ContentManagement.FieldStorage;
namespace Orchard.Fields.Fields {
public class MediaGalleryField : ContentField {
internal LazyField<ICollection<MediaGalleryItem>> _mediaGalleryItems = new LazyField<ICollection<MediaGalleryItem>>();
public ICollection<MediaGalleryItem> Items { get { return _mediaGalleryItems.Value ?? new MediaGalleryItem[0]; } }
public string SelectedItems {
get { return Storage.Get<string>(); }
set { Storage.Set(value); }
}
}
public class MediaGalleryItem {
public string Url { get; set; }
public string AlternateText { get; set; }
public string Class { get; set; }
public string Style { get; set; }
public string Alignment { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
using System.Linq;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.Fields.Fields;
using Orchard.Services;
namespace Orchard.Fields.Handlers {
public class MediaGalleryFieldHandler : ContentHandler {
private readonly IJsonConverter _jsonConverter;
private readonly IContentDefinitionManager _contentDefinitionManager;
public MediaGalleryFieldHandler(
IJsonConverter jsonConverter,
IContentDefinitionManager contentDefinitionManager) {
_jsonConverter = jsonConverter;
_contentDefinitionManager = contentDefinitionManager;
}
protected override void Loading(LoadContentContext context) {
base.Loading(context);
var fields = context.ContentItem.Parts.SelectMany(x => x.Fields.Where(f => f.FieldDefinition.Name == typeof (MediaGalleryField).Name)).Cast<MediaGalleryField>();
// define lazy initializer for MediaGalleryField.Items
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(context.ContentType);
if (contentTypeDefinition == null) {
return;
}
foreach (var field in fields) {
var localField = field;
field._mediaGalleryItems.Loader(x => _jsonConverter.Deserialize<MediaGalleryItem[]>(localField.SelectedItems) ?? new MediaGalleryItem[0]);
}
}
}
}

View File

@@ -72,6 +72,10 @@
<Content Include="Views\Web.config" />
<Content Include="Scripts\Web.config" />
<Content Include="Styles\Web.config" />
<Compile Include="Drivers\MediaGalleryFieldDriver.cs" />
<Compile Include="Drivers\MediaPickerFieldDriver.cs" />
<Compile Include="Fields\MediaGalleryField.cs" />
<Compile Include="Handlers\MediaGalleryFieldHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
@@ -114,7 +118,6 @@
<ItemGroup>
<Compile Include="Drivers\BooleanFieldDriver.cs" />
<Compile Include="Drivers\InputFieldDriver.cs" />
<Compile Include="Drivers\MediaPickerFieldDriver.cs" />
<Compile Include="Drivers\NumericFieldDriver.cs" />
<Compile Include="Drivers\DateTimeFieldDriver.cs" />
<Compile Include="Drivers\EnumerationFieldDriver.cs" />
@@ -128,6 +131,8 @@
<Compile Include="Fields\LinkField.cs" />
<Compile Include="Settings\BooleanFieldEditorEvents.cs" />
<Compile Include="Settings\BooleanFieldSettings.cs" />
<Compile Include="Settings\MediaGalleryFieldEditorEvents.cs" />
<Compile Include="Settings\MediaGalleryFieldSettings.cs" />
<Compile Include="Settings\InputFieldEditorEvents.cs" />
<Compile Include="Settings\InputFieldSettings.cs" />
<Compile Include="Settings\MediaPickerFieldEditorEvents.cs" />
@@ -141,6 +146,7 @@
<Compile Include="Settings\EnumerationFieldEditorEvents.cs" />
<Compile Include="Settings\EnumerationFieldSettings.cs" />
<Compile Include="Tokens\FieldTokens.cs" />
<Compile Include="ViewModels\MediaGalleryFieldViewModel.cs" />
<Compile Include="ViewModels\NumericFieldViewModel.cs" />
<Compile Include="ViewModels\DateTimeFieldViewModel.cs" />
</ItemGroup>
@@ -160,6 +166,18 @@
<ItemGroup>
<Content Include="Views\Fields\Input.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\DefinitionTemplates\MediaGalleryFieldSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Fields\MediaGallery.Edit.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Fields\MediaGallery.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Fields\MediaGallery.SummaryAdmin.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -27,13 +27,20 @@
<Place Fields_MediaPicker_Edit="Content:2.3"/>
<Place Fields_MediaPicker="Content:after"/>
<!-- MediaGallery -->
<Place Fields_MediaGallery_Edit="Content:2.3"/>
<Place Fields_MediaGallery="Content:after"/>
<Match DisplayType="SummaryAdmin">
<Place Fields_DateTime="Content"/>
<Place Fields_MediaPicker="-"/>
<Place Fields_MediaGallery="-"/>
<Place Fields_MediaGallery_SummaryAdmin="Content:after"/>
</Match>
<Match DisplayType="Summary">
<Place Fields_MediaPicker="-"/>
<Place Fields_MediaGallery="-"/>
</Match>
</Placement>

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;
namespace Orchard.Fields.Settings {
public class MediaGalleryFieldEditorEvents : ContentDefinitionEditorEventsBase {
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
if (definition.FieldDefinition.Name == "MediaGalleryField") {
var model = definition.Settings.GetModel<MediaGalleryFieldSettings>();
yield return DefinitionTemplate(model);
}
}
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
if (builder.FieldType != "MediaGalleryField") {
yield break;
}
var model = new MediaGalleryFieldSettings();
if (updateModel.TryUpdateModel(model, "MediaGalleryFieldSettings", null, null)) {
builder.WithSetting("MediaGalleryFieldSettings.Hint", model.Hint);
builder.WithSetting("MediaGalleryFieldSettings.AllowedExtensions", model.AllowedExtensions);
builder.WithSetting("MediaGalleryFieldSettings.Required", model.Required.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("MediaGalleryFieldSettings.Multiple", model.Multiple.ToString(CultureInfo.InvariantCulture));
}
yield return DefinitionTemplate(model);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Orchard.Fields.Settings {
public class MediaGalleryFieldSettings {
public string Hint { get; set; }
public string AllowedExtensions { get; set; }
public bool Required { get; set; }
public bool Multiple { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
table.media-gallery tr td:nth-child(1) {
background: url(images/move.gif) no-repeat 12px 14px;
cursor: move;
}
.media-gallery-message {
display: none;
}
.ui-sortable-helper .media-gallery-remove {
display: none;
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Orchard.Fields.Fields;
namespace Orchard.Fields.ViewModels {
public class MediaGalleryFieldViewModel {
public ICollection<MediaGalleryItem> Items { get; set; }
public string SelectedItems { get; set; }
public MediaGalleryField Field { get; set; }
}
}

View File

@@ -0,0 +1,33 @@
@model Orchard.Fields.Settings.MediaGalleryFieldSettings
<fieldset>
<div>
@Html.CheckBoxFor(m => m.Required) <label for="@Html.FieldIdFor(m => m.Required)" class="forcheckbox">@T("A media is required")</label>
<span class="hint">@T("Check to ensure the user is providing at least one media.")</span>
</div>
</fieldset>
<fieldset>
<div>
<input name="ext-@Html.FieldNameFor(m => m)" id="ext-@Html.FieldIdFor(m => m)" type="checkbox" @if (!String.IsNullOrWhiteSpace(Model.AllowedExtensions)) { <text>checked="checked"</text> } />
<label for="ext-@Html.FieldIdFor(m => m)" class="forcheckbox">@T("Allow only specific extensions")</label>
</div>
<div data-controllerid="ext-@Html.FieldIdFor(m => m)">
<div>
@Html.TextBoxFor(m => m.AllowedExtensions, new { @class = "textMedium" })
<span class="hint">@T("You can define a set of extensions the user will be able to pick, separated by spaces, e.g. jpg png gif")</span>
<span class="hint">@T("Leave it empty if you don't want to apply any restriction.")</span>
</div>
</div>
</fieldset>
<fieldset>
<div>
@Html.CheckBoxFor(m => m.Multiple) <label for="@Html.FieldIdFor(m => m.Multiple)" class="forcheckbox">@T("Allow multiple media items")</label>
<span class="hint">@T("Check to allow the user to select multiple media items.")</span>
</div>
</fieldset>
<fieldset>
<label for="@Html.FieldIdFor(m => m.Hint)">@T("Help text")</label>
@Html.TextAreaFor(m => m.Hint, new { @class = "textMedium", rows = "5" } )
<span class="hint">@T("The help text is written under the field when authors are selecting media items.")</span>
@Html.ValidationMessageFor(m => m.Hint)
</fieldset>

View File

@@ -0,0 +1,193 @@
@model MediaGalleryFieldViewModel
@using Orchard.Fields.Settings;
@{
Style.Include("media-gallery-admin");
Script.Require("jQueryUI_Sortable").AtFoot();
var settings = Model.Field.PartFieldDefinition.Settings.GetModel<MediaGalleryFieldSettings>();
var descriminator = Html.FieldIdFor(m => m.Field.Items);
}
<fieldset>
<label @if(settings.Required) { <text>class="required"</text> }>@Model.Field.DisplayName</label>
<span class="hint">@settings.Hint</span>
<div id="save-message-@descriminator" class="message message-Warning media-gallery-message">@T("You need to save your changes.")</div>
<table id="media-gallery-@descriminator" class="items media-gallery" summary="@Model.Field.DisplayName">
<colgroup>
<col id="Col1" style="width:20px" />
<col id="Col2" />
<col id="Col3" />
</colgroup>
<thead>
<tr>
<th scope="col" >&nbsp;&darr;</th>
<th scope="col">@T("Media")</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
@foreach (var media in Model.Items) {
<tr>
<td>&nbsp;</td>
<td>
<span data-url="@media.Url" data-alt="@media.AlternateText" data-class="@media.Class" data-style="@media.Style" data-align="@media.Alignment" data-width="@media.Width" data-height="@media.Height" data-fieldid="@descriminator" class="media-gallery-item"><a title="@T("Edit")" class="media-gallery-edit" href="#">@media.Url</a></span>
</td>
<td>
<span data-url="@media.Url" class="media-gallery-remove button grey">@T("Remove")</span>
</td>
</tr>
}
</tbody>
</table>
<span id="btn-@descriminator" class="button">@T("Add")</span>
@Html.HiddenFor(m => m.SelectedItems)
</fieldset>
@using (Script.Foot()) {
<script type="text/javascript">
//<![CDATA[
(function($) {
var required = @(settings.Required ? "true" : "false");
var multiple = @(settings.Multiple ? "true" : "false");
var addButton = $('#btn-@descriminator');
var @(descriminator)_Template = '<tr><td>&nbsp;</td><td><span data-url="{url}" data-alt="{alt}" data-class="{class}" data-style="{style}" data-align="{align}" data-width="{width}" data-height="{height}" data-fieldid="@descriminator" class="media-gallery-item"><a title="@T("Edit")" class="media-gallery-edit" href="#">{url}</a></span></td><td><span class="media-gallery-remove button grey">@T("Remove")</span></td></tr>';
var refreshIds = function() {
var id = $('#@Html.FieldIdFor(m => m.SelectedItems)');
var selectedItems = [];
$("span[data-fieldid = @descriminator]").each(function() {
selectedItems.push(extractModel(this));
});
id.val(JSON.stringify(selectedItems));
// can new items be added ?
if(!multiple && selectedItems.length > 0) {
addButton.hide();
}
else {
addButton.show();
}
};
refreshIds();
// adding a new item
addButton.click(function() {
addButton.trigger("orchard-admin-pickimage-open", {
callback: function(data) {
var template = @(descriminator)_Template
.replace( /\{url\}/g , formatUrl(data.img.src))
.replace( /\{alt\}/g , data.img.alt)
.replace( /\{class\}/g , data.img['class'])
.replace( /\{style\}/g , data.img.style)
.replace( /\{align\}/g , data.img.align)
.replace( /\{width\}/g , data.img.width || '0')
.replace( /\{height\}/g , data.img.height || '0');
var content = $(template);
$('#media-gallery-@descriminator tbody').append(content);
refreshIds();
$('#save-message-@descriminator').show();
},
baseUrl: '@Url.Content("~/")'
});
});
// editing an item
$(".media-gallery-edit").live("click", function() {
var img = extractModel($(this).parent());
if (img.Url && img.Url.length > 2 && img.Url.substr(0, 2) == "~/") {
img.Url = '@Url.Content("~/")' + img.Url.substr(2);
}
var tr = $(this).closest('tr');
$(this).trigger("orchard-admin-pickimage-open", {
img: {
src: img.Url,
alt: img.AlternateText,
'class': img.Class,
style: img.Style,
align: img.Alignment,
width: img.Width,
height: img.Height
},
baseUrl: '@Url.Content("~/")',
//uploadMediaPath: 'images',
callback: function (data) {
var template = @(descriminator)_Template
.replace( /\{url\}/g , formatUrl(data.img.src))
.replace( /\{alt\}/g , data.img.alt)
.replace( /\{class\}/g , data.img['class'])
.replace( /\{style\}/g , data.img.style)
.replace( /\{align\}/g , data.img.align)
.replace( /\{width\}/g , data.img.width || '0')
.replace( /\{height\}/g , data.img.height || '0');
var content = $(template);
tr.after(content);
tr.remove();
refreshIds();
$('#save-message-@descriminator').show();
}
});
});
$('#media-gallery-@descriminator .media-gallery-remove').live("click", function() {
$(this).closest('tr').remove();
refreshIds();
$('#save-message-@descriminator').show();
});
$("#media-gallery-@descriminator tbody").sortable({
handle: 'td:first',
stop: function(event, ui) {
refreshIds();
$('#save-message-@descriminator').show();
}
}).disableSelection();
function formatUrl(url) {
var applicationPath = '@HttpUtility.JavaScriptStringEncode(Url.RequestContext.HttpContext.Request.ApplicationPath.ToLower())';
if (!/\/$/.test(applicationPath)) {
applicationPath += '/';
}
if (url.substr(0, 4) != "http") {
return '~/' + url.substr(applicationPath.length);
}
return url;
}
function extractModel(dom) {
dom = $(dom);
return {
Url: dom.data('url'),
AlternateText: dom.data('alt'),
Class: dom.data('class'),
Style: dom.data('style'),
Alignment: dom.data('align'),
Height: dom.data('height'),
Width: dom.data('width')
};
}
})(jQuery);
//]]>
</script>
}

View File

@@ -0,0 +1,22 @@
@using Orchard.Utility.Extensions;
@{
var field = (MediaGalleryField) Model.ContentField;
string name = field.DisplayName;
var items = field.Items;
}
<p class="media-gallery-field media-gallery-field-@name.HtmlClassify()">
<span class="name">@name:</span>
@if (items.Any()) {
foreach (var item in items) {
<span class="value"><a href="@Url.Content(item.Url)">@item.Url</a></span>
if(item != items.Last()) {
<span>,</span>
}
}
}
else {
<span class="value">@T("No media.")</span>
}
</p>

View File

@@ -0,0 +1,23 @@
@using Orchard.Fields.Fields
@using Orchard.Utility.Extensions;
@{
var field = (MediaGalleryField) Model.ContentField;
string name = field.DisplayName;
var contentItems = field.ContentItems;
}
<p class="content-picker-field content-picker-field-@name.HtmlClassify()">
<span class="name">@name:</span>
@if(contentItems.Any()) {
foreach(var contentItem in contentItems) {
<span class="value">@Html.ItemDisplayLink(contentItem)</span>
if(contentItem != contentItems.Last()) {
<span>,</span>
}
}
}
else {
<span class="value">@T("No content items.")</span>
}
</p>