Sunday 25 June 2017

Редактор имен файлов (MFC-FileEditor)

Очень часто возникает необходимость массово редактировать файлы. Заменить в именах файлов одни символы на другие.
 При этом, желательно, чтобы замена выполнялась по выбору:
  • по всему имени
  • только в начале имени
  • только в конце имени 
Total Commander прекрасно выполняет эту работу, но он не может заменить только первый или только последний символ в строке. Мои поиски в интернете соответствующей программы привели к Bulk Rename Utility. Однако по ряду причин она меня не устроила, поэтому и было принято решение написать самостоятельно собственный редактор имен файлов. 

Исходники программы опубликованы здесь

Редактор имен файлов (MFC-FileEditor) и выполняет  вышеуказанную и другие задачи.

Программа работает с двумя списками для хранения имен файлов и содержит панель управления. 


По команде загружает список имен и выбранного каталога.

Список хранит имена файлов выбранного каталога, кроме тех, которые предусмотрены панелью "Исключения"

Кроме этого, список имен файлов можно ограничить с помощью панели "Строки для удаления"

Способ редактирования имен файлов регулируется настройкой панели "Опции"

После проверки заданных параметров команду редактора можно выполнить.
Команда "Выполнить" создаст протокол действий и запишет его в .log файл, который можно обнаружить в корне папки инсталляции программы. 

Sunday 16 April 2017

Использование в проектах SharePoint библиотеки jqGrid

В SharePoint 2013(2016) для отображения элементов списка используется Client Side Rendering (CSR). Работа на стороне клиента в первую очередь гарантирована методами clienttemplates.js. Для нормальной работы сайтов на SharePoint этого вполне может быть достаточно, но очень часто возникают нестандартные ситуации, решение которых потребует подключить к проекту библиотеки сторонних разработчиков.
Сегодня я хотел бы показать небольшой пример работы с библиотекой  jqGrid. Это JavaScript-элемент с поддержкой Ajax, который предоставляет решения для показа и обработки табличных данных в Интернете.
Предположим, у нас имеется очень простой список (SPList) PMBirthdayBoys (ПМ-Именинники). В него, кроме обязательных столбцов, добавлен один  столбец - BirthdayBoy, тип SPUser. Данные этого списка нужно показать в отдельном окне JQuery Dialog
Следующий ниже JavaScript-метод легко решает эту задачу:

