вторник, 26 июня 2012 г.

SharePoint и XSLT: область ссылок «быстрого доступа»

Возвращаюсь к серии статей про SharePoint и XSLT.

Все вы помните ссылку «Добавить новый элемент» внизу представлений списков SharePoint:

image

В связи с наличием этой ссылки возникает два естественных вопроса:
  1. Как поменять текст этой ссылки? (например вместо “Добавить новый элемент”, я хочу текст “Добавить товар” или “Добавить продукт” и т.д.)
  2. Как добавить еще пару ссылок рядом со ссылкой для создания нового элемента?
В последнее время я очень часто и текст ссылки меняю, и другие ссылки добавляю к ней. Такие представления начинают смотреться гораздо приятнее и дружественнее пользователю.

Например, некий список с заявками может иметь вот такие ссылки быстрого доступа:

image

, где ссылка Configure application route открывает редактор маршрута заявки (т.е. можно определить, кто и в каком порядке должен одобрять эту заявку).

В этой статье я расскажу, как реализовать такие изменения. Рассмотрим два способа:
  1. no-code — для частных решений, быстрых правок и кастомизаций Office365
  2. программно – что может потребоваться для создания коробочных решений

No code

Как обычно в случае XSLT, самый простой и быстрый способ найти нужный шаблон и произвести нужные изменения — это использовать SharePoint Designer 2010 (далее SPD). Откройте ваш список в SPD, щелкните по названию нужного представления, и вы увидите визуальный дизайнер страницы.

Однако, ссылки «Add new item» вы не увидите, даже несмотря на то, что на исходной странице в браузере она присутствует.

Чтобы её увидеть, необходимо сделать следующее: щелкнуть куда-нибудь внутрь таблицы, в контекстной ленте в группе вкладок List View Tools щелкнуть Design, и выбрать Options –> Summary Toolbar.

image

Дальше всё очень просто, щелкаем на надпись “Add new item”:

image

, и в правой панели в списке свойств видим:

image

В это свойство можно вписать ваш текст (обратите внимание на апострофы):

image

Жмем Enter, сохраняем страницу (кнопка image сверху слева), переходим в браузер, обновляем страницу с представлением списка, вуаля!

image

Чтобы добавить еще одну ссылку рядом с New memo, желательно всё-таки использовать XSLT-разметку.

Для этого, давайте выделим всю строку, содержащую ссылку New memo. Кстати, вместо того чтобы тыкать 10 раз и пытаться попасть в нужный тэг <tr>, существует простой способ выделить строку, к которой принадлежит текущее выделение:

image

Теперь, не снимая выделения, переключаемся на вкладку Code:

image

Нам будет отображен фрагмент XSL-преобразования, ответственный за отображение как раз той самой строки с ссылкой.

Соответственно, этот кусок можно скопировать, вставить копию чуть ниже, и подкорректировать.

Во-первых, нужно заменить картинку, за которую ответственен вот этот кусок кода:
<span style="height:10px;width:10px;position:relative;display:inline-block;overflow:hidden;" class="s4-clust">
  <img src="/_layouts/images/fgimg.png" alt="" style="left:-0px !important;top:-128px !important;position:absolute;"  />
</span>

Подойдет любая картинка 10х10 пикселов. Если вы берете картинку не из спрайта, код можно упростить примерно до такого вида:
<img src="/_layouts/images/wpedit.gif" alt="" style="height:10px;width:10px;" />

Далее, нужно подкорректировать саму ссылку, за которую ответственен вот этот кусок XSLT-кода:
  <a class="ms-addnew" id="{$ID}"
     href="{$Url}"
     onclick="javascript:NewItem2(event, &quot;{$Url}&quot;);javascript:return false;"
     target="_self">
    <xsl:value-of select="'New memo'" />
  </a>

Здесь совет: когда это возможно, используйте JavaScript, и открывайте модальный диалог с нужной вам страницей. Это более привычное поведение для пользователей, т.к. ссылка New item открывает именно модальный диалог.

В остальных случаях, очень полезно использовать функцию GoToPage, которая описана у Alejo El Norte. Она добавит параметр Source к адресу целевой страницы, и позволит вам по нажатию кнопки «Отмена» или «Назад» на целевой странице, легко вернуться туда, откуда пользователь пришел.

