понедельник, 4 июля 2011 г.

SP.UI.ModalDialog и стандартные диалоги SharePoint

На своем докладе на SharePoint Conference Russia 2011 я, в числе прочего, показывал интересный пример глубокой интеграции с SharePoint - использование диалогов SharePoint в собственных целях, получение и обработка их returnValue.

Такой подход позволяет обеспечить глубокую интеграцию с SharePoint и иногда - значительно упростить разработку того или иного функционала. Особенно это актуально в среде Office365, где нет возможности создания собственных Application Pages. В этом случае крайне важно по максимуму использовать тот функционал, который есть в SharePoint.

В этой заметке я хочу еще раз вернуться к теме возвращаемых значений диалогов, описать несколько примеров, и привести код для самостоятельного тестирования диалогов, которых я не видел :)

Пример с загрузкой картинки в библиотеку

На конференции я показывал пример, в котором создавалась кнопка на Ribbon'e, которая делала следующее:
  1. Открывала стандартное окошко добавления элемента в библиотеку рисунков
  2. В случае успешного добавления картинки, обновляла одно из полей выбранного элемента списка адресом этой картинки. Поле имело тип "Hyperlink or picture".
Приведу еще раз код, который для этого использовался:

var picturesLibraryGuid = 'put-your-guid-here';
if (SP.ListOperation.Selection.getSelectedItems().length != 1) {
    SP.UI.Notify.addNotification('Select a single item, please!');
}
else {
    var itemId = SP.ListOperation.Selection.getSelectedItems()[0].id;
    var listGuid = SP.ListOperation.Selection.getSelectedList();
    var onError = function (sender, args) {
        SP.UI.Notify.addNotification('Error occured: ' + args.get_message());
    };
    var onItemUpdated = function () {
        SP.UI.Notify.addNotification('Item updated successfully!');
        SP.UI.ModalDialog.RefreshPage(1);
    };
    var options =
        {
            url: '/TeamSite/_layouts/Upload.aspx?List=' + picturesLibraryGuid,
            title: 'Upload picture',
            dialogReturnValueCallback:
                function (dialogResult, returnValue) {
                    if (dialogResult == SP.UI.DialogResult.OK) {
                        var context = new SP.ClientContext.get_current();
                        var list = context.get_web().get_lists().getById(listGuid);
                        var item = list.getItemById(itemId);
                        item.set_item('Picture', returnValue.newFileUrl);
                        item.update();
                        context.executeQueryAsync(onItemUpdated, onError);
                    }
                }
        };
    SP.UI.ModalDialog.showModalDialog(options);
 
}

Самые важные моменты в коде:
  • SP.ListOperation.Selection - здесь можно получить Guid текущего списка, а также ID и fsObjType выбранных элементов этого списка;
  • SP.UI.ModalDialog.RefreshPage - позволяет обновить содержимое текущей страницы, если диалог корректно отработал и предполагается, что данные на странице изменились. В качестве параметра передается обычно dialogResult.
  • SP.ClientContext - точка входа для SharePoint EcmaScript Client Object Model.
  • "_layouts/Upload.aspx" - Путь к форме загрузки файла в библиотеку документов. В качестве параметра, нужно передать Guid библиотеки, в которую будет происходить загрузка файла.
  • returnValue.newFileUrl - возвращаемое значение диалога Upload.aspx, содержит свойство newFileUrl, которое выставляется в Url загруженной картинки;

Другие диалоги

Приведенный выше, довольно простой код, уже делает довольно интересные вещи. Но что же еще, помимо загрузки элемента в список, можно такого использовать, и как получить оттуда возвращаемое значение?

