среда, 6 апреля 2011 г.

SharePoint 2010 Fluent Ribbon API

На мой взгляд, Ribbon - это абсолютно лучшее нововведение 2010й версии SharePoint. Но для разработчиков Ribbon не настолько удобен, как он удобен и привычен для пользователей. И это нужно исправлять!

Поэтому, представляю уважаемым читателям еще один мой OpenSource-проект, SharePoint 2010 Fluent Ribbon API!


Этот проект нацелен на упрощение работы с Ribbon'ом в SharePoint'е. Как известно, программное создание Ribbon'а требует выполнения большого числа действий, которые мне удалось спрятать в сборке FluentRibbon.

FluentRibbon 1.0 включают следующие основные классы:
  1. ContextualWebPart - базовый класс для веб-частей с контекстными вкладками риббона
  2. RibbonLayoutsPage - базовый класс для Application Page с риббоном

При этом, возможно динамическое создание риббона (например, в зависимости от привилегий текущего пользователя.

Chess WebPart

Вместе с FluentRibbon поставляется проект-пример ChessWebPart - веб-часть с javascript-шахматами, управление игрой и настройки в которой вынесены на риббон:

34 комментария:

  1. Решил вам сюда написать, так как почему gotdotnet.ru не работает
    Вы писали использовать следующий код для добавления кнопки на ribbon:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
    SPWeb web = (properties.Feature.Parent as SPSite).RootWeb;

    var group = new GroupDefinition()
    {
    Id = "Order",
    Title = "Заявка на доступ к ИТ ресурсу",
    Template = GroupTemplateLibrary.SimpleTemplate,
    Controls = new ControlDefinition[]
    {
    new ButtonDefinition()
    {
    Id="Approve",
    Title = "Утвердить",
    Image = new ImageDefinition()
    {
    Url32 = "/_layouts/images/PpsMaAdminValid.png",
    Url16 = "/_layouts/images/NoteBoard_32x32.png"
    },
    ToolTip = new ToolTipDefinition()
    {
    Title = "Утвердить",
    Description = "Утвердить заявку"
    },
    CommandJavaScript = "alert('approve')"
    }
    }
    };

    var ribbonCustomAction = new RibbonCustomAction();
    ribbonCustomAction.AddControlGroup(group, SPRibbonIds.ListForm_Display.Id, 50);

    ribbonCustomAction.Provision(properties.Feature.DefinitionId, web, ListTypes.GenericList, ListForms.DisplayForm);

    }


    public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
    {
    SPWeb web = (properties.Feature.Parent as SPSite).RootWeb;
    RibbonCustomAction.RemoveAllCustomizations(web, properties.Feature.DefinitionId);
    }

    Добавил код такой же как у вас.
    В packagе добавил: Source Path: ссылка на FluentRiboon.Dll, Deployment Target: GlobalAssemblyCashe, Safe Controls - Namespace: FluentRibbon, Assembly Name: FluentRibbon, Safe: true

    Пробовал очищать кэш браузера.
    При активации фичи смотрю дебагом FeatureActivated выполняется без ошибок, но кнопка на ribbon так и не добавляется.

    ОтветитьУдалить
  2. Vladimir, ну Safe controls необязательно, в остальном вроде все нормально. У меня этот код работает, поэтому давайте сделаем проще - я выложил готовый проект, а вы уж там посмотрите, в чем была проблема :)

    Пока что не могу подумать ни на что другое, кроме как на кэш. Если в браузере точно кэш очищен, возможно, стоит очистить каталог C:\Users\(user)\AppData\Local\assembly\dl3\, ну и т.д.

    Еще можете залезть в PowerShell и проверить, что SPWeb-объект содержит в коллекции UserCustomActions эту модификацию. Делается это очень легко (естественно, нужна PowerShell-консоль SharePoint):

    $w = Get-SPWeb http://localhost
    $w.UserCustomActions

    ОтветитьУдалить
  3. Не получилось почему-то в sandbox развернуть готовый проект: Error occurred in deployment step 'Add Solution': Sandboxed code execution request failed.
    в логах ошибка:
    Updating SPPersistedObject SPFeatureDefinition Name=FeatureDefinition/45d39b27-aa81-4a82-8240-50b5a95b000a. Version: -1 Ensure: False, HashCode: 16117708, Id: 45d39b27-aa81-4a82-8240-50b5a95b000a, Stack: at Microsoft.SharePoint.Administration.SPPersistedObject.BaseUpdate() at Microsoft.SharePoint.Administration.SPPersistedChildCollection`1.Add(T newObj, Boolean ensure) at Microsoft.SharePoint.Administration.SPPersistedChildCollection`1.Add(T newObj) at Microsoft.SharePoint.Administration.SPFeatureDefinitionCollection.AddCore(SPFeatureDefinition featdef, SPSite site, String solutionHash, Boolean fForce, Boolean fDoValidation) at Microsoft.SharePoint.Administration.SPFeatureDefinitionCollection.AddInternal(String relativePathToFeatureManifest, Guid solutionId, String solutionHash, SPSite site, Boolean force, Boolean fDoValidation, SPFeatureDefinitionContext featureDefinitionContext) at Microsoft.SharePoint.Administration.SPSolutionPackage.AddFeatureDefinitions(SPFeatureDefinitionCollection featColl, SPFeatureDefinitionContext context, Boolean force, Boolean activateFeatures, SPSite site, SPWeb web) at Microsoft.SharePoint.Administration.SPUserCodeSolutionPackage.EnableSiteCollectionSolution(SPSite site, Int32 solutionGalleryItemId, Boolean force) at Microsoft.SharePoint.SPUserSolutionCollection.<>c__DisplayClass1.b__0() at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode) at Microsoft.SharePoint.SPUserSolutionCollection.AddOrUpgrade(SPListItem item, SPUserSolution existingSolution) at Microsoft.SharePoint.SPUserSolutionCollection.Add(Int32 solutionGalleryItemId) at Microsoft.VisualStudio.SharePoint.Commands.UserSolutionDeploymentManager.DeploySolution(String name, Int32 solutionFileId) at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Delegate.DynamicInvokeImpl(Object[] args) at Microsoft.VisualStudio.SharePoint.Commands.SharePointCommand.Execute(SharePointCommandContext commandContext) at Microsoft.VisualStudio.SharePoint.Commands.SharePointCommand.Execute(SharePointContext sharePointContext, CommandFlags flags, Byte[] serializedParameter, ISharePointCommandLogger logger) at Microsoft.VisualStudio.SharePoint.Commands.CommandManager.ExecuteCommand(SharePointContext sharePointContext, String commandId, CommandFlags flags, Byte[] messageBody) at Microsoft.VisualStudio.SharePoint.Commands.RemoteCommandService.ExecuteCommand(String commandId, CommandFlags flags, Byte[] messageBody)

    Развернул все то же самое в ферму, в референсах и в solution использовал FluentRibbon.dll
    Очистил кэш браузера, почистил папку C:\Users\(user)\AppData\Local\assembly\dl3\,
    папки C:\Windows\assembly\temp\
    C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\
    C:\Windows\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files\
    C:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\

    Выполнил командлет
    $w = Get-SPWeb http://localhost
    $w.UserCustomActions

    В коллекции UserCustomActions содержит в себе что-то.

    Но в кастомном списке так и не появляется кнопка на рибоне.

    ОтветитьУдалить
  4. Разобрался:) Беру SharePoint 2010 Fluent Ribbon API на вооружение

    ОтветитьУдалить
  5. Здравствуйте. не могли бы показать пример для свойства CommandEnableJavaScript.
    Мне надо реализовать следующее: добавляю кнопку на форму просмотра списка, например если в поле 'Название' написано 'Тест', то кнопку делать неактивной. Заранее благодарен.

    ОтветитьУдалить
  6. Здравствуйте.
    А как сделать так, чтоб кнопка отображалась или не отображалась в зависимости от кого какие права у пользователя?

    ОтветитьУдалить
  7. Владимир, добрый день!

    Если вы используете RibbonCustomAction для развертывания изменений ленты, то у метода
    RibbonCustomAction.Provision есть оверлоады с параметром rights - соответственно, в этот параметр вы можете прописать нужные для доступа к кнопке права (с помощью флагового перечисления SPBasePermissions).

    Если же вы используете RibbonController, RibbonLayoutsPage или ContextualWebpart, то можно просто формировать определение кнопки в зависимости от прав пользователя SPContext.Current.Web.CurrentUser, т.е. делать это на стороне сервера при создании определения кнопки.

    ОтветитьУдалить
  8. Добрый день!
    И все таки не могли бы показать пример для CommandEnableJavaScript. Очень надо.

    new ButtonDefinition()
    {
    Id="ApproveOrderButton",
    Title = "Утвердить",
    Image = ImageLibrary.GetStandardImage(8, 4),
    ToolTip = new ToolTipDefinition()
    {
    Title = "Утвердить заявку",
    Description = "Утвердить заявку"
    },
    CommandJavaScript = GetApproveJavascript(),
    CommandEnableJavaScript = EnableApproveButton()
    }

    CommandEnableJavaScript может принимать либо "true" либо "false"

    пробую сделать кнопку неактивной

    string EnableApproveButton()
    {
    string javascript = "javascript:return 'false'";
    }

    Но она остается активной.

    ОтветитьУдалить
  9. Пример для CommandEnableJavaScript:

    // Делать кнопку доступной только если в текущем представлении выбран 1 элемент списка
    CommandEnableJavaScript = "SP.ListOperation.Selection.getSelectedItems().length == 1;",

    ОтветитьУдалить
  10. с этим примером понятно, но я использую кнопку когда ribbon располагается на форме просмотра списка. Возможно ли с помощью CommandEnableJavaScript реализовать такой алгоритм: если в поле 'Название' написано 'Тест', то кнопку при открытии формы списка делать неактивной?

    ОтветитьУдалить
  11. По ходу в этом случае это свойство не работает. Это баг?

    ОтветитьУдалить
  12. Vladimir, такого рода условия если делать правильно, то придется использовать EcmaScript Client Object Model для запроса заголовка списка, потому что заголовок может быть не включен в представление. Если вы уверены, что заголовок всегда присутствует в представлении списка, можно попробовать выдрать его каким-нибудь jQuery-запросом.

    Да, для получения ID текущего выделенного элемента нужно использовать как раз функцию SP.ListOperation.getSelectedItems(), это массив объектов, у каждого объекта есть поле id. По SP.ListOperation много информации в MSDN и в гугле.

    ОтветитьУдалить
  13. Андрей, а как насчет PostBack'а?
    http://blog.vitalyzhukov.ru/ru/sharepoint-2010-fluent-ribbon-events-server-handling.aspx
    Что-нибудь вроде этого не пробовал добавить?

    ОтветитьУдалить
    Ответы
    1. Виталий, постбэки это буу :)

      А вообще да, работа с постбэками описана в документации. Еще на эту тему публиковал статью на NothingButSharePoint про то, как работать с ToggleButton'ами и UpdatePanel'ью :).

      P.S. Маленькая подсказка: в комментариях можно использовать HTML-тэги, в частности тэг <a&gt.

      Удалить
    2. Андрей, насчет "буу": это твое мнение, а SPRibbon - вещь классная и популярная, потому, я считаю, там это должно быть. Как, например, быть, если тебе после нажатия на кнопку риббона надо изменить содержимое риббона?




      И еще, добавь в проект классы аналогичные RibbonControl : UserControl, унаследованные от Microsoft.SharePoint.WebPartPages.WebPartPage,
      Microsoft.SharePoint.WebControls.LayoutsPageBase,
      Microsoft.SharePoint.WebPartPages.WikiEditPage. Или расскажи, почему этого не будет.

      Удалить
    3. Согласен, красивую обертку написать можно. Но как минимум пример работы с постбэками есть в документации, т.е. на текущий момент это наверное всё-таки не суперкритично.

      В общем, я создал задачу, сроков никаких не могу дать, но как минимум я подумаю как её лучше сделать и со временем сделаю. К слову, CodePlex очень хорошо поддерживает возможность предложения патчей и вообще командной разработки, так что если вдруг есть интерес помочь проекту и написать тот или иной функционал, это можно обсудить.

      Насчет контролов и страниц, скорее всего делать не буду, потому что есть и другие проблемы, которые возникают из-за жесткой привязки к наследованию. Например, были вот такие жалобы:
      1. Нельзя создать две вкладки на одной странице
      2. У некоторых людей были определены свои наследники для LayoutsPageBase например, поэтому они иногда вообще не могли использовать RibbonLayoutsPage без редизайна собственного кода

      Все это было известно и обсуждалось на CodePlex'е. Было создано даже несколько задач по этому поводу.

      Именно поэтому я в конечном итоге вывел класс RibbonController в "public", и теперь предлагаю использовать его вместо наследования от RibbonControl или RibbonLayoutsPage. Пользоваться им довольно удобно, почти никакого лишнего кода не будет. В рабочем проекте, мы уже полностью перешли на его использование.

      Удалить
    4. С огромным удовольствием при участие в проекте. Мне на проекте пришлось взять исходники и дописать функционал необходимый для проекта. Отсюда и мой пост появился. Я думаю, моя ситуация стандартна.


      Обсуждения на CodePlex'е не читал, каюсь.


      По поводу RibbonController'а: я сделал метод расширитель для класса Page, чтобы регистрировать таб на странице, сделал кэширование и прочее. Хотелось бы поделиться.


      Чуть не забыл: Спасибо за SPRibbon

      Удалить
  14. Коллеги, у меня одного при разворачивании проекта не активируются фичи?
    Создал проект веб-парта. Добавил туда код, использующий эту гениальную API. Код добавляет пару кнопок в рибон.
    Потом пытаюсь разворачиваться и мне говорят. "Ошибка в шаге развертывания "Активация компонентов": В экземпляре объекта не задана ссылка на объект.".
    Если я потом открываю список фич и активирую её вручную то всё работает, но вот из VS мне нормально проект не развернуть. Точнее он разворачивается, но фичи не активируются. Лог мне ничего интересного не выдал.

    ОтветитьУдалить
    Ответы
    1. Андрей, на Codeplex есть примеры (вкладка Source Code) с использованием этого API, и всё работает как часы. Рекомендую исследовать и делать по аналогии.

      На самом деле, баги бывают, к сожалению. Например, возможно, какой-нибудь параметр в определении кнопок не заполнен или заполнен не так как я это задумывал. Я был бы очень признателен, если бы вы запостили код определения ваших кастомизаций риббона на Codeplex в Discussions (там есть форматирование кода), я бы попробовал воспроизвести ошибку.

      Удалить
  15. День добрый! как можно привязать кнопку на рибоне к определенному списку?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте, Екатерина.
      Используйте метод RibbonCustomAction.Provision. Туда передавайте объект SPList, соответствующий вашему списку.

      Удалить
    2. Большое спасибо)) работает! А если взять не список, а библиотеку документов? не смотря на то, что библиотеку можно взять как splist, ничего не отображается на ленте где расположена библиотека документов. Есть какая то разница между списком и библиотекой?

      Удалить
    3. Елена, там используются разные вкладки, у которых соответственно разные ID'ы.

      Т.е. например, чтобы добавить группу на вкладку где расположены кнопки для элементов списка, нужно при Provision указывать ID целевой вкладки "Ribbon.ListItem", а для библиотеки документов - "Ribbon.Document". Напомню, эти ID-шники в Fluent Ribbon проще всего получать из класса SPRibbonIds.

      Удалить
    4. Этот комментарий был удален автором.

      Удалить
  16. Спасибо, все работает :) только я Катя)) еще вопросик... можно ли к такой кнопке прикрутить функционал, который написан на C#, а не на javascript(commandjavascript)?

    ОтветитьУдалить
  17. Hi. Cuold you tell me how to run javascript on Tab something like "Tab.Loaded(javascript)". I need to change RibbonLabel.Text (with value from UserControl "some" Label on same page). I can't use javascript from UserControl becouse Ribbon panel loads later then UC. Or you have some solution....

    ОтветитьУдалить
  18. Здравствуйте, помогите начинающему, пожалуйста - второй день мучаюсь.

    Как к кнопке Save (button.Command = SPRibbonIds.ListForm_Edit.Groups.Commit.ControlIds.Publish;) прикрутить JavaScript, который бы работал ПОСЛЕ сохранения элемента.

    Я уже кучу вариантов передумал: думал, что ткну на эту кнопку JavaScript'ом, затем сделаю, что нужно. Но у ней id не постоянный. Еще был вариант найти вызываемый этой командой js код, тут тоже крах. Дошел до команды Ribbon.ListForm.Edit.Commit.Publish, и все, сам код где-то запрятан - неизвестно где.

    ОтветитьУдалить
    Ответы
    1. День добрый!

      Задача не такая уж простая даже для многих "профессионалов"...

      В общем случае необходимо выполнить два шага:

      1. Заменить кнопку Save на свою собственную с помощью кастомизаций риббона
      2. В качестве обработчика использовать свой кастомный обработчик, в котором сначала вызвать стандартную команду, потом выполнить свой код

      Чтобы заменить стандартную кнопку на свою, надо пойти в 14\TEMPLATE\GLOBAL\XML\CMDUI.XML и подсмотреть там определение для кнопки. Задеплоить это определение, но вместо стандартного обработчика указать свой.

      В SharePoint Ribbon API для замены стандартных кнопок есть специальный класс ControlLibrary.StandardButtons, который очень легко использовать. Конкретно и с примером как это сделать - описано здесь.

      Чтобы вызвать стандартную команду для кнопки из обработчика, нужно использовать SP.Ribbon.PageManager. Команда выглядит примерно так:

      var commandName = "StandardCommandName";
      SP.Ribbon.PageManager.get_instance().executeRootCommand(commandName, window.g_CUIcommandProperties, {CommandId: commandName}, null);

      Т.е. в твоем случае commandName должно быть "Ribbon.ListForm.Edit.Commit.Publish".

      Я не тестировал конкретно для Publish, но для других команд такой способ работает отлично.

      В частности, вот мой ответ на SP SE, где спрашивали почти идентичный вопрос про подмену команды "DownloadCopy": Attach a Javascript function to an OOTB ribbon button.

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
    3. Большое спасибо за ответ,

      и я сейчас глупый вопрос задать хочу: а как к кнопке свой обработчик прикрутить?

      Вот то есть я создаю кнопку:

      var someBtn = ControlLibrary.StandardButtons.ListForm_Edit.Commit_Publish("", SPContext.Current.Web.Locale.LCID);
      button.Command = SPRibbonIds.ListForm_Edit.Groups.Commit.ControlIds.Publish;

      А что дальше?

      Удалить
  19. Здравствуйте, Андрей!

    Подскажите, пожалуйста, возможно ли в PWA (SharePoint 2010) с помощью Javascript переключать представления с одного на другое, например, в "Центре проектов" или "Отчете по задачам"?

    ОтветитьУдалить
    Ответы
    1. Виталий, здравствуйте! К сожалению с PWA практически не работал, поэтому ничего не могу сказать про переключение представлений :(

      Удалить
    2. Спасибо за отклик!
      А вообще возможно с помощью javascript управлять выпадающими списками в SharePoint 2010, а конкретно программно выбирать пункты выпадающего списка?

      Удалить

Внимание! Реклама и прочий спам будут беспощадно удаляться.