В любом случае, открываете ли вы некую страницу в модальном диалоге или напрямую, как правило для формирования ссылки требуются некоторые типовые сведения:

  • GUID текущего списка
  • Url текущего Web’а
  • и т.д.

  • Все эти сведения можно взять из глобальных параметров XSLT. Они довольно хорошо описаны на MSDN.

    Чтобы не искать, самые интересные параметры:

    $ListGUID списка
    $ListUrlDirАдрес корневого каталога списка, аналог SPList.RootFolder.ServerRelativeUrl
    $ServerRelativeUrlАдрес текущего узла относительно адреса сервера, аналог SPWeb.ServerRelativeUrl
    $RootSiteUrlАдрес коллекции узлов, которой принадлежит текущий узел
    $LCIDСтрока, содержащая номер текущей локали, аналог Thread.CurrentThread.CurrentUICulture.LCID
    $UseridID текущего пользователя

    Также, иногда хочется отобразить ту или иную ссылку по некоему условию. Наиболее частое условие — наличие у пользователя каких-либо прав. В этом случае пригодится функция ddwrt:IfHasRights.

    Например, если я хочу отобразить ссылку на переход на некую страницу с дополнительными настройками списка, только если у пользователя есть права на управление списками (SPBasePermissions.ManageLists), то я должен обернуть соответствующий <tr> в тег <xsl:if>, и передать в ddwrt:IfHasRights числовое значение, соответствующее ManageLists, т.е. 2048:

    <xsl:if test="ddwrt:IfHasRights(2048)">
      <!-- 2048 = SPBasePermissions.ManageLists -->
      <tr>
        <td class="ms-addnew" style="padding-bottom: 3px">
          <img src="/_layouts/images/wpedit.gif" alt="" style="height:10px;width:10px;" />
          <xsl:text disable-output-escaping="yes" ddwrt:nbsp-preserve="yes">&amp;nbsp;</xsl:text>
          <a class="ms-addnew" href="javascript:GoToPage('{$HttpVDir}/_layouts/My/MyPage.aspx?List={$List}');">
            Advanced settings
          </a>
        </td>
      </tr>
    </xsl:if>

    Соответствие строковых значений перечисления числовым, можно легко узнать, написав в Visual Studio “SPBasePermissions”, и нажав F12. Конечно, этот список есть и в интернетах.

    Программный способ


    Даже если вы большой любитель всё делать только в Visual Studio, я бы всё равно порекомендовал сгенерить нужный XSLT код с помощью SPD. Используя SPD, вы получаете хорошую возможность оттестировать ваш код, добиться чтобы он работал и выглядел как должно. И главное – в SPD это можно сделать это быстро и минимальными усилиями.

    Имея готовый отлаженный код, перейдите во вкладку Code в SPD, и скопируйте полностью содержимое тега <Xsl>. Этот код вставьте в отдельный xslt-файл, который нужно будет задеплоить куда-нибудь в Layouts.

    Если вы всё-таки не хотите копаться в SPD, но хорошо чувствуете себя с XSLT… Что ж, минималистичный пример преобразования, которое заменяет стандартное “New item” на “New product”, приведен ниже:

    <xsl:stylesheet
      xmlns:x="http://www.w3.org/2001/XMLSchema"
      xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
      version="1.0"
      exclude-result-prefixes="xsl msxsl ddwrt"
      xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
      xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
      xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:msxsl="urn:schemas-microsoft-com:xslt"
      xmlns:SharePoint="Microsoft.SharePoint.WebControls"
      xmlns:ddwrt2="urn:frontpage:internal"
      xmlns:o="urn:schemas-microsoft-com:office:office">
    
      <xsl:include href="/_layouts/xsl/main.xsl" />
      <xsl:include href="/_layouts/xsl/internal.xsl" />
    
      <xsl:template name="Freeform">
        <xsl:param name="AddNewText"/>
        <xsl:param name="ID"/>
        <xsl:variable name="Url" select="$ENCODED_FORM_NEW"/>
    
        <xsl:if test="$ListRight_AddListItems = '1' and (not($InlineEdit) or $IsDocLib)">
          <table id="Hero-{$WPQ}" width="100%" cellpadding="0" cellspacing="0" border="0">
            <tr>
              <td colspan="2" class="ms-partline">
                <img src="/_layouts/images/blank.gif" width="1" height="1" alt="" />
              </td>
            </tr>
            <tr>
              <td class="ms-addnew">
                <span style="height:10px;width:10px;position:relative;display:inline-block;overflow:hidden;" class="s4-clust">
                  <img src="/_layouts/images/fgimg.png" alt="" style="left:-0px !important;top:-128px !important;position:absolute;"  />
                </span>
                <xsl:text disable-output-escaping="yes" ddwrt:nbsp-preserve="yes">&amp;nbsp;</xsl:text>
                <a class="ms-addnew" id="{$ID}"
                    href="{$Url}"
                    onclick="javascript:NewItem2(event, &quot;{$Url}&quot;);javascript:return false;"
                    target="_self">
                  <xsl:value-of select="New memo" />
                </a>
              </td>
            </tr>
            <tr>
              <td>
                <img src="/_layouts/images/blank.gif" width="1" height="5" alt="" />
              </td>
            </tr>
          </table>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>

    Как и в случае с SDP, этот код нужно положить в отдельный файл, и задеплоить куда-нибудь в Layouts.

    Файл есть, теперь XSLT-преобразование к представлению списка подключаются очень просто, например при активации вашей фичи:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        var web = properties.Feature.Parent as SPWeb;
        var list = web.Lists["My list title"];
        var view = list.DefaultView;
        view.XslLink = @"../My/MyTransform.xsl";    view.Update();
    }

    Здесь я предполагаю, что фича имеет Scope=Web, XSLT-преобразование лежит в файле Layouts/My/MyTransform.xsl (путь указывается относительно папки Layouts/Xsl), а у списка заголовок “My list title” (хотя получать списки по заголовку нежелательно, лучше по Url или по Guid).

    P.S. Если вы не читали другие мои посты про XSLT и XsltListViewWebPart в SharePoint, очень рекомендую.

    16 комментариев:

    1. А я последнее время предпочитаю получать списки по RootFolder, как SPList tenderMembersList = web.Lists.Cast().Where(l => l.RootFolder.Name == "debug2Contractor").FirstOrDefault();
      вроде как InternalName списка, и узнать легко из URL, и привязки по Guid или Title не требует.

      ОтветитьУдалить
      Ответы
      1. Александр, но ведь RootFolder.Name может совпадать для нескольких списков, если их положить в разные папки.

        Я в основном получаю по URL, используя метод SPWeb.GetListFromWebPartPageUrl. При программном создании списков это, пожалуй, наиболее удобный вариант. Правда, если список не найден, вернет не null, а SPException, но обернуть в метод расширения - не проблема.

        Удалить
      2. Андрей, не совсем понял Вашу мысль, приведите пример.
        1. Насколько я знаю SPList.RootFolder == SPList.Folders[0] , т.е. имя корневой папки списка или библы. Ни о каких подпапках речи не идет. Или я ошибаюсь?
        1.1 При создании списка/библы, его Title назначается именем корневой папке в списке, аля RootFolder, и становиться частью URL, в последствии title менять можно, а вот RootFolder нет (могу ошибаться).
        1.2. На уровне узла, что для библы, что для списка RootFolder должен быть уникален. При попытке создать список или библу с указанием одного и тоже Title(=RootFolder) будет выдан Exception, что такой урл уже занят.
        2. но нюансы такого подхода все же есть:
        2.1) что для библы и списка RootFolder все может быть один и тот же. но можно же при выборе учитывать тип списка.
        2.2) вопрос производительности при таком подходе? может на самом деле SPWeb.GetList будет и предпочтительнее, а урл например можно состряпать программно исходя из WEB_URL/ListType/RootFolder
        3. собственного я не чего нового не придумал, а взял из коментариев http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.rootfolder(v=office.12).aspx

        Удалить
      3. Если создавать списки только через GUI - все списки кладутся в каталог Lists, и имеют соответствующий URL.

        Однако программно можно создать список по любому URL. В частности, 4 из 7 перегрузок SPListCollection.Add имеют параметр URL, т.е. можно задать URL списка.

        Через CAML тоже можно создать список по определенному URL.

        Например, спокойно могут существовать рядом следующие списки:
        1. http://portal/Lists/News
        2. http://portal/MySolution/News
        3. http://portal/Company/News

        Удалить
      4. Мдя, век живи век учись. Спасибо за ликбез. Будем учитывать такие нюансы...

        Удалить
    2. Заменил по инструкции "Добавить новый элемент" на свой вариант. Все отлично, только все кнопки на вкладке "Список" стали по виду неактивными, но при этом работают как раньше... Почему так?
      Спасибо

      ОтветитьУдалить
      Ответы
      1. Юрий, у меня всё ок, кнопки работают нормально и по виду тоже активные.
        Простая смена одного текста ссылки на другой абсолютно ни на что не может влиять.
        А на странице нет никаких ошибок JS?

        Удалить
      2. В этом представлении ранее условное форматирование прикрутили... мож и наложилось. Про ошибки JS ничего не могу сказать, ибо не программист.

        Удалить
    3. Андрей, можно подробнее показать как в новой ссылке на создание элемента заменить тип контента. Пример: список в нем могут создаваться 4 контента. хочу сделать 4 ссылки каждую на свой контент. По вашему описанию добавил еще одну кнопку, ее переименовал и ни как не могу заставить изменить создаваемый контент.

      ОтветитьУдалить
      Ответы
      1. Алексей, нужно передавать ID типа содержимого в Url.

        Обычно Url (т.е. значение переменной $Url) будет выглядеть как-то так: "http://portal/_layouts/listform.aspx?PageType=8&ListId={PUT-YOUR-GUID-HERE}&RootFolder="

        Вам нужно туда добавить "&ContentTypeId={PUT-YOUR-GUID-HERE}".

        Итого, XSLT-код будет выглядеть как-то так:

        Вместо переменной Url (<xsl:variable name="Url" select="$ENCODED_FORM_NEW" />), нужно будет вставить 4 переменных для ваших 4х типов содержимого:

        <xsl:variable name="Url1" select="concat($ENCODED_FORM_NEW,'&ContentTypeId={PUT-YOUR-GUID1-HERE}')"/>
        <xsl:variable name="Url2" select="concat($ENCODED_FORM_NEW,'&ContentTypeId={PUT-YOUR-GUID2-HERE}')"/>
        <xsl:variable name="Url3" select="concat($ENCODED_FORM_NEW,'&ContentTypeId={PUT-YOUR-GUID3-HERE}')"/>
        <xsl:variable name="Url4" select="concat($ENCODED_FORM_NEW,'&ContentTypeId={PUT-YOUR-GUID4-HERE}')"/>

        и соответственно в ссылках вместо $Url нужно будет использовать $Url1, $Url2, $Url3 и $Url4.

        Обратите внимание, переменная в ссылке встречается дважды: в href и в onclick.

        Удалить
    4. Подскажите как поместить кнопку добавления над списком? Достаточно ли будет соответствующий кусок XSL-преобразования перенести?

      ОтветитьУдалить
    5. Илья, здравствуйте!

      Вам нужно переопределить шаблон match="/" примерно следующим образом:

      <xsl:template match="/">
      <!-- ваш код -->

      <xsl:choose>
      <xsl:when test="$RenderCTXOnly='True'">
      <xsl:call-template name="CTXGeneration"/>
      </xsl:when>
      <xsl:when test="($ManualRefresh = 'True')">
      <xsl:call-template name="AjaxWrapper" />
      </xsl:when>
      <xsl:otherwise>
      <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition"/>
      </xsl:otherwise>
      </xsl:choose>
      </xsl:template>


      т.е. вместо "ваш код" нужно будет вставить тот самый кусок преобразования, ну или лучше выделить этот кусок в отдельный шаблон и добавить просто вызов вашего шаблона (xsl:call-template).

      ОтветитьУдалить
    6. Андрей, спасибо большое за пост! Долго искала how-to по редактированию ссылок. У меня еще вопрос: номер с переименованием ссылок (no code) не проходит со списком типа Discussion Board, там свои заморочки?

      ОтветитьУдалить
    7. Привет,Андрей статья действительно очень полезная!Спасибо. Хочу чтобы твои читатели знали и про другой вариант добавления xsl. Ты описал это в FeatureActivated.

      Я это описываю в ListDefinition => schema.xml

      ..\yourfolderinLayouts\yourfile.xsl


      И кстати, чуть ниже можно вставить это clienttemplates.js там же,в теге
      И 2013 версии мы можем редактировать список в режиме "Быстрого редактирования", иначе кнопка будет неактивная.
      Еще раз спасибо

      ОтветитьУдалить
    8. Исправил предыдущий комент
      <view BaseViewID="1" Type="HTML" ... >
      <XslLink Default="TRUE">..\yourfolderinLayouts\yourfile.xsl</XslLink>
      <JSLink>clienttemplates.js</JSLink>
      <view>

      ОтветитьУдалить
    9. Очень ценная информация, спасибо за ваш труд...

      ОтветитьУдалить

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