Показаны сообщения с ярлыком JavaScript. Показать все сообщения
Показаны сообщения с ярлыком JavaScript. Показать все сообщения

вторник, 30 июня 2015 г.

SharePoint. Как послать email используя JavaScript


У меня возникла необходимость послать email сообщение c формы элемента, сделав это асинхронно из JavaScript. В JSOM я не нашел такой возможности.

Способ № 1
Первое что пришло в голову - сделать на C# веб-часть, которая будет брать данные из url и отсылать почту, код выглядит достаточно коротким:

using (var oSite = new SPSite(SPContext.Current.Web.Url))
using (var oWeb = oSite.OpenWeb(SPContext.Current.Web.ServerRelativeUrl))
{
    string to, subject, body;
    to = Page.Request.QueryString["to"];
    subject = Page.Request.QueryString["subject"];
    body = Page.Request.QueryString["body"];
                
    var headers = new StringDictionary
        {
            {"to", to},
            {"subject", subject},
            {"content-type", "text/html"}
        };
        SPUtility.SendEmail(oWeb, headers, body);
}


Теперь нам необходимо вызвать серверной код GET запросом, код на JavaScript & jQuery будет выглядеть так:

// Отсылаем уведомление
function  SendMail(to, subject, body) {
    $.ajax({
        url: '/SitePages/SendMail.aspx',
        type: 'GET',
        data: { to: to, subject: subject, body: body },
        contentType: 'application/json; charset=utf-8',
        success: function  (response) {
            console.log("success send email to secretary");
        },
        error: function  () {
            console.log("error");
        }
    });
}


Недостаток этого решения в самом решении. Нужно создавать и разворачивать веб-часть, SPUtility недоступен в Sandbox решениях. Возможны прочие причины не использовать серверный код.

Способ № 2
Есть другой пусть. Как известно, мы можем слать письма используя рабочий процесс, так же мы знаем о возможности создавать элементы списка используя JSOM. Т.е. можем создать JavaScript'ом элемент списка, на создании которого запуститься рабочий процесс и отошлет письмо. Чтобы у рабочего процесса была информация куда, с какой темой и что слать - создадим соответствующие поля в списке. Наш элемент списка  должен  иметь 3 столбца:
  • Кому
  • Тема
  • Тело
Итак, у нас будет функция которая будет написана на JavaScript и использовать JSOM для создания элемента в списке:

// Создаем элемент списка
function CreateEmailListItem(emailListTitle, to, subject, body) {
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle(emailListTitle);
    var itemCreateInfo = new SP.ListItemCreationInformation();
    this.oListItem = oList.addItem(itemCreateInfo);

    oListItem.set_item('To', to);
    oListItem.set_item('Subject', subject);
    oListItem.set_item('Body', body);
    oListItem.update();
    clientContext.load(oListItem);

    clientContext.executeQueryAsync(function.createDelegate(this, this.onQuerySucceededEmailListItem), function.createDelegate(this, this.onQueryFailedEmailListItem));
}

function onQuerySucceededEmailListItem() {
    console.log('(Успех. Элемент списка создан.)Item created: ' + oListItem.get_id());
}

function onQueryFailedEmailListItem(sender, args) {
    console.log('Request failed. (Ошибка. Элемент списка не создан.)' + args.get_message() + '\n' + args.get_stackTrace());
}

После создания элемента будет срабатывать рабочий процесс отсылающий письмо. Для этого нам нужно одно действие рабочего процесса:



В редакторе необходимо настроить действие:


Для начала заполним отправителя:


Аналогично необходимо заполнить поля "Тема" и "Тело":


Теперь действие рабочего процесса заполнено:

 Необходимо опубликовать рабочий процесс и обязательно отметить пункт "Автоматически запускать рабочий процесс при создании элемента":
 Теперь попробуем вызвать нашу JavaScript функцию:

CreateEmailListItem('emailList', 'Ulmaskulov_ar@Borets.ru', 'subject', 'body')

После выполнения функции в списке можно увидеть созданный элемент:
После создания выполнится рабочий процесс и отправит письмо. Это решает проблему.

P. S. Не забудьте разобраться с правами на список, иначе пользователи смогут читать сообщения отправленные не им.
Рекомендую перейти в настройки списка: Параметры -> Дополнительные параметры -> Разрешения на уровне элементов -> Доступ на чтение и отметить "Чтение элементов, созданных пользователем". Выглядит это так:


Способ № 3
После публикации ссылки на пост в фейсбуке, в комментариях, мне подсказали еще один способ. Вероятно, это самый удобный из всех возможных. Спасибо Denis Molodtsov и Иван Горбадей.
Суть способа заключается в отправке запроса к SharePoint REST API.
Код выглядит так:

