В очередной раз понадобилось создать модуль, на этот раз это кнопка «Поделиться» от сервиса яндекса Ya.Share.
В идеале хотелось бы, чтобы через панель управления можно было управлять отображаемыми сервисами, видом кнопки и языком. Дополнительно включать и отключать кнопку. Так как показываться кнопка должна только для определённых типов контента, то нужна она, соответственно, как часть контента (ContentPart) причём предусмотреть надо и тот вариант, что кнопок может быть несколько на странице, поэтому js от яндекса не должен дублироваться, а в кнопке принудительно должна указываться ссылка на запись и заголовок (title).
Шаги для создания модуля всё те же:
- Запускаем командную строку, переходим в папку сайта, на базе которого будет создавать модуль и пишем:
orchard.exe codegen module YandexShare
-
Добавляем migration.cs, в котором определим в дальнейшем какие таблицы нам понадобиться для хранения настроек:
orchard.exe codegen datamigration YandexShare
-
Открываем проект созданного модуля в студии и начинаем добавлять необходимый функционал
Прежде всего в папку Models добавляем классы: YandexShareSettingsRecord (настройки модуля), YandexShareRecord (класс пустышка), YandexSharePart (собственно, класс, экземпляр которого будет определять параметры кнопки).
public class YandexShareSettingsRecord
{
public virtual int Id { get; set; }
public virtual bool IsEnabled { get; set; }
public virtual string ShareType { get; set; }
public virtual string ShareLang { get; set; }
public virtual string ShareServices { get; set; }
}
public class YandexShareRecord: ContentPartRecord
{
}
public class YandexSharePart: ContentPart<YandexShareRecord>
{
public string Title { get; set; } // заголовок, который будет отправлен в сервис
public string Link { get; set; } // ссылка на целевую страницу
public string ShareServices { get; set; } // отображаемые сервисы
public string ShareLang { get; set; } // язык для отображения кнопки
public string ShareType { get; set; } // тип отображения кнопки
}
Раз классы определены, то можно их теперь отразить в базу, поэтому правим Migration.cs следюущим образом:
public int Create() {
SchemaBuilder.CreateTable("YandexShareRecord", table => table.ContentPartRecord());
SchemaBuilder.CreateTable("YandexShareSettingsRecord", table => table.Column("Id", DbType.Int32, column => column.Identity().PrimaryKey())
.Column("ShareType", DbType.String, column => column.NotNull().WithDefault("button"))
.Column("ShareLang", DbType.String, column => column.NotNull().WithDefault("ru"))
.Column("ShareServices", DbType.String, column => column.NotNull().WithDefault(""))
.Column("IsEnabled", DbType.Boolean, column=>column.NotNull().WithDefault(false)));
ContentDefinitionManager.AlterPartDefinition("YandexSharePart", cfg => cfg.Attachable());
return 1;
}
Теперь сделаем пункт меню и контроллер с вью, чтобы настраивать кнопку «Поделиться». Для этого добавим к проекту файл AdminMenu.cs следующего содержания:
public class AdminMenu
: INavigationProvider
{
Localizer T
{ get
; set
; }
public AdminMenu
()
{
T
= NullLocalizer
.Instance;
}
public void GetNavigation
(NavigationBuilder builder
)
{
builder
.Add(T
("YandexShare"),
"50", menu
=>menu
.Add(T
("YandexShare"),
"0", item
=>item
.Action("Index",
"Admin",
new { area
= "YandexShare"}).Permission(StandardPermissions
.SiteOwner)));
}
public string MenuName
{
get
{ return "admin"; }
}
}
В нём мы определили в каком меню будет показываться файл (в меню панели управления), отображаться он будет в 50 позиции, называться YandexShare и в качестве обработчика ссылки мы выставили наш контроллер Admin и его действие Index. Не забываем указывать area = имени модуля.
Добавим, кстати, контроллер
[ValidateInput
(false), Admin
]
public class AdminController
:Controller
{
IYandexShareService _yandexShareService
;
public AdminController
(IYandexShareService yandexShareService
)
{
_yandexShareService
= yandexShareService
;
}
[HttpGet
]
public ActionResult Index
()
{
YandexShareSettingsViewModel viewModel
= new YandexShareSettingsViewModel
();
viewModel
.Settings = _yandexShareService
.Get();
viewModel
.ShareServicesList = new List
<string>("yaru,vkontakte,facebook,twitter,odnoklassniki,moimir,lj,friendfeed,moikrug,blogger,digg,evernote,delicious,gbuzz,greader,juick,liveinternet,linkedin,myspace,yazakladki".Split(new char[] { ',' }, StringSplitOptions
.RemoveEmptyEntries));
Dictionary
<string,
string> langDict
= new Dictionary
<string,
string> { { "be",
"" },
{ "en",
"" },
{ "kk",
"" },
{ "ru",
"" },
{ "tt",
"" },
{ "uk",
"" }};
SelectList langList
= new SelectList
(langDict,
"Key",
"Key", viewModel
.Settings.ShareLang);
viewModel
.ShareLangList = langList
;
List
<string> typeList
= new List
<string> {"button",
"link",
"icon",
"none" };
viewModel
.ShareTypeList = new SelectList
(typeList, viewModel
.Settings.ShareType);
return View
(viewModel
);
}
[HttpPost
]
public ActionResult Index
(bool? IsEnabled,
string ShareLang,
string ShareType, FormCollection formCollection
)
{
_yandexShareService
.Set(IsEnabled
.HasValue ? IsEnabled
.Value : false, ShareLang, ShareType, formCollection
["ShareServices"]);
return RedirectToAction
("Index");
}
}
Итак, в конструктор контроллера передаётся экземпляр класс унаследованного от интерфейса IYandexShareService, этот интерфейс и производный класс будут описаны чуть ниже. Сейчас же нужно знать только то, что унаследованный класс позволяет работать с данными о параметрах кнопки.
В Action по GET-запросу создаётся экземпляр класса YandexShareSettingsViewModel
public class YandexShareSettingsViewModel
{
public YandexShareSettingsRecord Settings { get; set; } // настройки в том виде, в котором они есть
public SelectList ShareLangList { get; set; } // список поддерживаемых языков
public SelectList ShareTypeList { get; set; } // возможные типы отображения кнопки
public List<string> ShareServicesList { get; set; } // список доступных сервисов
}
Данный класс необходим лично мне для упрощения передачи данных во вью и их дальнейшего отображения.
В Action для POST-запроса просто напросто сохраняем данные полученные с формы на странице редактирования.
Кстати, вью для настроек:
@model YandexShare
.ViewModels.YandexShareSettingsViewModel
<h1
>@Html
.TitleForPage(@T
("Manage Ya.Share settings").ToString())</h1
>
@
using (Html
.BeginFormAntiForgeryPost())
{
<fieldset
>
<p
>
<label
for="">Enabled
</label
>
<input type
="checkbox" id
="IsEnabled" name
="IsEnabled" value
="true" @
if(Model
.Settings.IsEnabled){<text
>checked="checked"</text
>} />
</p
>
<p
>
<label
for="">Block type
:</label
>
@Html
.DropDownList("ShareType", Model
.ShareTypeList)
</p
>
<p
>
<label
for="">Block lang
:</label
>
@Html
.DropDownList("ShareLang", Model
.ShareLangList);
</p
>
<p
>
<label
for="">Choose services
:</label
>
@
foreach (string item
in Model
.ShareServicesList)
{
<input type
="checkbox" id
="ShareServices" name
="ShareServices" value
="@item" title
="@item" @
if (Model
.Settings.ShareServices.Contains(item
))
{<text
>checked="checked"</text
>} /><span
>@item
</span
><br
/>
}
</p
>
<input type
="submit" value
="Save settings" />
</fieldset
>
}
Теперь стоит всё же рассказать о IYandexShareService. На самом деле это всего лишь интерфейс, который обязывает «наследника» определить методы для получения и обновления настроек нашего модуля. Он чрезвычайно прост:
public interface IYandexShareService: IDependency
{
YandexShareSettingsRecord Get();
void Set(bool IsEnabled, string ShareLang, string ShareType, string ShareServices);
}
А наследующий данный интерфейс класс выглядит следующим образом:
public class YandexShareService
: IYandexShareService
{
IRepository
<YandexShareSettingsRecord
> _repository
;
ICacheManager _cacheManager
;
ISignals _signals
;
public YandexShareService
(IRepository
<YandexShareSettingsRecord
> repository, ICacheManager cacheManager, ISignals signals
)
{
_repository
= repository
;
_signals
= signals
;
_cacheManager
= cacheManager
;
}
public YandexShareSettingsRecord Get
()
{
var settings
= _cacheManager
.Get("YandexShareSettings", x
=>{x
.Monitor(_signals
.When("YandexShareSettingsChanged")); return _repository
.Table.FirstOrDefault();}); // получаем настройки с использованием кеширования
if(settings
==null) // если настроек пока не было произведено, создаём запись с настройками по умолчанию
{
_repository
.Create(new YandexShareSettingsRecord
{ IsEnabled
= false, ShareLang
= "ru", ShareType
= "button", ShareServices
= ""});
settings
= _repository
.Table.FirstOrDefault();
_signals
.Trigger("YandexShareSettingsChanged");
}
return settings
;
}
public void Set
(bool IsEnabled,
string ShareLang,
string ShareType,
string ShareServices
)
{
var settings
= Get
();
if (settings
!= null) // если есть настройки, то обновляем и сбрасываем кеш
{
settings
.IsEnabled = IsEnabled
;
settings
.ShareLang = ShareLang
;
settings
.ShareServices = ShareServices
;
settings
.ShareType = ShareType
;
_repository
.Update(settings
);
_signals
.Trigger("YandexShareSettingsChanged");
}
}
}
Итак, функционал для настройки кнопки готов, осталось добавить реализацию для добавления в качестве ContentPart к типам контента и отображения кнопки.
Добавляем стандартного вида хендер:
public class YandexShareHandler: ContentHandler
{
public YandexShareHandler(IRepository<YandexShareRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
И теперь самое главное Driver:
public class YandexShareDriver: ContentPartDriver<YandexSharePart>
{
IWorkContextAccessor _workAccessor;
IYandexShareService _yandexShareSerivce;
public YandexShareDriver(IWorkContextAccessor workAccessor, IYandexShareService yandexShareService)
{
_workAccessor = workAccessor;
_yandexShareSerivce = yandexShareService;
}
protected override DriverResult Display(YandexSharePart part, string displayType, dynamic shapeHelper)
{
var settings = _yandexShareSerivce.Get(); // получаем настройки
if (settings == null) return null;
if (!settings.IsEnabled) return null; // включено ли отображение кнопки?
IResourceManager resourceManager = _workAccessor.GetContext().Resolve<IResourceManager>(); // понадобится для добавления скрипта в хедер сайта
if (resourceManager != null)
{
var scripts = resourceManager.GetRegisteredHeadScripts(); // получаем зарегистрированные в хедере скрипты
if((scripts!=null && scripts.Where(x=>x.Contains("yandex.st")==true).FirstOrDefault()==null) || scripts==null) // проверяем есть ли в скриптах наш и если нет, то добавляем
resourceManager.RegisterHeadScript("<script type="text/javascript" src="http://yandex.st/share/share.js" charset="utf-8"></script>");
}
string title = string.Empty;
string link = string.Empty;
var routePart = part.ContentItem.As<RoutePart>();
if (routePart != null) // содержит ли текущая отображаемая запись RoutePart, если содержит то получаем заголовок и ссылку
{
title = routePart.Title;
link = string.Format("http://{0}/{1}", HttpContext.Current.Request.Url.Host, routePart.Path);
}
// отображаем нашу вью, предварительно передав в неё данные
return ContentShape("Parts_YandexShare", () => shapeHelper.Parts_YandexShare(
Title:title,
Link:link,
ShareType: settings.ShareType,
ShareServices: settings.ShareServices,
ShareLang: settings.ShareLang
));
}
}
А вот так выглядит вью:
<div class="yashare-auto-init" data-yashareTitle="@Model.Title" data-yashareLink="@Model.Link" data-yashareL10n="@Model.ShareLang" data-yashareType="@Model.ShareType" data-yashareQuickServices="@Model.ShareServices"></div>
В конце остаётся добавить файл placement.info с информацией о том, где нужно отображать нашу кнопку
<Placement>
<Place Parts_YandexShare="Content:10"/>
</Placement>
И описание модуля в файл Module.txt.
Завершается процесс создания модуля как всегда командой
orchard.exe package create YandexShare f:
Вместо корня диска f можно указать свой путь.
Проект с исходным кодом
Модуль на сайте Orchard
Посмотреть как это работает можно тут.