понедельник, 8 августа 2016 г.

Создание "сложных" фильтров в веб-части на странице публикаций

Бывают случаи, когда есть необходимость создать «сложный» фильтр в представлении. Сложный – это громко сказано, с виду фильтр прост до безобразия, но вот реализовать через веб интерфейс не представляется возможным. К примеру такое уловие:
(«Статус» == «Новая») или («Статус» == «Не назначена» и «Отдел» == «Название отдела»).
К примеру можно решить задачу подменив CALM запрос в параметрах веб-чатс, но рассмотрим случай, когда есть страница публикаций и на ней находится несколько веб-частей с разными фильтрами. И тут возникает проблема, через дизайнер нет возможности поправить CAML запрос…

среда, 13 июля 2016 г.

Изменение привязки OOS к серверу SharePoint 2016

Как уже известно, OOS (Office Online Server) в SharePoint 2016 заменил OWA (Сервер Office Web Apps) используемого в SharePoint 2013. В этой статье я расскажу, как поменять привязку в SharePoint 2016. К примеру, из одного сервера OOS выросла ферма из нескольких серверов и имя поменялось.
Первое что делаем, это удаляем существующие привязки.

Remove-SPWOPIBinding -All:$true

среда, 22 июня 2016 г.

Запуск рабочего процесса с помощью PowerShell

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

среда, 1 июня 2016 г.

Не запускается служба User Profile Synchronization Service если используется SQL AlwaysOn

Сегодня добавлял в ферму третий сервер SharePoint, с виду было все как обычно…
Запустил требуемые службы и когда дело до шло до UPS, то она отказывалась стартовать.
Полез смотреть ULS и нашел это

Failed to set the new Service Broker on database User_SyncDB on server SQL. Exception: The operation cannot be performed on database "User_SyncDB" because it is involved in a database mirroring session or an availability group. Some operations are not allowed on a database that is participating in a database mirroring session or in an availability group.  ALTER DATABASE statement failed.  ALTER DATABASE statement failed.

После анализа логов стало понятно, что из-за того, что база находится в группе доступности служба не может произвести требуемые операции с базой User_SyncDB.



После удаления базы из группы доступности повторно запустил службу UPS и она запустилась. Вернул базу в группу доступности.
Вот тебе и поддержка AlwaysOn :)

вторник, 31 мая 2016 г.

Восстановление стандартной формы на примере NewForm.aspx

В случае если случайно удалена или изменена стандартная форма SharePoint, то ее можно легко восстановить. Ведь по факту SharePoint генерит ее содержание уже при открытии
Допустим есть список Test в котором нет формы NewForm.aspx

понедельник, 30 мая 2016 г.

this product requires microsoft .net framework 4.5 при установке SharePoint 2013 на сервер 2012 R2

Сегодня устанавливал SharePoint 2013 SP1 сервер 2012 R2 и после установки всех пререквезитов запустил инсталлер SharePoint и тут ошибка.

после некоторого поиска решения в инете помогло удаление на сервере обновления KB3102467.
перезагрузил сервер и все прошло отлично.
причиной ошибки был .Net Framework 4.6 который нужно было деинсталлировать, чтобы инсталлятор мог использовать .Net Framework 4.5

пятница, 13 мая 2016 г.

Публикация файлов в библиотеке с помощью PowerShell

Зачастую работая разрабатывая решения, я кладу файлы в «Библиотеку стилей» (Style Library) и при загрузке туда файлов они имеют минорную версию и не доступны большинству пользователей. Что бы не опубликовывать каждый файл руками, ведь файлов может быть очень много, когда используешь готовую библиотеку с доп. фалами (js, css, фалы локализации и т.п.), я написал короткий скрипт, который пробегает по всей библиотеке и публикует файлы.

пятница, 29 апреля 2016 г.

среда, 20 апреля 2016 г.

Ссылка на просмотр свойств документа в представлении списка

В этой статье я расскажу как можно переопределить шаблон рендера поля и вывести ссылку на просмотр свойств документа.
по умолчанию что бы добраться до свойств документа нужно сделать несколько кликов.

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

четверг, 14 апреля 2016 г.

Удаление ссылки с поля подстановки в представлении списка.