function SendEmail(from, to, body, subject) {
    var siteurl = _spPageContextInfo.webServerRelativeUrl;
    var urlTemplate = siteurl + "/_api/SP.Utilities.Utility.SendEmail";
    $.ajax({
        contentType: 'application/json',
        url: urlTemplate,
        type: "POST",
        data: JSON.stringify({
            'properties': {
                '__metadata': { 'type': 'SP.Utilities.EmailProperties' },
                'From': from,
                'To': { 'results': [to] },
                'Body': body,
                'Subject': subject
            }
        }
        ),
        headers: {
            "Accept": "application/json;odata=verbose",
            "content-type": "application/json;odata=verbose",
            "X-RequestDigest": $("#__REQUESTDIGEST").val()
        },
        success: function(data) {
            alert("Email sent");
        },
        error: function(err) {
            alert(err.responseText);
        }
    });
};

вторник, 8 июля 2014 г.

SharePoint создать и загрузить текстовый файл используя JSOM & REST

У меня возникла необходимость генерировать txt и загружать его в библиотеку SharePoint.
Сам процесс аналогичен загрузке файла. Точнее ознакомиться с процессом можно по ссылке.
В коде, по ссылке выше есть метод PerformUploadInit(), один из параметров fileData можно заполнить обычной строкой, в случае текста не на латинице я получал такое отображение файла:
Проблема решилась сменой кодировки в CP-1251 из Unicode. Для смены кодировки я использовал функцию от cryo.

Конечный код решения по генерации .txt и его загрузке в библиотеку SharePoint:
var currentWebUrl;
var context;
var web;
var digest = "";
var DMap = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101, 102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109, 110: 110, 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117, 118: 118, 119: 119, 120: 120, 121: 121, 122: 122, 123: 123, 124: 124, 125: 125, 126: 126, 127: 127, 1027: 129, 8225: 135, 1046: 198, 8222: 132, 1047: 199, 1168: 165, 1048: 200, 1113: 154, 1049: 201, 1045: 197, 1050: 202, 1028: 170, 160: 160, 1040: 192, 1051: 203, 164: 164, 166: 166, 167: 167, 169: 169, 171: 171, 172: 172, 173: 173, 174: 174, 1053: 205, 176: 176, 177: 177, 1114: 156, 181: 181, 182: 182, 183: 183, 8221: 148, 187: 187, 1029: 189, 1056: 208, 1057: 209, 1058: 210, 8364: 136, 1112: 188, 1115: 158, 1059: 211, 1060: 212, 1030: 178, 1061: 213, 1062: 214, 1063: 215, 1116: 157, 1064: 216, 1065: 217, 1031: 175, 1066: 218, 1067: 219, 1068: 220, 1069: 221, 1070: 222, 1032: 163, 8226: 149, 1071: 223, 1072: 224, 8482: 153, 1073: 225, 8240: 137, 1118: 162, 1074: 226, 1110: 179, 8230: 133, 1075: 227, 1033: 138, 1076: 228, 1077: 229, 8211: 150, 1078: 230, 1119: 159, 1079: 231, 1042: 194, 1080: 232, 1034: 140, 1025: 168, 1081: 233, 1082: 234, 8212: 151, 1083: 235, 1169: 180, 1084: 236, 1052: 204, 1085: 237, 1035: 142, 1086: 238, 1087: 239, 1088: 240, 1089: 241, 1090: 242, 1036: 141, 1041: 193, 1091: 243, 1092: 244, 8224: 134, 1093: 245, 8470: 185, 1094: 246, 1054: 206, 1095: 247, 1096: 248, 8249: 139, 1097: 249, 1098: 250, 1044: 196, 1099: 251, 1111: 191, 1055: 207, 1100: 252, 1038: 161, 8220: 147, 1101: 253, 8250: 155, 1102: 254, 8216: 145, 1103: 255, 1043: 195, 1105: 184, 1039: 143, 1026: 128, 1106: 144, 8218: 130, 1107: 131, 8217: 146, 1108: 186, 1109: 190};

function GenerateAndUploadTXTFileToSharePointLibrary(library,fileName,folder,content){
 var enodedContent = UnicodeToWin1251(content);
 PerformUpload(library,fileName,folder,content);
}

function UnicodeToWin1251(s) {
    var L = []
    for (var i=0; i&lts.length; i++) {
        var ord = s.charCodeAt(i)
        L.push(String.fromCharCode(DMap[ord]))
    }
    return L.join('')
}