function GetDataPM(ListTitle) {
    var url = _spPageContextInfo.siteAbsoluteUrl +
                        "/_api/web/lists/getByTitle('" + ListTitle + "')/items?" +
"$select=Id,BirthdayBoy/Title,BirthdayBoy/Department,"+
BirthdayBoy/EMail,BirthdayBoy/JobTitle,"+
BirthdayBoy/Name&" +
                        "$expand=BirthdayBoy/Id,BirthdayBoy/Picture&" +
                        "$orderby=Id desc";
    var wWidth = $(window).width(), wHeight = $(window).height(), 
ContainetID = "InformersID", TableID = "Table_" + ContainetID;
    $('body').append($('<div />').attr('id', ContainetID).css('background-color''white')
.addClass('my-dialog')
                .append($('<div />').addClass('main-forms-dialog')));
    //-----------------------------
    SetDocData_pm(url, function (data) {
        var data_res = data.results;
        if (data_res.length > 0) {
            var grid = $("#" + TableID), dialog = $('#' + ContainetID);
            dialog.dialog({
                buttons: [{
                    text: "Закрыть", click: function () {
                        $(this).dialog('close');
                    }
                }
                ],
                height: wHeight,
                width: wWidth,
                closeText: "",
                modal: true,
                title: "Форма уведомлений",
                show: "slide",
                hide: "slide",
                resizable: true,
                create: function (event, ui) { },
                open: function () {                    
                    $('#' + ContainetID).remove('#' + TableID).children('div')
                                .append($('<div />').attr('id''jqGridPager')
                    .addClass('main-forms-col-dialog padding-10')).append($('<table />')
.attr('id', TableID));
                    var grid = $("#" + TableID);
                    //----------------------
                    grid.jqGrid({
                        regional: "ru",
                        data: data_res,
                        datatype: "local",
                        rowList: [],
                        pgbuttons: false,
                        pgtext: null,
                        viewrecords: true,
                        pager: "#jqGridPager",
                        colMenu: true,
                        height: wHeight * 0.7,
                        width: wWidth * 0.9,
                        rownumbers: true,
                        sortname: "invdate",
                        sortorder: "desc",
                        colModel: [
                         { name: "Id", width: 30, formatter: "integer", hidden: true
                         colmenu: false, key: true, index: "Id" },
                         { name: "BirthdayBoy.EMail", label: "EMail", colmenu: false },
                         { name: "BirthdayBoy.Title", label: "Именинник", colmenu: false },
                         { name: "BirthdayBoy.JobTitle"
                         label: "Должность", colmenu: false },
                         { name: "BirthdayBoy.Department"
                         label: "Место работы", colmenu: false },
                         { name: "BirthdayBoy.Name", label: "Логин", colmenu: false }
                        ]
                    })
                        .jqGrid("navGrid""#jqGridPager", { edit: false, add: false
                        del: false, search: true, refresh: false, view: false
                        position: "left", cloneToTop: true })
                        .jqGrid("gridResize")
                        .jqGrid("navButtonAdd""#jqGridPager", {
                            buttonicon: "ui-icon-mail-open",
                            title: "Поздравить именинника",
                            caption: "Поздравить",
                            position: "next",
                            onClickButton: function () {
                                var rowid = grid.jqGrid("getGridParam""selrow");
                                if (rowid) {
                                    var getCell_Id = grid.jqGrid('getCell', rowid, 'Id');
                                    //--что то делаем здесь
                                }
                                else {
           alert("Строка не выделена. Необходимо установить курсор мыши на строку таблицы.");
                                }
                            }
                        });
                },
                close: function () { $('body').find('#' + ContainetID).remove(); }
            });
        }
        else {
            alert("Список «Именинник» пустой...");
        } 
    }, true); 
Метод GetDataPM принимает имя списка. Далее выполняет запрос об указанном в списке пользователе, выясняет его электронный адрес, имя, логин и место работы (все эти данные хранятся в базе профилей пользователей SP). Инициализирует jqGrid таблицу, загружает туда полученные с сервера данные и сохраняет все это в форме JQuery Dialog
Вот и все....😄
В моем случае все прекрасно работает. Публикую эту информацию в надежде, что она может быть кому нибудь пригодится. А может быть, кто нибудь укажет мне на явные ошибки, что очень приветствуется😂.





Saturday 15 April 2017

Решение для фермы SharePoint. Учет сотрудников

Недавно меня попросили создать решение для демонстрации технологий SharePoint:
  • CAML
  • TimerJob
  • WebPart
  • ControlTemplates
  • WCF 
Решение должно: 
  1. Разворачивать шаблон списка SharePoint для хранения информацию о сотрудниках и их днях рождения;
  2. Создавать экземпляр этого списка с 5 тестовыми записями; 
  3. Содержать страницу с настройками решения;
  4. Реализовать механизм рассылки уведомлений о приближающихся днях рождения менеджеру персонала на указанный в настройках e-mail за указанное в настройках количество дней;
  5. Содержать webpart, который отображает ближайшие дни рождения из этого списка (у webpart должны быть настройки за сколько дней до ДР уведомление должно появляться на странице);
  6. Быть разработано для сервера SharePoint 2013 и предназначено для учета сотрудников; предприятия. Акцент в решении сделан на дни рождения сотрудников.
Я подготовил в соответствии с задачей необходимый проект.

Состав проекта:

1. Шаблоны списков:
  • MPListEmployees. Для хранения информации о сотрудниках 
  • MPEmployeesResursec. Для хранения служебной информации
  • PMBirthdayBoys. Для краткосрочного хранения информации о днях рождения 
2. Две веб части:
  • VPMEmployees.webpart. Для просмотра сведений об именинниках
  • VPMAdmin.webpart. Для установки списков решения на сайтах фермы SharePoint


Описание проекта:
Количество списков в решение и их поля регламентированы необходимостью полностью выполнить поставленные задачи с наименьшими затратами и с наибольшей точностью.
Некоторые поля списков не используются, так как сделаны с прицелом на будущее развитие.
В момент инсталляции решения:
 1. Его ресурсы добавляются в папки фермы:
  • CONTROLTEMPLATES
  • IMAGES
  • FEATURES
  • LAYOUTS
2. В список MPListEmployees загружаются сведения о 5-и учетных записях.
3. Администратору фермы направляются советующие уведомления. 
4. В решении использованы технологии:
  • CAML
  • TimerJob
  • WebPart
  • ControlTemplates
  • WCF 
Моя реализация поставленной задачи потребовала создать в проекте два решения:
  1. Первое - PMEmployeesTimerJob выполняет задания таймера.
  2. PMEmployees - делает все остальное, в т.ч. содержит ресурсы.  
Проект - исходные файлы

Метод SetUrlKeyValue и его JQuery аналог

Недавно, работая над очередным решением для фермы SharePoint 2013, я использовал метод JavaScript - SetUrlKeyValue для редактирования параметра в строке запроса. 

SetUrlKeyValue (KEYNAME, KeyValue, bEncode, URL)

Устанавливает значение ключа в URL. Третий параметр указывает - необходимо ли значение кодировать.

Определение метода я нашел в файле INPLVIEW.debug.js

function SetUrlKeyValue(keyName, keyValue, bEncode, url) {
    if (url == null)
        url = window.location.href + "";
    var val = keyValue;
    var uri = new URI(url, {
        disableEncodingDecodingForLegacyCode: true
    });
 
    url = uri.getQuery();
    if (bEncode)
        val = escapeProperly(val);
    if (url.indexOf(keyName + "=") < 0) {
        if (url.length > 1)
            url += "&";
        else if (url.length == 0)
            url += "?";
        url += keyName + "=" + val;
    }
    else {
        var re = new RegExp(keyName + "=[^&]*");
 
        url = url.replace(re, keyName + "=" + val);
    }
    uri.setQuery(url);
    return uri.getString();
}

И все было прекрасно, метод великолепно выполнял свое назначение, и я был доволен. 
Тем не менее, изначально, я не учел очень важную особенность работы метода SetUrlKeyValue - он выполняется в среде, запущенной от имени пользователей, обладающих, как минимум, правами для совместной работы с текущим контентом.
Меня это не устраивало, так как метод должен выполняться и для пользователей с правами только для чтения текущей страницы. И таких пользователей, как правило, должно быть большинство. Не давать же в конце концов им всем права на совместное пользование сайтом SharePoint☺.

Вот фрагмент моей функции, который изначально мною использовался:
$("a[title='zamena']").each(function () {
        var url = $(this).attr("href");
        var _BZnaniyId_ = url.indexOf('BZnaniyId=');
        if (_BZnaniyId_ > 0) {
            var BZnaniyId = parseInt(GetUrlKeyValue("BZnaniyId"));
            if (BZnaniyId > 1) {
                try{
                    var url1 = SP.Utilities.UrlBuilder.removeQueryString(url, "BZnaniyId");
                    if (typeof url1 != "undefined") {
                        var url2 = SetUrlKeyValue("BZnaniyId", BZnaniyId, false, url1);
                        if (typeof url2 != "undefined") {
                            $(this).attr("href", url2);
                        }
                    }
                }
                catch (e)
                {
                    console.log('Ошибка: ' + e);
                }
            }
        } 
    });
Присваивание значения переменной  url2 вызывало исключение. После некоторых экспериментов я решил поменять способ инициализации переменной. И это стало выглядеть следующим образом:
$("a[title='zamena']").each(function () {
        var url = $(this).attr("href");
        var _BZnaniyId_ = url.indexOf('BZnaniyId=');
        if (_BZnaniyId_ > 0) {
            var BZnaniyId = parseInt(GetUrlKeyValue("BZnaniyId"));
            if (BZnaniyId > 1) {
                try{
                    var url1 = SP.Utilities.UrlBuilder.removeQueryString(url, "BZnaniyId");
                    if (typeof url1 != "undefined") {
                        var url2 = updateQueryStringParameter("BZnaniyId", BZnaniyId, url1);
                        if (typeof url2 != "undefined") {
                            $(this).attr("href", url2);
                        }
                    }
                }
                catch (e)
                {
                    console.log('Ошибка: ' + e);
                }
            }
        }
    });

Для инициализации переменной url2 я использовал другой метод чтения и редактирования строки запроса.

Реализация этого метода выглядит следующим образом:
function updateQueryStringParameter(key, value, uri) {
    var re = new RegExp("([?&])" + key + "=.*?(&|$)""i");
    var separator = uri.indexOf('?') !== -1 ? "&" : "?";
    if (uri.match(re)) {
        return uri.replace(re, '$1' + key + "=" + value + '$2');
    }
    else {
        return uri + separator + key + "=" + value;
    }
}

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