понедельник, 21 ноября 2016 г.

regex word wrapping в духе notepad

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

Т.е. нам надо симулировать поведение функции Word Wrap из приложения Notepad в Windows.

Есть два варианта решения этой задачи:
  1. Написать алгоритм
  2. Прикрутить регулярные выражения. И если учесть, что такая задача может возникнуть где угодно (т.е. как на клиенте, так и на сервере), а регулярные выражения поддерживаются практически всеми языками программирования, то этот вариант кажется наиболее предпочтительным.

Вот просто пример на C#, когда нам нужно разбить строку с длинным почтовым адресом longAddressString и представить результат в виде коллекции строк, чья длинна не превышает 40 символов.

List<string> adresses = (from Match m in Regex.Matches(longAddressString, @"(?:((?>.{1,40}(?:(?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|.{1,40})(?:\r?\n)?|(?:\r?\n|$))") where !String.IsNullOrWhiteSpace(m.Value) select m.Value ).ToList();

А ниже объяснение работы данного регулярного выражения от автора.

# MS-Windows  "Notepad.exe Word Wrap" simulation
 # ( N = 16 )
 # ============================
 # Find:     @"(?:((?>.{1,16}(?:(?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|.{1,16})(?:\r?\n)?|(?:\r?\n|$))"
 # Replace:  @"$1\r\n"
 # Flags:    Global     

 # Note - Through trial and error discovery, it apparears Notepad accepts an extra whitespace
 # (possibly in the N+1 position) to help alignment. This matters not because thier viewport hides it.
 # There is no trimming of any whitespace, so the wrapped buffer could be reconstituted by inserting/detecting a
 # wrap point code which is different than a linebreak.
 # This regex works on un-wrapped source, but could probably be adjusted to produce/work on wrapped buffer text.
 # To reconstitute the source all that is needed is to remove the wrap code which is probably just an extra "\r".

 (?:
      # -- Words/Characters 
      (                       # (1 start)
           (?>                     # Atomic Group - Match words with valid breaks
                .{1,16}                 #  1-N characters
                                        #  Followed by one of 4 prioritized, non-linebreak whitespace
                (?:                     #  break types:
                     (?<= [^\S\r\n] )        # 1. - Behind a non-linebreak whitespace
                     [^\S\r\n]?              #      ( optionally accept an extra non-linebreak whitespace )
                  |  (?= \r? \n )            # 2. - Ahead a linebreak
                  |  $                       # 3. - EOS
                  |  [^\S\r\n]               # 4. - Accept an extra non-linebreak whitespace
                )
           )                       # End atomic group
        |  
           .{1,16}                 # No valid word breaks, just break on the N'th character
      )                       # (1 end)
      (?: \r? \n )?           # Optional linebreak after Words/Characters
   |  
      # -- Or, Linebreak
      (?: \r? \n | $ )        # Stand alone linebreak or at EOS
 )

среда, 16 ноября 2016 г.

Изменил логин (ФИО и email) учётной записи в AD, а в SharePoint Foundation отображаются старый данные пользователя

У нас в компании сложилась интересная ситуация. Время от времени у руководителя меняться ассистент и секретарь. Следовательно "новому" сотруднику необходимо передать календарь Exchenge и всё всё всё, с чем работал "старый" сотрудник.. Ведь учётная запись такого важного человека как ассистент или секретарь светится в разных системах предприятия.

Администраторы вышли из этого положения очень просто. Они полностью переименовывают учётную запись "старого" секретаря. Т.е. заменяют в учётной записи "старого" секретаря все поля:

  • Логин (я кстати не знал, что его запросто можно поменять в AD)
  • ФИО
  • почтовый ящик
  • и т.д.

И вроде бы все системы подсасывают из AD новые данные из учётки.. все.. кроме SharePoint Foundation.

SharePoint Foundation не синхронизирует данные пользователя с AD (Active Directory)!

Вот в очередной раз "старый" секретарь уступил своё место "новому". "Новый" секретарь заходит на портал и авторизуется вводя свой логин и пароль ( хотя за частую авторизация проходит прозрачно и ни какого логина и пароля портал вообще не запрашивается). Однако на первой же странице портала в верхнем правом углу он видит Имя "старого" секретаря. У сотрудника складывается впечатление, что он каким то образом зашёл на портал под другим пользователем. Попытка перелогиниться результатов не даёт. Сотрудник по прежнему видит имя "старого" секретаря.

При этом админы AD разводят руки, у них то всё правильно.

Что же происходит?