function PerformUpload(libraryTitle, fileName, folderTitle, content) {

    var reqExecutor = new SP.RequestExecutor(currentWebUrl);
    var url = "/registry/_api/web/GetFolderByServerRelativeUrl('" + libraryTitle + "/" + folderTitle + "')/Files/add(url='" + fileName + "', overwrite=true)";
    reqExecutor.executeAsync({
        url: url,
        method: "POST",
        headers: {
            //"Accept": "application/json; odata=verbose",
            "X-RequestDigest": digest
        },
        binaryStringRequestBody: true,
        body: content,
        success: function () {
            console.log("Файл удачно загружен.");
        },
        error: function (x, y, z) {
            alert(x); alert(y); alert(z);
            console.log("Не удалось загрузить файл.");
        }
    });
}

function getWebProperties() {
    console.log("Начало выполнения функции 'getWebProperties()'");
    context = SP.ClientContext.get_current();
 web = context.get_web();
 list = web.get_lists();
    context.load(web, 'ServerRelativeUrl');
    context.executeQueryAsync(
 function () {
     console.log("Удалось получить текущий url сайта");
     currentWebUrl = web.get_serverRelativeUrl();
 },
 function () {
     console.log("Не удалось получить текущий url сайта");
 }
   );
}

function myOnload() {
 $.getScript("/_layouts/15/SP.RequestExecutor.js", function (){});
 digest = $("#__REQUESTDIGEST").val();
 getWebProperties();
}

_spBodyOnLoadFunctionNames.push("myOnload");

Пример вызова:
GenerateAndUploadTXTFileToSharePointLibrary('documentLibraryTitle','fileName.txt','folderTitle','Слово');

Прячем подписи к полям на формах SharePoint используя JavaScript и jQuery

При создании столбца мы имеем возможность заполнить его описание:
 Если это сделать, то на формах создания и редактирования, мы сможем видеть описание столбца:
Может возникнуть необходимость оставить описание, но не показывать его на форме.
Для этого подключите к странице jQuery и используйте код:
$('.ms-metadata').hide()

Но для того что бы спрятать описание типа "Пользователь", надо использовать немного другой код:
$('.ms-usereditor span').hide()

Установить ширину столбца списка в SharePoint используя JavaScript и jQuery

Порой возникает необходимость зафиксировать ширину столбцов в представлении списка.
Для этого можно использовать JavaScript и библиотеку jQuery.
Подключите jQuery и добавьте JavaScript функцию:
function setColumnWidth(field, width)
{ 
    $("TH.ms-vh2:contains('" + field + "')").css( "max-width",width );
}
Пример вызова функции:
setColumnWidth('TitleOfColumn', '100px')

пятница, 6 июня 2014 г.

Доработка SPServices Autocomplete

Я, как и многие, давно использую библиотеку SPServices для решения многих задач.
У этой библиотеки есть функционал по автокомплиту:
Но есть у этого механизма несколько недоработок:
  • нет задержки перед показом вариантов
  • ничего не отображается, если нет вариантов
  • нельзя предлагать варианты действий, если ничего не найдено
