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

пятница, 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);
    }
    

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

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

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

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

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

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

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