Когда пользователь заходит на портал, то для него создаётся запись в скрытом списке:
 User Information List (/_catalogs/users/simple.aspx)
Эти записи, скажем так, выполнят роль профиля пользователя в SharePoint Foundation. Данные в этот список подтягиваются из AD из учётной записи пользователя.

Однако данные в User Information List сами по себе не обновляются! (По крайней мере в SharePoint Foundation. Возможно в SharePoint Server, где есть синхронизация с AD, всё иначе и данный с AD синхронизируются корректно)

Таким образом, если пользователя завели в AD и дали доступ на портал, то для него будет создана запись в User Information List. Но если после этого админы зашли в Active Directory и поменяли у данной учётно записи все параметры (включая логи пользователя, ФИО и адрес почты), то в список User Information List эти новые данные автоматически не подтянутся и следовательно данные в User Information List останутся старыми и будут отличаться от данных в AD.

Например, если первоначально секретарём был Иванов Иван Иванович с логином i.ivanov, а потом администраторы переименовали (именно изменили существующую, а не создали ) его учётную запись в AD в Петров Пётр Петрович с логином p.petrov, то такой пользователь будет нормально логиниться на портал под логином p.petrov, НО будет видеть вместо своего имени (Петров Пётр Петрович), имя старого секретаря (Иванов Иван Иванович), потому что имя и прочие данные будут подтягиваться из User Information List, куда они были записаны ещё в те давние времена, когда учётная запись секретаря именовалась Иванов Иван Иванович.

Самый простой способ исправить эту ситуацию, это найти и удалить пользователя с портала и удалить запись в списке User Information List, а затем вновь добавить пользователя на портал.

Чтобы удалить пользователя и запись в User Information List, для начала её необходимо найти. Для этого нужно открыть список User Information List, который находится по адресу /_catalogs/users/simple.aspx и ручками прошерстить его в поисках записи для соответсвующей учётной записи.

В нашем случае нам необходимо найти учётную запись с именем "старого" секретаря. Так как именно она содержит устаревшие данные.

 Однако если вы знаете ID пользователя, то можно открыть интересующую нас запись, воспользовавшись следующим URL-адресом:
 /_layouts/15/userdisp.aspx?ID={ID пользователя}
или
/_layouts/listform.aspx?PageType=4&ListId={{GUID списка - User Information List}}&ID={ID пользователя}


Например:
http://emg-sed/_layouts/15/userdisp.aspx?ID=150
или
http://emg-sed/_layouts/listform.aspx?PageType=4&ListId={246E4250-6364-4241-10C3-34BF2E063802}&ID=150

А далее достаточно нажать кнопку "Удалить пользователя из семейства веб-сайтов":

После этого я, как правило, прошу пользователя вновь зайти на портал. При этом SharePoint разумеется не пустит пользователя и покажет ему страницу с которой он может написать обращение к администратору SharePoint - "Зачем вам нужен доступ на портал".
Получив такой запрос я вновь выдаю пользователю все необходимые права и SharePoint вновь создаёт (хотя правильнее сказать, восстанавливает) запись в User Information List, но уже с правильными, новыми, актуальными данными, которые он подтягивает из AD.

Теперь наш "новый" секретарь, заходя на портал видит своё имя и запись в User Information List содержит актуальные данные из учётной записи AD.

PS: Что интересно, запись в списке User Information List создаётся с тем же самым ID, с каким она была, до удаления. По этому можно говорить о том, что запись не создаётся а восстанавливается.

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

Соответственно с моей стороны разумно было бы ожидать, что удалив и вновь добавив пользователя на портал, я получу пользователя с новым ID. Однако это не так.
На практике, добавляя на портал пользователя (а точнее говоря учётную запись из AD), который однажды уже имел доступ к этому порталу, мы увидим, что SharePoint восстановит для него запись в User Information List с тем же самым ID, правда при этом обновит её значения новыми данными из AD. Чего мы и добивались.

вторник, 16 сентября 2014 г.

Боковое меню с эффектом аккордеона для SharePoint 2013

Иногда боковое меню сайта SharePoint принимает просто угрожающие размеры. При этом пользователи разбивают пункты меню на подгруппы. Что правильно и совершенно логично.

Это делает меню более понятным визуально, но его юзабилити, а главное размер, по прежнему оставляют желать лучшего. Так как по умолчанию в SharePoint не поддерживается "эффект аккордеона", когда подпункты меню схлопываются в виде гармошки.

Здесь описан пример создания "эффекта аккордеона" на одном CSS, без применения JavaScript.


