пятница, 22 июня 2012 г.

SharePoint: Расширенная подстановка - Использование EntityEditorWithPicker

По долгу службы приходится помимо разработки на SharePoint заниматься и 1С-программированием. Когда в первый раз столкнулся с 8-ой версией данного продукта при разработке конфигурации, сразу что бросилось в глаза и понравилось, так это составной тип данных. И больше всего заинтересовало,  то, что в одном реквизите какого-либо типа метаданных (справочника, документа и проч.) можно указать подстановку объектов разных метаданных, к примеру, элементов только из 2 конкретных справочников или  элемент справочника и документ. Сразу же возникла мысль сделать такую же возможность и для пользователей SharePoint. Наверняка, у многих возникала необходимость подстановки в поле lookup элементов двух, а может, и больше списков. К своему удивлению, я обнаружил, что данное решение можно буквально собрать из готовых решений.

Разработка началась с ознакомления со статьей Customizing EntityEditorWithPicker. Взяв за основу приведенный в ней пример несложно разработать свой пользовательский EntityEditorWithPicker. Это сведется лишь к переопределению трех классов: EntityEditorWithPicker, PickerDialog и SimpleQueryControl. С переопределением первого класса особых затруднений не возникает и собственно мало что там примечательного. Гораздо интересней обстоит дело с двумя другими. Как говорилось раньше пользователю необходимо предоставить возможность выборки элементов из нескольких списков. Забегая немного вперед, следует уточнить, что разрабатываемый элемент управления вначале даст возможность выбрать тот или иной список (т.е., будет композиция элементов управления).

SharePoint extension lookup field


А затем при нажатии кнопки «Browse» элемента EntityEditorWithPicker откроется то самое окно PickerDialog, в котором будет возможно осуществить поиск элементов по тому или иному столбцу выбранного списка и соответственно выбрать из них нужный.


SharePoint custom PickerDialog



По умолчанию при нажатии кнопки «Browse» элемента EntityEditorWithPicker выполняется JavaScript, который открывает в модальном диалоговом окне страницу Picker.aspx с указанными значениями параметров MultiSelect, CustomProperty, EntitySeparator, DialogTitle, DialogImage, PickerDialogType, являющимися в свою очередь свойcтвами класса EntityEditorWithPicker. Самый примечательный из них это, конечно же, CustomProperty, через который можно передать любую строку, и соответственно затем обработать её значение по необходимости как в переопределенном классе PickerDialog, так и в SimpleQueryControl. Оба эти элемента управления находятся на одной этой странице (Picker.aspx).  В данном случае достаточно будет своевременно изменять значение свойства CustomProperty разрабатываемого элемента EntityEditorWithPicker и использовать его в функционалах PickerDialog и SimpleQueryControl. В этом примере в качестве такого параметра передается ID выбранного списка и URL узла, в котором он содержится, а также ID представления списка для заполнения списка столбцов для поиска элементов списка по их значению (выпадающий список с идентификатором mColumnList в SimpleQueryControl) и формирования столбцов табличной части PickerDialog (свойства columnDisplayNames и columnNames). Этот параметр можно передать в качестве строки вида: [URL узла];[ID списка];[ID представления];[InternalName столбца списка для отображения выбранного значения в текстовом поле EntityEditorWithPicker].

Соответственно код класса, наследуемого от PickerDialog будет выглядеть следующим образом:
public class CustomLookupListPickerDialog : PickerDialog
{
    public CustomLookupListPickerDialog()
        : base(new CustomLookupListQueryControl(), new TableResultControl(), new CustomLookupListEntityEditor())
    { }
 
    protected override void OnLoad(EventArgs e)
    {
        string sData = this.Page.Request.QueryString["CustomProperty"];
        List<string> data = sData.Split(';').ToList<string>();
        ResultControl.Visible = false;
        // очищаем массивы информации о столбцах
        ArrayList columnDisplayNames = ((TableResultControl)base.ResultControl).ColumnDisplayNames;
        columnDisplayNames.Clear();
        ArrayList columnNames = ((TableResultControl)base.ResultControl).ColumnNames;
        columnNames.Clear();
        ArrayList columnWidths = ((TableResultControl)base.ResultControl).ColumnWidths;
        columnWidths.Clear();
        // получаем список из текущего семейства узла
        SPList listCurrent = MetaData.List_GetThis(SPContext.Current.Site.Url, data[0], data[1]);
        // получаем представление
        SPView view = listCurrent.Views[new Guid(data[2])];
        // для упрощения сделаем одинаковую ширину у всех столбцов
        int width = (int)100 / view.ViewFields.Count;
        // заполняем массивы информации о столбцах
        foreach (string field in view.ViewFields)
        {
            columnDisplayNames.Add(listCurrent.Fields.GetFieldByInternalName(field).Title);
            columnNames.Add(field);
            columnWidths.Add(width.ToString() + "%");
        }
        base.OnLoad(e);
    }
}

А код класса, наследуемого от SimpleQueryControl, приведен ниже:
public class CustomLookupListQueryControl : SimpleQueryControl
{
    SPList listCurrent;
    SPView view;
    string fldView;
    List<string> data;
 