На самом деле, таких диалогов немало. И источником Номер Один для всяких вкусностей, для меня традиционно является SharePoint Rich Text Editor. Вот чем он может поделиться с нами сегодня:
  1. Чтобы дать пользователю возможность выбрать, в какую библиотеку загружать картинку, можно использовать диалог со следующим url'ом: 
    /_layouts/RTEUploadDialog.aspx?LCID=1033&Dialog=UploadImage&UseDivDialog=true
    
    Этот диалог выглядит следующим образом:
    Заголовок диалогу, естественно, можно менять произвольным образом.
    В раскрывающемся списке будут представлены все библиотеки узла.
    Вернет диалог строку, содержащую html-код, соответствующий тэгу img. Из него легко получить тот же самый Url с помощью простейшей jquery-конструкции: $(returnValue).attr('src').
  2. Если заменить в предыдущем примере Dialog=UploadImage на Dialog=UploadDocument, получим аналогичную форму для загрузки документа произвольного типа. Возвращаемое значение в этом случае будет иметь опять же вид строки с html-кодом, но на этот раз будет содержать ссылку на файл и img-тэг с картинкой, соответствующей типу файла.
    Например:

    <a href="/Shared Documents/Presentation.pptx"><img class="ms-asset-icon ms-rtePosition-4" src="/_layouts/images/icpptx.png" alt="Presentation.pptx"/>Presentation.pptx</a>
  3. Также есть простенький текстовый редактор, тот самый, в котором редактируется HTML из Content Editor'а, например. Url диалога:
    /_layouts/zoombldr.aspx?UseDivDialog=true
    
    Чтобы проинициализировать значение этого диалога, нужно передать в качестве аргументов объект со свойством value, куда и поместить начальный текст:
    options.args = { value: "initial dialog text goes here" };
    
    Диалог вернет текст, введенный пользователем в окно ввода.
В SharePoint'е очень большое количество разных диалогов. К сожалению, не все диалоги возвращают какие-либо значения.

К примеру, форма /_layouts/attachfile.aspx принимает параметры ListId (Guid списка) и ItemId (целочисленный ID элемента списка), и хотя ничего не возвращает, но понять, что вложение было успешно загружено, можно по dialogResult - а выяснять, что именно было загружено, придется уже через Client Object Model. Это, видимо, будет последний файл в соответствующем элементу списка каталоге вложений. Определить каталог вложений довольно просто:
var listItemAttachmentsFolder = list.RootFolder.SubFolders["Attachments"].SubFolders[listItem.ID];

Также, ничего не возвращает форма добавления нового элемента списка - тоже неприятно, и опять придется прибегать к помощи Client Object Model, хотя конечно очень хотелось бы получить ID записи в виде возвращаемого значения...

В общем, диалогов много, и большинство из них можно использовать для своих нужд. Здесь я рассмотрел крохотную толику всех этих диалогов, к тому же работаю я с SharePoint Foundation, а в серверной версии наверняка таких диалогов - еще больше!

Как тестировать

Если вы найдете какой-то другой диалог, который вам хотелось бы использовать в собственных целях - сразу возникает вопрос: как понять, что он возвращает? Я использую для этого простенький html-фрагмент, который можно вставить в любой Content Editor на любой странице локального портала. Для вставки html-кода, напомню, на Ribbon'е есть специальная кнопочка в группе Markup:  

Итак, код для тестирования возвращаемых значений диалогов:

<input id="urlTest" type="text"/>
<input onclick="var options={url:document.getElementById('urlTest').value,dialogReturnValueCallback:function(dialogResult,returnValue){if(dialogResult==SP.UI.DialogResult.OK){alert(returnValue)}}};SP.UI.ModalDialog.showModalDialog(options);" type="button" value="Test"/> 


Вставляем этот код, сохраняем изменения, получаем вот такую форму:


Вбиваем в поле ввода url диалога, жмем кнопку Test - всплывает диалог, тестируем его, по завершении всех действий получите alert c искомым возвращаемым значением.

Скопировать из окна сообщения текст можно, жмякнув Ctrl+C.