Это решение работает. Оно очень простое и эффективное. Однако стоит признать, что в целом поведение такой "гармошки", не такое классное, как хотелось бы.

По сути, всё решение сводится к добавлению в master page ссылки на css файл со следующим содержимым:

.ms-core-listMenu-verticalBox li.static {
    height: 2em;
    overflow: hidden;
}
.ms-core-listMenu-verticalBox li.static:hover {
    height: auto;
}
.ms-core-listMenu-verticalBox li > span.menu-item {
    cursor: pointer;
}
/* Format the headers */
.ms-core-listMenu-verticalBox li > span.menu-item {
    cursor: pointer;
    background: #0171C6;
    color: white;
    border: solid #fff;
    border-width: 1px 0;
}
/* Format the headers */
.ms-core-listMenu-verticalBox li > span.menu-item {
    cursor: pointer;
    background: #0171C6;
    color: white;
    border: solid #fff;
    border-width: 1px 0;
}
/* Format the header hover, list item hover and currently selected item */
.ms-core-listMenu-verticalBox li > span.menu-item:hover, /*Header */
.ms-core-listMenu-verticalBox a.selected, /* Selected */
.ms-core-listMenu-verticalBox a.menu-item:hover /* List item */{
    color:#FFF;
    background:#073D7D;
}




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

Определение Edit Mode страницы с помощью JavaScript

В SharePoint 2013 есть такая штука как Publishing Pages. С точки зрения рядового пользователя, это веб-страницы, которые он может создавать прямо в браузере, в стиле WYSIWYG. Достаточно создать новую страницу публикации (Publishing Page), затем открыть её в режиме редактирования и можно сразу набивать контент, при этом особо не запариваясь относительно стилей и получаемой разметки. Вещь удобная и пользуются ей, как говорится, направо и налево.

Впрочем увлёкшись WISIWIG вы всё ещё можете остановиться и взглянуть на полученную HTML-разметку. И даже отредактировать её, дабы наполнить не передаваемым шармом в духе HTML5. Для чего на панели инструментов предусмотрена кнопка "Изменить источник".



А что если мы решим зайти дальше в нашем стремлении сделать контент страницы более интерактивным и насыщенным, и добавим на страницу публикации веб-часть "Редактор сценариев". А в этот редактор сценариев поместим JavaScript, который, используя jQuery и добавленную нами на страницу HTML-разметку, создаёт интерактивный и кувыркающийся Коллаж из фотографий и лозунгов, рождённый воспалённой фантазией вашего дизайнера и PR-менеджера, слившихся в творческом экстазе.

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

Вот только когда вас попросят добавить в коллаж новый лозунг, и вы вновь откроете страницу в режиме редактирования, то увидите что вся ваша HTML-разметка "пляшет" и "кувыркается" по воле JavaScript'a из веб-части "Редактор сценариев". А стоит вам сохранить страницу, как вся эта чехорда "впечатается" в контент страницы на мертво и JavaScript уже будет не в состоянии её оживить.

Дело в том, что когда вы открыли страницу на редактирования, то JavaScript в веб-части
"Редактор сценариев" начнёт выполняться, как ни в чём не бывало. При этом он будет, искать в содержимом страницы определённую HTML-структур (например, див внутри которого находится список, каждый элемент которого является сложным описанием одной из страниц слайда). Найдя её, он оживит её самым чудесным образом, динамически внося изменения в этот кусок HTML кода. Однако SharePoint понятия не имеет кто вносит изменения в разметку, Вы или ваш JavaScript. По этому когда вы сохраните страницу, он с чистой совестью сохранит всю необходимую HTML разметку, включая исковерканную вашим же JavaScript'ом. После чего она будет разительно отличаться от той, что нужна JavaScript'у для оживления коллажа.

Выход есть! Необходимо, чтобы перед тем как пытаться оживлять коллаж, JavaScript проверил в каком режиме находится страница публикации. И если страницы открыта в режиме редактирования контента, то JavaScript прекращает работу ни как не влияя на разметку.

<script type="text/javascript">
        (function ($) {

            $(document).ready(function () {
                // Проверям режим страницы
                var InEditMode = false;
                try{
                InEditMode = SP.Ribbon.PageState.Handlers.isInEditMode();
                } catch(e){}
               
                if (InEditMode) {return;}
                // Каруселить список клиентов
                // btnNext и btnPrev - указывают на кнопки вперёд/назад
                // visible - скорлько элементов карусели будет показано единовременно
                // circular - зациклить карусель по кругу
                $(".clients-list").jCarouselLite({
                    btnNext: "#gon",
                    btnPrev: "#pon",
                    visible: 5,
                    circular: true
                });

            });

        })(jQuery);

    </script>

