вторник, 20 ноября 2012 г.

Инструкция по обновлению Workflow (без потери старой версии)


1. Увеличиваем версию в имени Assembly. Заходим в Project -> Properties -> Application
2. Увеличиваем версию в атрибуте Name и задаем новый Solution Id в package

3. Увеличиваем версию в имени фичи, которая выкладывает Workflow.
4. Задаем новый Id фичи, которая выкладывает Workflow.

5. Увеличиваем версию в имени Workflow.

6. В файле Elements.xml, принадлежащему Workflow, увеличиваем версию в атрибуте Name и задаем новый Id.

7. Закрываем Visual Studio и ОБЯЗАТЕЛЬНО удаляем файл “*.csproj.user” в папке с проектом.
8. Открываем Visual Studio, снова прописываем для проекта Site URL, куда будет выкладываться пакет, и восстанавливаем ассоциации с листами (Target List, Task List, History List) для Workflow. В мастере восстановления ассоциации Workflow с листами, указываем имя Workflow с увеличенной версией.

9. Выполняем Deploy проекта, и проверяем, что Workflow создался и привязан к листу

10. Для старой версии Workflow отключаем генерацию новый экземпляров, снимая галочки.

 Результат на лицо.

Lookup колоноки как текст

Иногда приходиться показывать lookup колонки, по нажатию на которые SP любезно переводит нас в связный лист. Это не подходит для большинства проектов, поскольку необходима строгая логика переходов пользователя. И поэтому необходимо преобразовывать их в текст, дабы исключить эти ситуации.

Код:

// Removes link to lookup column
function RemoveLookupLinks()
{
 // Remove lookup column hyper link
 $(document).ready(function () {
  $('a[href*="RootFolder=*"]').each(function (index) {
   var link = $(this);
   $(this).after("<span>" + link.text() + "</span>");
   $(this).remove();
  });
 });
} 

Если вы надумаете использовать ListViewByQuery, то понадобиться вторая часть

// Removes link to user list from list view.
//listID : SPContext.Current.Web.SiteUserInfoList.ID.ToString().ToUpper()
function RemoveUserLinks(userInfoListID)
{
 // Remove SPUser Hyperlink
 $(document).ready(function () {
  $('a[href*="userdisp.aspx?ID="]').each(function (index) {
   var link = $(this);
   $(this).after("<span>" + link.text() + "</span>");
   $(this).remove();
  });
 });
 
 // Remove SPUser Hyperlink
 $(document).ready(function () {
  $('a[href*="ListId={' + userInfoListID + '}"]').each(function (index) {
   var link = $(this);
   $(this).after("<span>" + link.text() + "</span>");
   $(this).remove();
  });
 });
}
 