Т.е. необходимо поменять поведение, что бы варианты появлялись с задержкой в секунду, если ничего не нашли, то показываем как на картинке:
Тут же даем возможность добавить элемент:
Извещаем о добавлении:
Вот код уже поправленной функции $.fn.SPServices.SPAutocomplete:
$.fn.SPServices.SPAutocomplete = function(options) {

        var opt = $.extend({}, {
            webURL: "", // [Optional] The name of the Web (site) which contains the sourceList
            sourceList: "", // The name of the list which contains the values
            sourceColumn: "", // The static name of the column which contains the values
            columnName: "", // The display name of the column in the form
            CAMLQuery: "", // [Optional] For power users, this CAML fragment will be Anded with the default query on the relatedList
            CAMLQueryOptions: "", // [Optional] For power users, allows specifying the CAMLQueryOptions for the GetListItems call
            CAMLRowLimit: 0, // [Optional] Override the default view rowlimit and get all appropriate rows
            filterType: "BeginsWith", // Type of filtering: [BeginsWith, Contains]
            numChars: 0, // Wait until this number of characters has been typed before attempting any actions
            ignoreCase: false, // If set to true, the function ignores case, if false it looks for an exact match
            highlightClass: "", // If a class is supplied, highlight the matched characters in the values by applying that class to a wrapping span
            uniqueVals: false, // If set to true, the function only adds unique values to the list (no duplicates)
            maxHeight: 99999, // Sets the maximum number of values to display before scrolling occurs
            slideDownSpeed: "fast", // Speed at which the div should slide down when values match (milliseconds or ["fast" | "slow"])
            processingIndicator: "_layouts/images/REFRESH.GIF", // If present, show this while processing
			htmlifnothingfound: "Ничего не найдено!", 
            debug: false // If true, show error messages;if false, run silent
        }, options);

        var matchNum;

		
		
        // Find the input control for the column and save some of its attributes
        var columnObj = $("input[Title='" + opt.columnName + "']");
        columnObj.css("position", "");
        var columnObjColor = columnObj.css("color");
        var columnObjWidth = columnObj.css("width");
		// myAdd Добавляем переменную columnObjPadding
		var columnObjPadding = columnObj.css("padding");
        if (columnObj.html() === null && opt.debug) {
            errBox("SPServices.SPAutocomplete",
                "columnName: " + opt.columnName,
                "Column is not an input control or is not found on page");
            return;
        }

        // Remove the 
 which isn't needed and messes up the formatting
        columnObj.closest("span").find("br").remove();
        columnObj.wrap("
"); // Create a div to contain the matching values and add it to the DOM var containerId = genContainerId("SPAutocomplete", opt.columnName); // myAdd изменен цвет на #2A8DD4 как в 2013 по умолчанию columnObj.after("
"); // Set the width to match the width of the input control $("#" + containerId).css("width", columnObjWidth); // Handle keypresses $(columnObj).keyup(function() { // Get the column's value var columnValue = $(this).val(); // Hide the container while we're working on it $("#" + containerId).hide(); // Have enough characters been typed yet? if (columnValue.length < opt.numChars) { return false; } // Show the the processingIndicator as a background image in the input element columnObj.css({ "background-image": "url(" + opt.processingIndicator + ")", "background-position": "right", "background-repeat": "no-repeat" }); // Array to hold the matched values var matchArray = []; // Build the appropriate CAMLQuery var camlQuery = ""; if (opt.CAMLQuery.length > 0) { camlQuery += ""; } camlQuery += "<" + opt.filterType + ">" + columnValue + "</" + opt.filterType + ">"; if (opt.CAMLQuery.length > 0) { camlQuery += opt.CAMLQuery + ""; } camlQuery += ""; // Call GetListItems to find all of the potential values $().SPServices({ operation: "GetListItems", async: false, webURL: opt.WebURL, listName: opt.sourceList, CAMLQuery: camlQuery, CAMLQueryOptions: opt.CAMLQueryOptions, CAMLViewFields: "", CAMLRowLimit: opt.CAMLRowLimit, completefunc: function(xData) { // Handle upper/lower case if ignoreCase = true var testValue = opt.ignoreCase ? columnValue.toUpperCase() : columnValue; // See which values match and add the ones that do to matchArray $(xData.responseXML).SPFilterNode("z:row").each(function() { var thisValue = $(this).attr("ows_" + opt.sourceColumn); var thisValueTest = opt.ignoreCase ? $(this).attr("ows_" + opt.sourceColumn).toUpperCase() : $(this).attr("ows_" + opt.sourceColumn); // Make sure we have a match... if (opt.filterType === "Contains") { var firstMatch = thisValueTest.indexOf(testValue); if ((firstMatch >= 0) && // ...and that the match is not already in the array if we want uniqueness (!opt.uniqueVals || ($.inArray(thisValue, matchArray) === -1))) { matchArray.push($(this).attr("ows_" + opt.sourceColumn)); } } else { // Handles normal case, which is BeginsWith and and other unknown values if (testValue === thisValueTest.substr(0, testValue.length) && // ...and that the match is not already in the array if we want uniqueness (!opt.uniqueVals || ($.inArray(thisValue, matchArray) === -1))) { matchArray.push($(this).attr("ows_" + opt.sourceColumn)); } } }); } }); // Build out the set of list elements to contain the available values var out = ""; // !myAdd if(matchArray.length==0){ matchArray.push(opt.htmlifnothingfound); } for (i = 0; i < matchArray.length; i++) { // If a highlightClass has been supplied, wrap a span around each match if (opt.highlightClass.length > 0) { // Set up Regex based on whether we want to ignore case var thisRegex = new RegExp(columnValue, opt.ignoreCase ? "gi" : "g"); // Look for all occurrences var matches = matchArray[i].match(thisRegex); var startLoc = 0; // Loop for each occurrence, wrapping each in a span with the highlightClass CSS class for (matchNum = 0; matchNum < matches.length; matchNum++) { var thisPos = matchArray[i].indexOf(matches[matchNum], startLoc); var endPos = thisPos + matches[matchNum].length; var thisSpan = "" + matches[matchNum] + ""; matchArray[i] = matchArray[i].substr(0, thisPos) + thisSpan + matchArray[i].substr(endPos); startLoc = thisPos + thisSpan.length; } } // Add the value to the markup for the container out += "

  • " + matchArray[i] + "
  • "; } // Add all the list elements to the containerId container $("#" + containerId).html(out); // Set up hehavior for the available values in the list element // !myAdd if(matchArray[0] != opt.htmlifnothingfound) if(matchArray[0] != opt.htmlifnothingfound){ $("#" + containerId + " li").click(function() { $("#" + containerId).fadeOut(opt.slideUpSpeed); columnObj.val($(this).text()); }).mouseover(function() { var mouseoverCss = { "cursor": "hand", "color": "#ffffff", "background": "#3399ff" }; $(this).css(mouseoverCss); }).mouseout(function() { var mouseoutCss = { "cursor": "inherit", "color": columnObjColor, "background": "transparent" }; $(this).css(mouseoutCss); });} // If we've got some values to show, then show 'em! if (matchArray.length > 0) { // !myAdd delay(1000) $("#" + containerId).slideDown(opt.slideDownSpeed).delay(2000); } // Remove the processing indicator columnObj.css("background-image", ""); }); };

    Просто замените код из библиотеки SPServices кодом выше.
    Правда теперь и прикреплять автокомплит надо несколько иначе:
    function AddAutoCompleteContractor() {
    	console.log("Начало выполнения функции 'AddAutoCompleteContractor()");
    	//Добавляем autocomplete для поля "Наименование контрагента"
    	$().SPServices.SPAutocomplete({	
    			sourceList: "Contractors",
    			sourceColumn: "Title",
    			columnName: "Наименование контрагента",
    			ignoreCase: true,
    			filterType: "Contains", 
    			numChars: 3,
    			slideDownSpeed: 400,
    			htmlifnothingfound: "Ничего не найдено! <a href='#' onClick='ShowPopupContractor()'>Добавить?</a>", 
    			debug: true
    		});
    	}
    
    Добавили опцию "htmlifnothingfound" - в ней указывайте html, который будет отображаться, если ничего не найдено.

    Так же был изменен цвет, под 2013 и ширину, что бы вровень было. В 2013 padding появился.
    Теперь нам необходимо добавить код, который покажет popup:

    function ShowPopupContractor() {
    	console.log("Начало выполнения функции 'ShowPopupContractor()");
    	$("#Anchor").append("Добавьте контрагента:
    
    
    
    
    
    ");
    	$('#popupDivContractor').show();
    		SP.UI.ModalDialog.showModalDialog({
    			html: document.getElementById('popupDivContractor'),
    			title: "Введите комментарий",
    			allowMaximize: false,
    			showClose: false,
    			autoSize: true,
    			dialogReturnValueCallback: onPopUpCloseCallBackContractor
    		});
    	}
    

    Дальше добавим функцию добавление элемента списка и колбэки, которые отработают при удачном\неудачном завершении процесса создания элемента:
    function createListItemContractor() {
    	console.log("Начало выполнения функции 'createListItemContractor()");
        var clientContext = new SP.ClientContext(currentWebUrl);
        var oList = clientContext.get_web().get_lists().getByTitle('Contractors');        
        var itemCreateInfo = new SP.ListItemCreationInformation();
        this.oListItem = oList.addItem(itemCreateInfo);
        oListItem.set_item('Title', $("#ContractorInput").val());
        oListItem.update();
        clientContext.load(oListItem);
        clientContext.executeQueryAsync(
            Function.createDelegate(this, this.onQuerySucceededСreateListItemContractor), 
            Function.createDelegate(this, this.onQueryFailedСreateListItemContractor)
        );
    }
    
    function onQuerySucceededСreateListItemContractor() {
    	console.log("Начало выполнения функции 'onQuerySucceededСreateListItemContractor()");
        console.log('Элемент создан: ' + oListItem.get_id());
    }
    
    function onQueryFailedСreateListItemContractor(sender, args) {
    	console.log("Начало выполнения функции 'onQueryFailedСreateListItemContractor()");
        console.log('Ошибка при создании элемента. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
    

    Теперь добавим функции, которые закрепим за кнопкой Добавить и Отмена.
    При добавлении будет создавать элемент и закрытие формы, в случае нажатия по кнопке Отмена - только закрытие формы. Важно отметить, что в этих функциях мы сообщаем и результат для колбэка закрытия формы. Именно в колбэке логика по которой мы показываем уведомление.
    function onPopUpCloseCallBackContractor(result, returnValue) {
        console.log("Начало выполнения функции 'onPopUpCloseCallBackContractor()");
    	$("nobr:contains('Наименование контрагента')").closest('tr').find("input").val("");
    	$("nobr:contains('Наименование контрагента')").closest('tr').find("input").keyup()
        if (result == SP.UI.DialogResult.OK) {
            SP.UI.Status.removeAllStatus(true);
            var sId = SP.UI.Status.addStatus("Контрагент добавлен!");
            SP.UI.Status.setStatusPriColor(sId, 'green');
        }
        else if (result == SP.UI.DialogResult.cancel) {
            SP.UI.Status.removeAllStatus(true);
            var sId = SP.UI.Status.addStatus("Вы не стали добавлять контрагента");
            SP.UI.Status.setStatusPriColor(sId, 'yellow');
        }
    }
    
    function closePopupOkContractor() {
        console.log("Начало выполнения функции 'closePopupOkContractor()");
        createListItemContractor();
        SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, null);
    }
    
    function closePopupCancelContractor() {
        console.log("Начало выполнения функции 'closePopupCancelContractor()");
        SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel, null);
    }
    

    Пример работы:

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

    После старта рабочего процесса из формы элемента - Вас вернет на представление списка.
    Поменять такое поведение можно, если добавить немного JavaScript кода на страницу с представлением списка. Код будет читать referrer, брать ID и делать редирект на форму элемента списка. В нашем случае на форму просмотра. При это важно, что бы код отработал только если Вы перешли на страницу после старта рабочего процесса. Это мы сможем понять по присутствию "Workflow.aspx" в referrer.

    Код выглядит так:
    // Функция позволяет взять параметр из referrer
    function getParameterByName(name, url) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
    results = regex.exec(url);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }
    
    // Получаем referrer
    var ref = document.referrer
    // Получаем ID из referrer
    var ID = getParameterByName("ID", ref)
    if (ref.indexOf("Workflow.aspx") > -1){
    // Делаем редирект
        window.location = "/office/Lists/Contractors/DispForm.aspx?ID=" + ID
    }

    Теперь надо добавить это все в представление списка, воспользуемся SharePoint Designer 2013:
    В моем случая я имею только одно представление, откроем его в расширенном режиме и добавим код, как на картинке:
    Сохраните изменение и проверьте работу.

    понедельник, 28 апреля 2014 г.

    Показать поле "Создан" на форме элемента

    Некоторые поля, например 'Id' или 'Создано' ('Created') невозможно отображать изначально на формах. В SharePoint Designer они в списке столбцов не отображаются. Заказчик просит изобразить на форме примерно следующее:


    Для этого необходимо создать новую форму и добавить в нужное Вам место следующий серверный элемент SharePoint:
    
    

    В итоге у нас получится примерно следующее:
    Не очень красиво, необходимо сменить формат даты. Что бы было еще и на нашем языке.
    Для этого мы с Вами попробует библиотеку momentjs.com

    Для этого ее надо скачать и подключить к странице. Что бы мы могли отобразить время на нашем языке - качать необходимо moment-with-langs.min.js
    Теперь получаем дату в старом формате, меняем и вставляем обратно в страницу.
    Что бы пользователь не видел сам процесс обработки - прячем контейнер со старой датой:

    <tr>
        <td colspan="2">
            <h1>
                Заявка создана:
                <span id="DateCreationLabel" style="color:navy;display:none">
                    <SharePoint:ListItemProperty Property="Created" ID="CreatedDateField" runat="server" />
                </span>
            </h1>
        </td>
    </tr>
    

    Над html выше - выполняем JS ниже:

    var date = $('#DateCreationLabel').text();
        if (moment(date).isValid()) {
            moment.lang('ru');
            date = moment(date).format('LL');
            $('#DateCreationLabel').text(date);
        }
        $('#DateCreationLabel').show();
    
    После выполнения кода видим следующее:
    Вполне приемлемо.

    Tabs in SharePoint form

    Опишу краткий пример как добавить функционал jQuery UI к формам SharePoint на примере табов.
    Каких то особых изменений при добавлении на обычную страницу не имеется.
    Создайте новую форму просмотра.
    Добавьте на нее html перед таблицей с полями:
    <div id="tabs">
        <ul>
            <li><a href="#FieldGroup1">Поле 1 и 2</a></li>
            <li><a href="#FieldGroup2">Поле 3 и 4</a></li>    
        </ul>
        <div id="FieldGroup1"></div>     
        <div id="FieldGroup2"></div>
    </div>

    Потом сделайте ссылки на необходимые файлы В нашем случае это сама библиотека jQuery, библиотека jQuery UI и на CSS:
    <script type="text/javascript" src="/ScriptLibrary/jquery-1.11.0.min.js"></script>
    <script type="text/javascript" src="/ScriptLibrary/jquery-ui-1.10.4.min.js"></script>
    <link href="/ScriptLibrary/jquery-ui-1.10.4.min.css" rel="stylesheet">
    <script type="text/javascript">
    function myOnload(){
        $('#tabs').tabs();
    }
    _spBodyOnLoadFunctionNames.push("myOnload");
    </script>

    Как результат Вы увидите табы. Теперь необходимо в них добавить поля. Представим что в нашем списке 2 поля: Title; Title2. Добавьте в div c id="FieldGroup1" html:
    <div id="FieldGroup1">
    <table border="0" cellspacing="0" width="100%">    
     <tr>
      <td width="190px" valign="top" class="ms-formlabel">
       <H3 class="ms-standardheader">
        <nobr>Title<span class="ms-formvalidation"> *</span>
        </nobr>
       </H3>
      </td>
      <td width="400px" valign="top" class="ms-formbody">
       <SharePoint:FormField runat="server" id="ff1{$Pos}" ControlMode="New" FieldName="Title" __designer:bind="{ddwrt:DataBind('i',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}"/>
       <SharePoint:FieldDescription runat="server" id="ff1description{$Pos}" FieldName="Title" ControlMode="New"/>
      </td>
     </tr>
    </table>
    

    а в div c id="FieldGroup2" html:
    <div id="FieldGroup2">
    <table border="0" cellspacing="0" width="100%">    
     <tr>
      <td width="190px" valign="top" class="ms-formlabel">
       <H3 class="ms-standardheader">
        <nobr>Title2<span class="ms-formvalidation"> *</span>
        </nobr>
       </H3>
      </td>
      <td width="400px" valign="top" class="ms-formbody">
       <SharePoint:FormField runat="server" id="ff1{$Pos}" ControlMode="New" FieldName="Title2" __designer:bind="{ddwrt:DataBind('i',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title2')}"/>
       <SharePoint:FieldDescription runat="server" id="ff1description{$Pos}" FieldName="Title2" ControlMode="New"/>
      </td>
     </tr>
    </table>
    

    Для скриншотов я использовал вариант с 4 столбцами: Title,Title2,Title3,Title4.
    Все нажатия кнопки "сохранить" все данные удачно попадают в список.

    Должен Вас заранее предупредить, что Вам придется самим реализовать механизм, который будет подсвечивать вкладки, в которых имеются незаполненные, но обязательные к заполнению поля. В противном случае пользователь подумает что форма сломалась.

    InfoPath умер. Редактирование форм. Замена и аналоги.

    Как известно 2013 - последняя версия InfoPath.
    Это значит, что в следующей версии SharePoint его не будет. Новость не новая. Но остается ряд вопросов. Понятно, что создавать новые решения на InfoPath, мысль не лучшая. Нет смысла продвигаться в этом направлении в плане знаний и опыта. Так же нельзя будет мигрировать формы на новую версию SharePoint.
    Нужно отдать должное - я сэкономил кучу времени используя InfoPath. Хотя и не сразу понял, что создавать большие решения на основе InfoPath это несколько рискованно. Неудобная отладка. При изменении xsd заполненные формы не работали, т.к. не соответствовали новой схеме. Приходилось писать код, который сам обновляет уже созданные формы до последней версии схемы. Но механизм правил позволял мне на лету создавать простые решения, которые потом мог поддерживать менее квалифицированный персонал.

    Как можно редактировать формы сейчас? Ниже перечислены основные способы:

    1. JavaScript
    2. Nintex Forms
    3. SPForm
    4. K2
    Есть и менее популярные решения, например pistachio.

    П. 2 разработан нашими соотечественниками.
    Все платные, кроме п. 1, он же и самый трудоемкий.
    При самостоятельном редактировании форм, используя JavaScript, помимо привычного jQuery есть крайне полезная библиотека SPServices.

    Редактирование JS для SharePoint

    JSOM становиться все популярнее. Разработчиков SharePoint все плотнее толкают в сторону клиентской разработки. Так же появился CSR и TypeScript c полезными дополнениями. И несмотря на явное сопротивление, я все же начинаю все активнее писать JavaScript код. Создав несколько решений используя JavaScript, вместо Custom field type, удалось получить небольшой опыт удачного использования окружения и инструментов. Главный вывод который я сделал - не использовать для редактирования JS кода SharePoint Designer 2013.
    Необходимо делать ссылку на файл c JS кодом, а сам файл класть в библиотеку. Подключать по WebDav к проводнику. Включить версионность в библиотеке со скриптами. Дальше Вы можете редактировать код в Visual Studio или Webstorm:


    Для JS рекомендую использовать webstorm. Он не много стоит.

    SharePoint custom file uploader by JSOM & REST\oData

    SharePoint имеет встроенную возможность прикреплять файлы к элементам списка.

    Но нет возможности иметь 2 поля. Что бы логически разделять документы.
    Такого столбца просто нет в списке.
    Так же нет возможности задать библиотеку, куда складывать вложения.
    Что бы просмотреть вложенные элементы, необходимо открыть элемент списка.
    Решением может быть создание своего custom field type.
    Использовать серверный код для реализации функционала. В таком случае ограничений почти нет.
    Можно купить. Есть несколько предложений на рынке.
    Я же кратко покажу как добавить такой функционал без серверного кода.
    Решение должно иметь возможность явно указать библиотеку, куда складывать файлы.
    Не блокировать интерфейс на время загрузки.
    Иметь возможность загрузки файлов большого размера.
    Решение должно сразу же отображать загруженный файл и предоставлять возможность его удаления.
    Все должно быть без постбэков.
    Все будет сделано на JavaScript.

    Для начала создайте новую форму просмотра.
    Это можно сделать в SharePoint Designer.
    Имя папки для хранения файлов- "Documents".
    Библиотека указывается в html элементе для загрузки файлов:
    onchange="UploadFile('Documents', 'fileinputInit')"
    Добавьте в нее html:
    <tr> 
        <td width="190px" valign="top" class="ms-formlabel"> 
            <h3 class="ms-standardheader"> 
                <nobr>Документы по открытию</nobr> 
            </h3> 
        </td> 
        <td width="400px" valign="top" class="ms-formbody"> 
            <input style="margin-left:2px" type="button" id="browseButton" value="Добавить файл ..." onclick="fileinputInit.click()" />
            <input type='file' id='fileinputInit' onchange="UploadFile('Documents', 'fileinputInit')" style='position:absolute;visibility:hidden;' multiple="multiple" /> 
            <div id="attachedFilesInit"> 
                <h2 id="initAnchor"></h2> 
            </div> 
        </td>
    </tr> 
    
    Этот html надо вставить в таблицу полями формы.
    Что бы поле загрузки располагалось после поле "Название", вставьте код выше после указанного ниже:
        <td width="190px" valign="top" class="ms-formlabel"> 
            <h3 class="ms-standardheader"> 
                <nobr> 
                    Название<span class="ms-formvalidation"> *</span> 
                </nobr> 
            </h3> 
        </td> 
        <td width="400px" valign="top" class="ms-formbody"> 
            <SharePoint:FormField runat="server" id="ff1{$Pos}" ControlMode="New" FieldName="Title" __designer:bind="{ddwrt:DataBind('i',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}" /> 
            <SharePoint:FieldDescription runat="server" id="ff1description{$Pos}" FieldName="Title" ControlMode="New" /> 
        </td> 
    </tr>
     Потом добавить после:
    
    
    Этот JS
    
    
    
    
    
    Решение работает асинхронно.
    Загрузки делалась именно с помощью REST, т.к. JSOM имеет ограничение файла на загрузку в 2 Mb.

    В результате у Вас должно быть как на гиф ниже:

    Решение есть куда дорабатывать.
    Для начала отключить возможность сабмита формы, на время загрузки.
    И индикатор загрузки в список файлов.



    среда, 16 апреля 2014 г.

    Баг SharePoint Designer 2013

    В связи с известиями о прекращении развития InfoPath и его отсутствии в новой версии SharePoint, а следовательно и невозможности миграции форм - было принято решение реализовать задачу с использованием JavaScript. Все делать исключительно на клиенте.

    Изначально весь JS вставлял в стандартные формы списка.
    Периодически весь JS код переставал работать.
    Причиной было поведение SharePoint Designer 2013.
    В код, который сбрасывал состояние элемента:

    window.reset = function (e) {
       e.wrap('<form>').closest('form').get(0).reset();
       e.unwrap();
    }
    


    Дизайнер добавлял:
    <head> 
    <meta name="WebPartPageExpansion" content="full" /> 
    </head> 
    
    В итоге я получал:
    window.reset = function (e) { 
        console.log("Начало выполнения функции 'window.reset"); 
        e.wrap('<head> 
        <meta name="WebPartPageExpansion" content="full" /> 
        </head> 
        <form>').closest('form').get(0).reset(); 
        e.unwrap(); 
    }
    


    Решить удалось подключая код отдельным файлом.
    Как и должно быть.