четверг, 3 июля 2014 г.

Многострочные строки в JavaScript или аналог @ в C#.

В языке C# есть так называемые буквальные строковые литералы.

Пример буквальных строковых литералов:
// Буквальные строковые литералы воспринимают всё написанное буквально, кроме кавычек. Что бы задать кавычки необходимо воспользоваться специальной управляющей последовательностью - двойные кавычки, т.е. вот так: ""
string s = @"C:\Windows";

s = @"Роман ""Война и мир"""// Роман "Война и Мир"

Кроме того, буквальный строковый литерал позволяет отказаться от использования управляющей последовательности \n новая строка.
Т.е. вместо того чтобы писать так:

string s = "Переход на новую строку.. \n.. новая строка";

Можно записать так:

string s  = @"Переход на новую строку..
.. новая строка";

А результат будет одним и тем же.

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

var multiLine = '\
Первая строка \
Вторая строка \
Третья строка \
';
alert(multiLine);


понедельник, 30 июня 2014 г.

Как добавить в форму создания нового элемента списка пункт "Вложить файл"

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

1) Открываем на редактирование страницу с формой создания нового элемента списка.
2) Добавляем новую веб-часть "Редактор контента". Самый простой способ сделать это, просто выбрать Insert -> Text:
3) Затем вставляем в веб-часть следующую разметку:

<div>
  <a title="Attach File" class="ms-toolbar" accesskey="I" onclick="javascript:UploadAttachment();" href="javascript:UploadAttachment()">
     <img width="16" height="16" align="absMiddle" alt="Attach File" src="/_layouts/images/attachtb.gif" style="border-width: 0px;"/>
  </a>
  <a id="ctl00_m_g_7df5d998_1390_4e67_8fc4_2da066cbcf7d_ctl00_ctl01_ctl00_toolBarTbl_RptControls_diidIOAttach{generate-id()}" href="javascript:UploadAttachment()" style="visibility: hidden;">
  </a>
  <a class="ms-toolbar" id="ctl00_m_g_7df5d998_1390_4e67_8fc4_2da066cbcf7d_ctl00_ctl01_ctl00_toolBarTbl_RptControls_diidIOAttach_LinkText" accesskey="I" onclick="javascript:UploadAttachment();" href="javascript:UploadAttachment()">
    Attach File
  </a>

</div>

Делается это через пункт меню "Изменить источник"...

... вот в таком диалоговом окне.


И в результате получаем:


4) И в итоге получаем следующий результат

вторник, 11 февраля 2014 г.

Как в тело письма, которое генерируется в SingleTask activity, добавить ссылку на задачу?

Долго искал как делается этот трюк, пока знающие люди не подсказали. :-)

Суть вопроса такова. Необходимо c помощью Visual Studio создать в SharePoint 2013 новый рабочий процесс (Workflow 4). В рамках этого рабочего процесса мы используем Single Task activity, чтобы создать новую задачу и назначить её на некоего пользователя. Single Task activity позволяет нам не только назначить задачу на сотрудника, но и тут же отправить ему на почту оповещение о том, что ему назначена новая задача. При этом мы можем отредактировать как тело письма, так и его тему.



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

Как оказалось, решить данную проблемы чрезвычайно просто. Достаточно добавить в тело письма вот такую вот ссылку:

 <a href='%TaskSpecial: TaskUrl%'>%Task: Title%</a>  

Вот как это будет выглядеть в итоге:

А вот так будет выглядеть письмо, пришедшее пользователю:

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

NameDescription
Public field Static memberAssignedToReplace the %Task: AssignedTo% token with the individual or group to whom the task is currently assigned.
Public field Static memberDescriptionReplace the %Task: Body% token with the description of the task.
Public field Static memberDueDateReplace the %Task: DueDate% token with the date the task is due.
Public field Static memberRegularFormatThe regular format for the token replacement.
Public field Static memberRelatedItemTitleReplace the %TaskSpecial: RelatedItemTitle% token with the title of the task’s related item.
Public field Static memberRelatedItemUrlReplace the %TaskSpecial: RelatedItemUrl% token with a link to the task’s related item.
Public field Static memberSpecialFormatThe special format for the token replacement.
Public field Static memberTaskUrlReplace the %TaskSpecial: TaskUrl% token with a link to the task.
Public field Static memberTitleReplace the %Task: Title% token with the title of the task.