Дерзайте! :)

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

  1. Прежде всего, Андрей, это очень классная статья! +стопицот!

    А как получать url'ы диалогов? Например, чтобы добыть url диалога "простенький текстовый редактор, тот самый, в котором редактируется HTML из Content Editor'а" я бы средствами хрома раскрыл выпадающее меню http://4.bp.blogspot.com/-b1T3ew7-4Vs/Tg41dWY2zxI/AAAAAAAAATo/D2WYX50Hgpk/s1600/htmlButton.png, потом бы нажал кнопочку для пошагового выполнения кода, мышкой надал бы на пункт меню, который вызывает искомый диалог и по шагам идя по java script'у пытался бы обнаружить искомый url. Путь весьма не удобный. Может быть есть более удобный вариант?

    ОтветитьУдалить
  2. Спасибо :)

    В IE после открытия диалога, правой кнопкой мыши по любому свободному месту, Свойства, там можно скопировать url.

    В хроме и других браузерах не знаю, я тока оперой и IE пользуюсь :(

    ОтветитьУдалить
  3. А есть еще способ, более кроссбраузерный. Тоже довольно простой: подсмотреть src у iframe.

    После открытия диалога, запускаешь FireBug, IE Developer Tools, DragonFly или аналог, жмешь на стрелочку и тыкаешь в заголовок диалога. Ищешь, чуть пониже будет div, в нем iframe, у этого iframe смотришь атрибут src - в нем и обнаружится искомый адрес.

    ОтветитьУдалить
  4. Здорово! Спасибо!

    Пошел по пути дебага, совсем не в нужную сторону.

    ОтветитьУдалить
  5. Андрей спасибо.=)
    Для меня данный хак был просто открытием.
    У меня небольшие вопросы:
    Можно к примеру сделать таким же образом в рибоне кнопку,а скриптом меня значение у конкретного элемента? (типа если у поля есть статус и по нажатию кнопки менять его на определенное значение). И еще я так и не понял про Маску прав.Можете немного пояснить если не сложно.
    Заранее большое спасибо

    ОтветитьУдалить
  6. Виктор, привет!

    Как менять значение поля у конкретного элемента - представлено в самом первом фрагменте кода, изучайте как следует.

    Если вкратце, с помощью SP.ListOperation определяем текущий список и текущий выбранный элемент, а затем обновляем поле, используя Client Object Model (SP.ClientContext).

    ОтветитьУдалить
  7. Спасибо за скорый ответ =)
    Сейчас изучаю и пробую. Если что можно задавать вопросы если что нибудь не понятно будет?
    Сейчас пытаюсь наработать профит, так что не всегда все понятно сразу.

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

    ОтветитьУдалить
  9. Он почему то не видит столбец, в который я пытаюсь отправить параметр т.е. сразу перед
    item.set_item('[Столбец]', [Parametr]);

    Я уже все возможные варианты перепробовал, которые знал.

    ОтветитьУдалить
  10. эмм.. если не видит колонку значит неверно ее референсишь. там вроде бы надо DisplayName а не InternalName. залезь в powershell да посмотри какие значения DisplayName и InternalName у соответствующего пооя из коллекции SPList.Fields.

    P.S. прошу прощения за поздний ответ, 5го у меня Д.Р. было, а потом забыл)

    ОтветитьУдалить
  11. Привет, omlin
    Не мог бы мне помочь с javascript-м "SP.ListOperation.Selection.getSelectedList()".
    У меня Custom-ный список, где в риббоне я поставил кнопку , вызывающий страницу, которая делает автоматическую загрузку выбранных файлов (использовал Uploadify)
    Когда я вызываю, данный кусок кода (getSelectedList) из списка , с выделенным Item-ом, он работает и выдает айди.(я сделал эту кнопку в риббоне списка и в риббоне Item-а, т.е. Edit form )
    Но стоит мне зайти в модальное окно,(к примеру Edit form) и оттуда нажать на кнопку в Ribbon-e, ничего не возвращает.
    Как можно получить ListID из модального окна? Я уже использовал {ListID} и {SelectedListID }из CommandAction в CommandUIHandler-e, не помогли эти методы.

    ОтветитьУдалить
    Ответы
    1. Привет! Попробуй использовать SP.PageContextInfo.get_pageListId()
      И вообще, PageContextInfo - очень полезный класс: http://msdn.microsoft.com/en-us/library/ee552069(v=office.14).aspx

      Удалить
  12. Привет.
    Подскажи, плиз, в чем может быть проблема: в коллбек функции мне приходит returnValue == undefined, хотя файл успешно загрузился в библиотеку. Делаю так:
    SP.UI.ModalDialog.showModalDialog(
    {
    url: '/_layouts/Upload.aspx?List=' + listGuid,
    title: 'Upload',
    dialogReturnValueCallback:
    function (dialogResult, returnValue) {
    if (dialogResult == SP.UI.DialogResult.OK) { //returnValue = undefined :(
    }
    }
    });
    ---
    Пробовал также другие url:
    url: "/_layouts/Upload.aspx?List=myguid&RootFolder=%2FDocLib&Source=DocLib%2FForms%2FAllItems%2Easpx&IsDlg=1"
    url: "/_layouts/Upload.aspx?List=myguid&IsDlg=1",
    url: "/_layouts/RTEUploadDialog.aspx?LCID=1033&Dialog=UploadImage&UseDivDialog=true",
    url: "/_layouts/listform.aspx?PageType=8&ListId=myguid&IsDlg=1",

    Везде (за исключением RTEUploadDialog.aspx) returnValue == undefined (при этом dialogResult приходит правильный 1-выбрали файл, 0-нажали отмену).
    Для RTEUploadDialog.aspx после загрузки файла 1.png приходит returnValue == img src="/DocLib/1.png" alt="1.png"
    Но видимо это из-за того, что после диалога с загрузкой файла и выбора библиотеки куда загружать, появляется еще один диалог, где можно изменить имя и название файла. Т.е. как воркэраунд можно юзать RTEUploadDialog, но при этом задизаблить список, куда можно отправить файл. Но не хочется юзать воркэраунд, должно быть более прямое "каноническое" решение :-)
    PS: Все эти диалоги с разными урлами загружают файл в библиотеку - я проверил. Проблема в том, что после загрузки я хочу в коллбек функции показать в нужном DIV загруженный файл. Опять же, можно последний загруженный файл получить в js через SP.ClientContext, но это воркэраунд №2, что тоже не гуд :-), я хочу чтобы шарик делал то, что он должен делать "по-умолчанию", а не изобретать костыли :-)
    PPS: Может проблема в том, что файл долго загружается в библиотеку? Т.е. нужно не сразу получить значение в коллбеке на закрытие диалогового окна, а реагировать на коллбек загрузки файла непосредственно в DocLib?
    Спасибо!

    ОтветитьУдалить
    Ответы
    1. ... дам более подробный код, чтобы было понятнее, что мне нужно :-)
      1) в верстке страницы есть список загруженных файлов:
      ul id="listAttachments" runat="server" style="width: 250px; height: 200px; overflow: auto"
      2) вот код коллбек функции:
      dialogReturnValueCallback: function (dialogResult, returnValue) {
      //debugger;
      alert(returnValue);
      if (dialogResult == SP.UI.DialogResult.OK) {
      var newFileUrl = (returnValue == undefined) ? "null" : returnValue.newFileUrl;
      var ul = document.getElementById("<%=this.listAttachments.ClientID%>");
      var li = document.createElement("li");
      li.innerHTML = "<" + "a href='" + newFileUrl + "'>" + newFileUrl + "<" + "/" + "a>";
      ul.appendChild(li);
      }

      PS: пришлось слегка ухудшить читаемость кода, т.к. нельзя опубликовать теги a ul с кавычками :-)

      Удалить
    2. Было тоже самое, проблема в том, что нет ни одного Required поля, поэтому SharePoint автоматически закрывает диалог, а не предлагает выбрать метаданные. Добавь в свою библиотеку хотя бы одно обязательное поле (required fileld) и все заработает.

      Удалить

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