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

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