    public CustomLookupListQueryControl() { }
 
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        EnsureChildControls();
        string sData = this.Page.Request.QueryString["CustomProperty"];
        // парсим строку параметра CustomProperty страницы Picker.aspx
        data = sData.Split(';').ToList<string>();
        // получаем список из текущего семейства узла
        listCurrent = MetaData.List_GetThis(SPContext.Current.Site.Url, data[0], data[1]);
        // "активируем" выпадающий список столбцов списка - иначе поиск будет вестись только по первому столбцу в списке
        mColumnList.AutoPostBack = true;
        mColumnList.SelectedIndexChanged += new EventHandler(mColumnList_SelectedIndexChanged);
        // получаем представление
        view = listCurrent.Views[new Guid(data[2])];
        fldView = data[3];
        // заполняем выпадающий список столбцов списка
        foreach (string field in view.ViewFields)
            mColumnList.Items.Add(new ListItem(listCurrent.Fields.GetFieldByInternalName(field).Title, field));
    }
 
    void mColumnList_SelectedIndexChanged(object sender, EventArgs e)
    {
        ViewState["mColumnList"] = mColumnList.SelectedValue;
    }
 
    protected override int IssueQuery(string search, string groupName, int pageIndex, int pageSize)
    {
        SPListItemCollection coll;
        // если строка поиска пуста - то выводим будем выводить все элементы
        if (String.IsNullOrEmpty(search))
            coll = listCurrent.Items;
        else
        {
            // формируем запрос к данным списка по выбранному столбцу и строки поиска
            SPQuery query = new SPQuery(view);
            query.Query = "<Where><Contains><FieldRef Name='" + mColumnList.SelectedValue + "' /><Value Type='Text'>" + search + "</Value></Contains></Where>";
            coll = listCurrent.GetItems(query);
        }
        // формируем и заполняем данными таблицу результатов запроса
        DataTable dummyTable = new DataTable();
        foreach (string field in view.ViewFields)
            if (!dummyTable.Columns.Contains(field))
                dummyTable.Columns.Add(field);
        foreach (SPListItem li in coll)
        {
            DataRow row = dummyTable.NewRow();
            foreach (string field in view.ViewFields)
                row[field] = ((li[listCurrent.Fields.GetFieldByInternalName(field).Id] != null) ? li[listCurrent.Fields.GetFieldByInternalName(field).Id].ToString() : "");
            dummyTable.Rows.Add(row);
        }
        PickerDialog.Results = dummyTable;
        PickerDialog.ResultControl.PageSize = dummyTable.Rows.Count;
        return dummyTable.Rows.Count;
    }
 
    public override PickerEntity GetEntity(DataRow dr)
    {
        PickerEntity entity = new PickerEntity();
        entity.DisplayText = dr[fldView].ToString();
        entity.Key = dr["ID"].ToString();
        entity.Description = dr[fldView].ToString();
        entity.IsResolved = true;
        entity.EntityData.Add(dr["ID"].ToString(), dr[fldView].ToString());
        return entity;
    }
}
Здесь стоит обратить внимание на метод GetEntity. В качестве свойства Key объекта PickerEntity следует указывать, естественно, уникальный идентификатор выбранного элемента списка, коим является ничто иное как свойство ID элемента списка. Поэтому в рассматриваемом случае либо, представление должно содержать столбец ИД(ID), либо просто в коде нужно ввести в таблицу данных этот столбец и соответствующим образом его заполнить.

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

SharePoint extension lookup field


Тут на помощь придет JavaScript
function FillExtDataPanel(panelname, hiddenfield, ctx) {
    // panelname - id панели информации о выбранном элементе списка
    // hiddenfield - строка, содержащая информацию о столбцах представления списка, определенного в настройках столбца,
    // с их внутренними именами (для получения значений столбцов) и заголовками (для отображения самих столбцов)
    // ctx - идентификатор элемента управления EntityEditorWithPicker
    var resStr = "";
    var div = document.getElementById(panelname);
    var hidden = document.getElementById(hiddenfield);
    var sVals = hidden.value.split(';');
    if (sVals.length > 0) {
        var sTitles = sVals[3].split('^');
        var sFields = sVals[2].split('^');
        for (i = 0; i < sTitles.length; i++) {
            var listitem = GetListItem(sVals[0], sVals[1], document.getElementById(getSubControlID(ctx, "HiddenEntityKey")).value);
            resStr += "<tr><td Class=\"ms-vb2\">" + sTitles[i] + "</td>" +
                "<td Class=\"ms-vb2\">" + listitem.getElementsByTagName("z:row")[0].getAttribute("ows_" + sFields[i]) + "</td></tr>";
        }
    }
    if (resStr.length > 0) {
        div.innerHTML = "<table class=\"ms-listviewtable\">" + resStr + "</table>";
    }
    return resStr;
}
Ссылка на этот скрипт объявляется в свойстве OnValueChangedClientScript объекта класса EntityEditorWithPicker. Функция GetListItem возвращает данные о выбранном элементе списка посредством использования в нём вызов метода GetListItems для получения данных о выбранном элементе списка веб-службы SharePoint Lists.asmx (пример вызова веб-службы SharePoint в JavaScript уже был приведен ранее в этом блоге. Что есть HiddenEntityKey можно узнать из Inside the SharePoint People Picker, описывающей анатомию элемента управления EntityEditorWithPicker.

Напоследок хотелось бы отметить, что, конечно, данный тип поля можно «расширить» до подстановки элементов списков  из других узлов или же разработать первоначальную выборку по определению списка (ListDefinition), как показано на рисунке ниже.

SharePoint extension lookup field


English version - SharePoint Extended lookup field. Part1: Using EntityEditorWithPicker.

Комментариев нет:

Отправить комментарий