В этой статье я расскажу, как можно убрать ссылки с Lookup поля в представлении. Для этого мы переопределим шаблон рендера полей.
Для этого нам нужно или создать файл js и подключить его к веб-части через jsLink, можно добавить ссылку на файл через веб-часть «Редактор скриптов» или можно просто добавить код непосредственно в веб-часть «Редактор скриптов».

четверг, 7 апреля 2016 г.

Долгое обновление элемента через REST при использование балансировщика NetScaler

Недавно создали новую ферму из двух WFE, при этом балансировка реализована через NetScaler.
Было разработано решение, работающее по средствам REST запросов. Казалось, что все работает хорошо, но вот в один момент мы заметили, что при работе через Internet Explorer при обновлении элемента SharePoint не приходил ответ с сервера, хотя по факту элемент обновлялся быстро. При этом через Chrome ответ приходил сразу.
Первое что было замечено, что ответ приходил, но спустя 2 минуты после отправки запроса.
Начали изучать далее, поставили Wireshark на локальный компьютер, ничего подозрительного не было замечено. Тогда поставили на сервер, и тут сразу заметили, что при работе через Chrome балансировщик принимал пакеты с сервера, а при работе через IE пакеты не принимались.

Решение

Нам помогла статья

Page not rendering properly when Sharepoint2013 is Accessed via Internet Explorer

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

четверг, 28 января 2016 г.

disabled поля многострочный текст с форматированием (jQuery + SharePoint 2013 Fields Part 5)

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



 или вообще все удалить зайдя в редактор

я решил разобраться как SharePoint понимает, что нужно показать риббон.

понедельник, 14 декабря 2015 г.

воскресенье, 15 ноября 2015 г.

Удаление промежуточных версий файлов через PowerShell

Согласно документации MS количество промежуточных версий у списка/библиотеки должно быть не более 511, но я заметил что когда работаешь с файлом через WebDav, то не учитывается лимит который установлен в списке/библиотеки.

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

среда, 21 октября 2015 г.

среда, 9 сентября 2015 г.

представления в списке Доска обсуждений

При создании списка «Доска обсуждений» мы получаем представление AllItems.aspx которое не доступно для редактирования. Если присмотрится внимательно, то можно заметить, что переключение между отображением информации происходит через JS .
Немного углубившись я заметил следующее:
Изменение отображения происходит при вызове функции

SP.UI.Discussions.SortFilterItem.onPivotControlMenuOptionClick('SortItem1')



При изменении параметра меняется отображение данных.Использование SortItem1 равносильно нажатию на «Последние»

Так же при использовании разных параметров меняет способ визуализация представления.

понедельник, 7 сентября 2015 г.

Форматирование календаря с помощью JS + REST

Сегодня я расскажу, как можно произвести форматирование в представлении Календарь стандартного списка. В интернете можно найти решения, когда создается вычисляемое поле, в него подается значение из нескольких полей элемента. В представлении выводится вычисляемое поле, а дальше с помощью JS происходит форматирование и скрытие лишней информации. В целом решение не плохое, но мне не особо понравилось, и я решил пойти по другому пути…

Берем представление списка «Календарь» пробегаем по всем ссылкам на в календаре и формируем массив ссылок.


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


function  getColor(login)
{
 var color;
 login = login.replace("i:0#.w|domain\\","");
 switch(login){
  case "Kaplin Vladimir" : color = "red"; break;
  case "P Michael" : color = "green"; break;
  case "F Pavel" : color = "rgba(254, 164, 0, 1)"; break;

  default: color = "rgb(78, 142, 227)"
  
 }
 return color;
}

function postRender()
{
 data = jQuery("a[href*='.aspx?ID=']");
 //обработка ссылок на элементы
 var arrayID = [] 
 for (var i=0; i <  data.length; i++)
 { 
  var str = data[i].toString();
  var start = str.indexOf('?ID=');
  var end = str.indexOf('"',start);
  if (end == -1){
   var id = str.slice(start+4);
  }
  else{
   var id = str.slice(start+4, end+1);
  }
 
  arrayID.push("Id eq " + id);
 } 
 //работа с объектами элементов
 if (arrayID.length > 0)
 {
  var getItems = getListItem(arrayID.join(" or "));
  getItems.done(function (data, status, xhr) {   
   var items = data.d.results; 
   for(var i=0; i < items.length; i++ )
   {   
    var url = String.format("a[href*='.aspx?ID={0}']", items[i].ID)
    if(items[i].Editor.Title)
    {
     console.log(items[i].Editor.Title)
     var color = getColor(items[i].Editor.Title)
     jQuery(url).closest(".ms-acal-item").css("background-color", color)      
    };    
   }
  })
  getItems.fail(onError)
 }
}
function onError(error) {
    console.log(error.responseText);
}