Вызов: (я использую WebPart`ы )
<script type="text/javascript">
    RemoveLookupLinks();
    RemoveUserLinks('<%=SPContext.Current.Web.SiteUserInfoList.ID.ToString().ToUpper()%>');
</script>

Как скрывать Ribbon в SharePoint

Этот метод хорошо работает для модальных диалогов, в немодальных есть проблема со скролом!!!

Казалось бы, делов-то семечки: #s4-ribbonrow { display: none; }, и вуаля. Согласен, можно и так. Но есть нюансы!

Во-первых, способов скрывать Ribbon – далеко не один. А в SharePoint, если есть несколько способов что-то сделать, скорее всего чем-то эти способы отличаются. И в одних ситуациях можно применять, в других нет.

Во-вторых, если скрывать через css как было обозначено выше, перестают работать статусы (SP.UI.Status) и оповещения (SP.UI.Notify). В общем, не всё так просто!

Способы скрывать Ribbon

Способов много. Какой выбрать – зависит от того, чего вы хотите добиться.

Наиболее частая потребность – это скрыть Ribbon полностью, либо для анонимных пользователей, либо для всех кроме админов. В этом случае, просто оберните тэг <div id="s4-ribbonrow" ... > ... </div> на masterpage в SPSecurityTrimmedControl.

Второй вариант использования более интересен. Предположим, вы разрабатываете какую-нибудь форму на Site Page. Например, эта форма просто содержит горстку настроек и кнопки OK/Отмена. Очевидно, что Ribbon на такой форме не нужен. А поскольку он занимает довольно много места, скрывать его с форм весьма актуально, ведь формы как правило открываются в модальных диалогах...

Для этого варианта, первое что приходит в голову – написать двухстрочную веб-часть “Скрыватель Ribbon”, которая будет делать SPRibbon.GetCurrent(Page).CommandUIVisible = false; и кидать эту веб-часть на те страницы, где нужно скрыть Ribbon. Или то же самое сделать с помощью Control’а, если не хочется, чтобы веб-часть светилась в пользовательском интерфейсе.

Этот способ  не будет работать в Sandbox решениях, потому что SPRibbon находится в пространстве имен Microsoft.SharePoint.WebControls, который целиком запрещен для использования в Sandbox.

И вот здесь остается только способ со скрытием Ribbon’а через css. Разновидностью этого способа можно считать применение вашего css через JavaScript.

Проблема с статусной строкой

Как я уже упоминал выше, скрытие Ribbon’а чаще всего приводит к тому, что перестают работать статусные сообщения. Это неприятно. Лично я использую и SP.UI.Status, и SP.UI.Notify крайне интенсивно. Это идеальный способ отображать всякие подсказки и Ajax-сообщения.

В чем же проблема?

Оказывается, внутри div’а s4-ribbonrow есть три важных div’а:

image
  1. Панель s4-ribboncont отображает собственно ленту.
  2. Панель notificationArea ответственна за показ статусных сообщений.
  3. Наконец, панель WebPartAdderUpdatePanelContainer нужна для работы страницы в режиме редактирования, а конкретно – для отображения панели добавления веб-частей.
Казалось бы, чего проще, давайте скрывать s4-ribboncont вместо s4-ribbonrow. Нет, нельзя. s4-ribbonrow добавляет специальные стили, и плюс для этой панели динамически выставляется её высота. В общем, получите неприятное пустое место на странице, которое не так уж просто убрать. Есть ли способ проще?

Я делаю так: если скрываю Ribbon, то выношу notificationArea из s4-ribbonrow.

Для случая скрытия Ribbon по всему сайту через мастер-страницу, нужно проделать эту операцию единожды вручную.

Для случая Site Page форм, то же самое можно сделать простейшим куском JavaScript’а:

<script type="text/javascript">
    (function () {

        if (GetUrlKeyValue("IsDlg") == "1") {
            var notificationArea = $get('notificationArea');
            var ribbonRow = $get('s4-ribbonrow');
            ribbonRow.removeChild(notificationArea);
            ribbonRow.parentNode.insertBefore(notificationArea, ribbonRow);
            ribbonRow.style.display = 'none';
        }

    })();</script>

Как видите, лента будет скрыта только в случае, если страница запущена в модальном диалоге (IsDlg=1).

Можно еще немного модифицировать это условие. Например, вот так:

if (GetUrlKeyValue("IsDlg") == "1" && document.forms[MSOWebPartPageFormName].MSOSPWebPartManager_DisplayModeName.value != "Edit")

В этом случае Ribbon будет скрыт, только если страница запущена в модальном диалоге, и только если она находится в режиме просмотра. В режиме редактирования лента будет отображаться.

Такой вариант позволит вам размещать на этой странице стандартные веб-части (например, тот же самый Content Editor WebPart), и пользователи смогут, используя контекстное меню веб-части, перевести страницу в режим редактирования и кастомизировать её как их душе будет угодно.

Как получить экземпляр рабочего процесса

Как это делают индусы на странице /_layouts/WrkStat.aspx?List={C9E48B30-6341-4068-BC63-146F6D7FF1A2}&WorkflowInstanceID={03C35125-F3F2-4843-B82E-8BD6AC15E049}

Код в OnLoad:

if (string.IsNullOrEmpty(this.StrGuidWorkflow))
{
   this.StrGuidWorkflow = this.Request.QueryString["WorkflowInstanceID"];
   this.WorkflowInstanceID.Value = this.Request.QueryString["WorkflowInstanceID"];
}
 
Guid workflowInstanceId;
 
try
{
 workflowInstanceId = new Guid(this.StrGuidWorkflow);
 this.workflow = new SPWorkflow(this.Web, workflowInstanceId);
 this.wa = this.workflow.ParentAssociation;
 this.ModificationUrl = this.wa.ModificationUrl;
 this.List = SPContext.Current.List;
}
cath {}

собственно просто вызов конструктора this.workflow = new SPWorkflow(this.Web, workflowInstanceId);

Как привязать к списку свои ASPX-формы

Как быстро и просто программным способом назначить для списка свои формы, лежащие в _layouts? Для этого нужно взять у списка нужный тип содержимого и подменить часть XML в его определении. Для примера возьмём первый попавшийся тип содержимого и назначим ему формы просмотра и редактирования:

1 вариант:
var ct = list.ContentTypes[0];
string ns = "http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url";
var xml = new System.Xml.XmlDocument();
xml.LoadXml(
"<FormUrls xmlns=\"" + ns + "\">" +
    "<Display>_layouts/MyDisplayForm.aspx</Display>" +
    "<Edit>_layouts/MyEditForm.aspx</Edit>" +
"</FormUrls>");
ct.XmlDocuments.Delete(ns);
ct.XmlDocuments.Add(xml);
ct.Update();

2 вариант:
public static void AddFormToList(SPWeb web, string sitePrefix, string listname, string formName, string webPartName, PAGETYPE pageType)
        {
            string errorMessage = "Import error";

            web.AllowUnsafeUpdates = true;

            //get current list
            SPList currentList = web.Lists.TryGetList(listname);

            if (currentList == null)
                return;

            //get web part gallery
            SPList wpGallary = ListTools.GetListByRelativePath(web, "_catalogs/wp");

            if (wpGallary == null)
                return;

            if (!formName.Contains(".aspx"))
                formName = formName + ".aspx";

            //get root folder
            SPFolder root = currentList.RootFolder;

            //form url
            string customFormUrl = SPUrlUtility.CombineUrl(web.ServerRelativeUrl, "/" + root.Url + "/" + formName);

            //get page
            SPFile page = web.GetFile(customFormUrl);

            if (!page.Exists)
                page = root.Files.Add(customFormUrl, SPTemplateFileType.FormPage);

            //get part from gallery
            SPListItemCollection collect = wpGallary.GetItems(new SPQuery() { Query = string.Format("<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>{0}</Value></Eq></Where>", webPartName) });

            if (collect.Count > 0)
            {
                using (SPLimitedWebPartManager wpmgr = page.GetLimitedWebPartManager(PersonalizationScope.Shared))
                {
                    //add empty part
                    DataFormWebPart webPart = new DataFormWebPart();
                    IListWebPart part = webPart;
                    part.ListId = currentList.ID;
                    part.PageType = pageType;
                    wpmgr.AddWebPart(webPart, "Main", 1);

                    //add custom part
                    string webpartString = web.GetFileAsString(web.Url + "/" + collect[0].File.Url);
                    string webPartXml = new StringReader(webpartString).ReadToEnd();
                    XmlTextReader reader = new XmlTextReader(new StringReader(webPartXml));
                    System.Web.UI.WebControls.WebParts.WebPart importWebPart = wpmgr.ImportWebPart(reader, out errorMessage);
                    importWebPart.Title = webPartName;
                    wpmgr.AddWebPart(importWebPart, "Main", 0);
                }

                switch (pageType)
                {
                    case PAGETYPE.PAGE_DISPLAYFORM:
                        currentList.DefaultDisplayFormUrl = customFormUrl;
                        break;
                    case PAGETYPE.PAGE_EDITFORM:
                        currentList.DefaultEditFormUrl = customFormUrl;
                        break;
                    case PAGETYPE.PAGE_NEWFORM:
                        currentList.DefaultNewFormUrl = customFormUrl;
                        break;
                    default:
                        break;
                }

                currentList.Update();
            }

            web.AllowUnsafeUpdates = false;
        }

Интересные страницы Download.aspx и CopyUtil.aspx

1. Download.aspx

Надо скачать какой-то файл с портала? Авторизовавшись, запрашиваем страницу /_layouts/download.aspx с параметром SourceUrl, в котором указываем адрес нужного файла. В ответ SharePoint вернёт содержимое файла.
Например, я хочу посмотреть, как сделана страница default.aspx, которая была добавлена в корневую папку сайта:
http://site/_layouts/download.aspx?SourceUrl=default.aspx

2. CopyUtil.aspx 

В SharePoint существует страница приложения /_layouts/CopyUtil.aspx. Совсем не просто определить её назначение по названию. На самом деле она ничего никуда не копирует, а просто перенаправляет пользователя на форму определённого элемента списка.
Использование её целесообразно при формировании списка ссылок для элементов с разных узлов, например, для отображения результата запроса, сделанного через SPSiteDataQuery. Рассмотрим, какие параметры следует добавить в адрес страницы CopyUtil.
Если нужный элемент находится на том же узле, на котором будет размещена ссылка, то достаточно добавить параметр InThisWeb=1. В противном случае мы должны как-то указать узел элемента: либо через идентификаторы, либо через адрес. Мы имеем два варианта указания нахождения элемента: по идентификаторам коллекции узлов, узла, списка и самого элемента, или же просто по адресу элемента. В первом случае нужно обязательно добавить параметры Use=id, SiteId, WebId (эти два последних игнорируются, если есть InThisWeb=1), ListId и ItemId. Во втором случае - добавить параметр ItemUrl.
Для того, чтобы открылась форма редактирования элемента, необходимо добавить параметр Action=editform. Чтобы направить пользователя на форму просмотра, добавляем Action=dispform или же вовсе пропускаем параметр Action.
Если добавить параметр Source, то он без изменения будет добавлен в адрес элемента.
Получив необходимые данные, страница CopyUtil откроет, если надо, узел, возьмёт нужный список и адреса его форм, сформирует адрес элемента и выполнит перенаправление с использованием метода SPUtility.Redirect.

Использование этой страницы вместе с публикацией имеет свои особенности и ограничения, про которые можно прочитать здесь: http://blog.mastykarz.nl/copyutilaspx-and-publishing-sites/

 

Функция GoToPage

В скриптах SharePoint, в файле init.js, есть глобальная функция GoToPage, принимающая один параметр - адрес страницы. Алгоритм работы этой функции прост: происходит перенаправление на указанный в параметре адрес с добавленным параметром Source. В этот параметр Source записывается текущий адрес, т.е. адрес страницы, с которой происходит перенаправление.
Использовать функцию GoToPage целесообразно для открытия страниц, на которых имеются кнопки для закрытия формы, отмены операции или возврата на предыдущую страницу, ведь в адрес для возврата не нужно вычислять, он уже передан в параметре Source.
Пример:
<a href="javascript:GoToPage('/someFolder/somePage.aspx')">Some page</a>
Для проверки выполните в консоли браузера такой код:
GoToPage(window.location.pathname);