function getListItem(itemId) { 
 var listTitle = "Заявки"  // название списка
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var selectFields = ["Id","Title","Editor/Id","Editor/Title"]; // получаемые поля
 var expandFields = ["Editor/Id","Editor/Title"]; // поля подстановки 
 var listItemUri = webUrl + "/_api/web/lists/GetByTitle('"+ listTitle + "')/Items?$filter=" + itemId+ "&$select="+selectFields.join()+"&$expand="+expandFields.join();
 request = jQuery.ajax({
      url: listItemUri,
      type: "GET",
      headers: { "accept": "application/json;odata=verbose" }
     }); 
 request.done(function (data){console.log('данные REST полученны, объектов - ' + data.d.results.length );});  
 request.fail(function (){console.log('Ошибка при вылнении запроса REST')}); 
 return request;       
}


/* =======================================================================
блок для вызова функции обработки при изменении представления месяц - день 
======================================================================*/ 
 function onCalendarGridsRendered(){
    setTimeout(function () { 
        postRender()
    }, 100);
}

function onCalendarResized(){
    setTimeout(function () { 
        //Add your functionality here
  postRender()
    }, 100);
}

SP.SOD.executeOrDelayUntilScriptLoaded(function () {

    //Week or Day Calendar View
    SP.UI.ApplicationPages.DetailCalendarView.prototype.renderGrids_Old = 
        SP.UI.ApplicationPages.DetailCalendarView.prototype.renderGrids;
    SP.UI.ApplicationPages.DetailCalendarView.prototype.renderGrids = 
        function SP_UI_ApplicationPages_DetailCalendarView$renderGrids($p0) {
        this.renderGrids_Old($p0);

        onCalendarGridsRendered();
    };
    
    //Month Calendar View
    SP.UI.ApplicationPages.SummaryCalendarView.prototype.renderGrids_Old = 
        SP.UI.ApplicationPages.SummaryCalendarView.prototype.renderGrids;
    SP.UI.ApplicationPages.SummaryCalendarView.prototype.renderGrids = 
        function SP_UI_ApplicationPages_SummaryCalendarView$renderGrids($p0) {
        this.renderGrids_Old($p0);

        onCalendarGridsRendered();
    };

    //Resize Calendar
    SP.UI.ApplicationPages.CalendarStateHandler.prototype.parentResized_Old = 
        SP.UI.ApplicationPages.CalendarStateHandler.prototype.parentResized;
    SP.UI.ApplicationPages.CalendarStateHandler.prototype.parentResized = 
        function SP_UI_ApplicationPages_CalendarStateHandler$parentResized() {
        this.parentResized_Old();

        onCalendarResized();
    };
}, "SP.UI.ApplicationPages.Calendar.js"); 

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

понедельник, 10 августа 2015 г.

Создание элемента списка в папке используя REST

Все больше в своих решениях я использую REST запросы и для одной из задач необходимо было создавать элементы в папках.

Для начала нужно проверить есть ли папка или папку нужно ее создать:
Пример :
 checkingExistenceFolder("Билеты", "Folder 10")

function checkingExistenceFolder(listTitle, folderTitle)
{
 filter = "ContentTypeId eq '0x0120001EE026E991F8644A85A5F2888FADB06D' and Title eq '" + folderTitle +"'"; 
 query =  getRest(listTitle,"",filter,"","")
 query.done(function (data, status, xhr) {
  if(data.d.results.length > 0)
  {
   console.log(data.d.results.length);
   console.log("папка '" + folderTitle + "' найдена ")
  }
  else{
   console.log("папка '" + folderTitle + "' не найдена");  
   createFolder(listTitle,folderTitle)
  }
  
  
 });
 query.fail(function (){
  console.log("папка '" + folderTitle + "' не найдена");  
 }); 
 
}
function createFolder(listTitle,folderName)
{ 
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/Items/";
 console.log(uri);
 var getlistType = getListEntityTypeName(listTitle);
 getlistType.done(function (data, status, xhr) {
  console.log(data.d.EntityTypeName)
  var listType = data.d.EntityTypeName
 
  var body = "{ '__metadata': { 'type': 'SP.Data." + listType + "Item' },  'Title': '" + folderName + "', 'FileLeafRef': '" + folderName + "', 'ContentTypeId': '0x0120001EE026E991F8644A85A5F2888FADB06D'}"
  var newItem = addListItem(listTitle, body);
  newItem.done (function (data, status, xhr) {
   console.log(data)
   var newItemId = data.d.Id;
   var body = "{ '__metadata': { 'type': 'SP.Data." + listType + "Item' },  'FileLeafRef': '" + folderName + "' }";
   updateListItem(listTitle, newItemId, body);
   
   
  });
  
 });  
} 


function addListItem(listTitle,body)
{
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/Items/"; 
 var query = jQuery.ajax({
     url: uri,
     type: "POST",
     data: body,
     contentLength: body.length,
     headers: {  "accept": "application/json; odata=verbose", 
        "content-type" : "application/json; odata=verbose",
        "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val() }
    }); 
 query.done(function (data){ console.log('элемент создан'); console.log(data)
 
  });  
 query.fail(function (error){console.log('Ошибка при создании элемента'); console.log(error)}); 
 return query;
}

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


перемещение 
moveListItem( "Билеты", "Folder 10", 26)


function moveListItem( listTitle, folderTitle, itemId)
{
 var getlistType = getListEntityTypeName(listTitle);
 getlistType.done(function (data, status, xhr) {
  console.log(data.d.EntityTypeName)  
  var listType = data.d.EntityTypeName;
  var listInternalName = listType.substring(0, listType.length - 4 )
  console.log(listInternalName)
    var webUrl = _spPageContextInfo.webAbsoluteUrl;
  var uri = webUrl+ "/_api/web/getfilebyserverrelativeurl('/Lists/"+ listInternalName + "/" + itemId+ "_.000')/moveto(newurl='/Lists/"+ listInternalName + "/" + folderTitle + "/" + itemId+ "_.000',flags=1)";
  var query = jQuery.ajax({
   url: uri,
   type: "POST",
   headers: {  "accept": "application/json; odata=verbose", 
      "content-type" : "application/json; odata=verbose",
   "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val()} 
   });
   query.done(function (data){ console.log('элемент создан'); console.log(data) });  
   query.fail(function (error){console.log('Ошибка при создании элемента'); console.log(error)}); 
  

 });
}
function getListEntityTypeName(listTitle)
{
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/EntityTypeName";
 var queryAjax = jQuery.ajax({
      url: uri,
      type: "GET",
      headers: {  "accept": "application/json; odata=verbose", 
         "content-type" : "application/json; odata=verbose",
         "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val() }
     }); 
 queryAjax.done(function (data){ console.log('EntityTypeName полученны'); console.log(data)});  
 queryAjax.fail(function (){console.log('Ошибка при вылнении запроса EntityTypeName')}); 
 return queryAjax; 
} 
Если заметили, то мне пришлось еще использовать доп функцию которая возвращает  EntityTypeName. в первом случае я его использую для создания папки, а второй раз при перемещении элемента, т.к. нужно указывать путь (Url)

Еще пришлось обновлять элемент при создании папки, т.к. при ее создании путь и Title верный, но вот Name имеет вид "12_.000"

По этому у созданной папки указываем 'FileLeafRef', т.к. при создании заполнить его не получается.

скрипт целиком

function checkingExistenceFolder(listTitle, folderTitle)
{
 filter = "ContentTypeId eq '0x0120001EE026E991F8644A85A5F2888FADB06D' and Title eq '" + folderTitle +"'"; 
 query =  getRest(listTitle,"",filter,"","")
 query.done(function (data, status, xhr) {
  if(data.d.results.length > 0)
  {
   console.log(data.d.results.length);
   console.log("папка '" + folderTitle + "' найдена ")
  }
  else{
   console.log("папка '" + folderTitle + "' не найдена");  
   createFolder(listTitle,folderTitle)
  }
  
  
 });
 query.fail(function (){
  console.log("папка '" + folderTitle + "' не найдена");  
 }); 
 
}
function createFolder(listTitle,folderName)
{ 
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/Items/";
 console.log(uri);
 var getlistType = getListEntityTypeName(listTitle);
 getlistType.done(function (data, status, xhr) {
  console.log(data.d.EntityTypeName)
  var listType = data.d.EntityTypeName
 
  var body = "{ '__metadata': { 'type': 'SP.Data." + listType + "Item' },  'Title': '" + folderName + "', 'FileLeafRef': '" + folderName + "', 'ContentTypeId': '0x0120001EE026E991F8644A85A5F2888FADB06D'}"
  var newItem = addListItem(listTitle, body);
  newItem.done (function (data, status, xhr) {
   console.log(data)
   var newItemId = data.d.Id;
   var body = "{ '__metadata': { 'type': 'SP.Data." + listType + "Item' },  'FileLeafRef': '" + folderName + "' }";
   updateListItem(listTitle, newItemId, body);
   
   
  });
  
 });  
} 


function addListItem(listTitle,body)
{
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/Items/"; 
 var query = jQuery.ajax({
     url: uri,
     type: "POST",
     data: body,
     contentLength: body.length,
     headers: {  "accept": "application/json; odata=verbose", 
        "content-type" : "application/json; odata=verbose",
        "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val() }
    }); 
 query.done(function (data){ console.log('элемент создан'); console.log(data)
 
  });  
 query.fail(function (error){console.log('Ошибка при создании элемента'); console.log(error)}); 
 return query;
}
function moveListItem( listTitle, folderTitle, itemId)
{
 var getlistType = getListEntityTypeName(listTitle);
 getlistType.done(function (data, status, xhr) {
  console.log(data.d.EntityTypeName)  
  var listType = data.d.EntityTypeName;
  var listInternalName = listType.substring(0, listType.length - 4 )
  console.log(listInternalName)
    var webUrl = _spPageContextInfo.webAbsoluteUrl;
  var uri = webUrl+ "/_api/web/getfilebyserverrelativeurl('/Lists/"+ listInternalName + "/" + itemId+ "_.000')/moveto(newurl='/Lists/"+ listInternalName + "/" + folderTitle + "/" + itemId+ "_.000',flags=1)";
  var query = jQuery.ajax({
   url: uri,
   type: "POST",
   headers: {  "accept": "application/json; odata=verbose", 
      "content-type" : "application/json; odata=verbose",
   "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val()} 
   });
   query.done(function (data){ console.log('элемент создан'); console.log(data) });  
   query.fail(function (error){console.log('Ошибка при создании элемента'); console.log(error)}); 
  

 });
}


function updateListItem(listTitle, itemId, body)
{
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/Items("+ itemId +")"; 
 var query = jQuery.ajax({
     url: uri,
     type: "POST",
     data: body,
     contentLength: body.length,
     headers: {  "accept": "application/json; odata=verbose", 
        "content-type" : "application/json; odata=verbose",
        "X-HTTP-Method":"MERGE",
        "IF-MATCH": "*",
        "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val() }
    }); 
 query.done(function (data){ console.log('элемент обновлен'); console.log(data)
 
  });  
 query.fail(function (error){console.log('Ошибка при обновлении элемента'); console.log(error)}); 
 return query;
}
function getListEntityTypeName(listTitle)
{
 var webUrl = _spPageContextInfo.webAbsoluteUrl;
 var uri = webUrl + "/_api/web/lists/GetByTitle('"+listTitle+"')/EntityTypeName";
 var queryAjax = jQuery.ajax({
      url: uri,
      type: "GET",
      headers: {  "accept": "application/json; odata=verbose", 
         "content-type" : "application/json; odata=verbose",
         "X-RequestDigest" : jQuery("#__REQUESTDIGEST").val() }
     }); 
 queryAjax.done(function (data){ console.log('EntityTypeName полученны'); console.log(data)});  
 queryAjax.fail(function (){console.log('Ошибка при вылнении запроса EntityTypeName')}); 
 return queryAjax; 
}