(Dateiname bei FINDOLOGIC: structured_autocomplete.js)

Bei der JavaScript-Datei findologic_suggest.js handelt es sich um das Suggest-Script von FINDOLOGIC, das in Abhängigkeit zu den beiden jQuery-Komponenten jquery.preload.js und jquery.autocomplete.js steht. Es muss immer sichergestellt sein, dass deren Code vor findologic_suggest.js geladen wurde.

Wegweiser: Integration der FINDOLOGIC Suggest-Funktion

// ======================================
var FlAutocomplete = FlAutocomplete || {};

// ---------------------------------------------------------

FlAutocomplete.AbstractAutocomplete = function(options) {

	// Define class names
	this.formIdPrefix = "flAcArticle_";
	this.formClass = 'article_order_form';
	this.shoppingCartWrapper = 'shopping_cart_wrapper';
    this.basketButtonClass = 'button_add_basket_ajax';
    this.increaseAmountButtonClass = 'button_increase_amount';
    this.decreaseAmountButtonClass = 'button_decrease_amount';
    this.amountWrapper = "amount_wrapper";
    this.inputAmountFieldClass = 'amount_field';
    this.cartButtonWrapper = 'cart_button_wrapper';

    /* Maximum articles which can be added into cart */
    this.itemLimit = 999;
};

FlAutocomplete.AbstractAutocomplete.prototype = {

	/**
	* Returns the html for the cart button column as jquery object
	* @param id The article id
	* @return jQuery-Object
	*/
    getCartHtml : function(id) { return; },

    /**
	* Adds item to cart
	* @param id The article id
	*/
    addToCart : function(id) { return; },

    /**
	* Adds onClick events to shopping cart button and in/decrease buttons
	* @param id The article id
	*/
    registerEvents : function(id) {	return; },

    /**
    * Changes the value of the article-number field in a row, depending on operation
    * @param integer id The article id
    * @param integer operation Wheter '1' increase or '-1' decrease
    */
    changeNumberOfArticles : function(id, operation, callback) {

    	var currentForm = this.getFormWrapperById(id),
    		amountField = currentForm.find('.' + this.inputAmountFieldClass),
    		currentValue = parseInt(amountField.val(), 10),
    		newValue = 0;

    	if (!currentValue || isNaN(currentValue)) {
    		currentValue = 0;
    	}

    	newValue = currentValue + operation; // Either +1 or -1

    	if (currentValue >= this.itemLimit && operation > 0) {
    		newValue = currentValue
    	}

    	// Can not be negative
    	newValue = newValue < 1 ? 1 : newValue;

    	// Set new value
    	amountField.val(newValue);

    	if (typeof callback === 'function') {
    		callback(newValue);
    	}
    },

    /**
	* Gets the form element by id
	* @param id The article id
	* @return jQuery-Object
	*/
    getFormWrapperById : function(id) {
    	return jQuery('#' + this.formIdPrefix + id);
    }

};

// ---------------------------------------------------------

FlAutocomplete.Plentymarkets = function(options) {
	FlAutocomplete.AbstractAutocomplete.apply(this, arguments);
    jQuery.extend(this, options);    
};
FlAutocomplete.Plentymarkets.prototype = new FlAutocomplete.AbstractAutocomplete();

FlAutocomplete.Plentymarkets.prototype.getCartHtml = function(id, priceId) {
	
	// Wrap it twice, because we use .html(), which only returns the inner html
	var cart = '<div>';

	if (id) {

	    cart += '<form id="' + this.formIdPrefix + id + '" data-id="' + id + '" class="' + this.formClass + '">';

			cart += '<input type="hidden" name="ActionCall" value="WebActionBasketAddArticle">';
		    cart += '<input type="hidden" name="ArticleID" value="' + id + '">';		    
		    cart += '<input type="hidden" name="ArticlePrice" value="">';
		    cart += '<input type="hidden" name="ArticleQuantity__AutoField" value="1">';
		    cart += '<input type="hidden" name="SYS_P_ID" value="' + priceId + '">';

		    cart += '<div class="' + this.shoppingCartWrapper + '">';

			    cart += '<div class="' + this.amountWrapper + '">';
			    cart += '<input type="text" name="ArticleQuantity" class="amount_field" value="1" size="3" maxlength="3">';   
			    cart += '<button type="button" value="1" class="' + this.increaseAmountButtonClass + '">+</button>';
			    cart += '<button type="button" value="-1" class="' + this.decreaseAmountButtonClass + '">-</button>';
			    cart += '</div>';

			    cart += '<div class="' + this.cartButtonWrapper + '">';
			    cart += '<span class="button"><button type="button" class="' + this.basketButtonClass + '" data-id="' + id + '">In den Warenkorb</button></span>';
			    cart += '</div>';

		    cart += '</div>';

	    cart += '</form>';
	}

	cart += '</div>';

    return jQuery(cart);
};

// Unbind all binded events
FlAutocomplete.Plentymarkets.prototype.unbindEvents = function(list) {
	list.off('click', '.' + this.basketButtonClass);
	list.off('click', '.' + this.amountWrapper);
	list.off('click', '.' + this.increaseAmountButtonClass + ', .' + this.decreaseAmountButtonClass);
};

FlAutocomplete.Plentymarkets.prototype.bindEvents = function(list) {

	// Because every autocomplete requests calls this method, it binds a new event on the elements
	// remove first all binded events
	this.unbindEvents(list);
    
    var self = this;

    // AddToCart buttons
    list.on('click', '.' + this.basketButtonClass, function(e) {
        var id = jQuery(this).data('id');
        self.addToCart(id);
        e.stopPropagation();
        e.preventDefault();
    });

    // Do not trigger click (on the parent row element), when clicking in amount div
    list.on('click', '.' + this.amountWrapper, function(e) {
    	e.stopPropagation();
    	e.preventDefault();
    });

	// Number of article buttons
    list.on('click', '.' + this.increaseAmountButtonClass + ', .' + this.decreaseAmountButtonClass, function(e) {

        var button = jQuery(this),
            correspondingForm = jQuery(this).parents('form'),
        	id = correspondingForm.data('id'),
        	operation = parseInt(button.val(), 10);

        self.changeNumberOfArticles(id, operation, function(newValue) {
            correspondingForm.find('input[name="ArticleQuantity__AutoField"]').val(newValue);
        });

        e.stopPropagation();
        e.preventDefault();
    });
};

// This is just a hack method, because in our test shop it does not update the basket amount
// There is probably a plenty function which does it
FlAutocomplete.Plentymarkets.prototype.updateBasketQuantity = function(id) {

	var currentForm = this.getFormWrapperById(id),
	    amountField = currentForm.find('.' + this.inputAmountFieldClass),
	    basket = jQuery('#basket_quantity'),

	    amountFieldValue = parseInt(amountField.val(), 10),
	    currentBasketValue = parseInt(basket.html(), 10),

	    value = currentBasketValue + amountFieldValue;

	basket.html(value);
};

FlAutocomplete.Plentymarkets.prototype.addToCart = function(id) {

	if (id) {

	    var formData = this.getFormWrapperById(id).serialize();

	    // Check if plenty function is defined
	    if (typeof plentyAjaxRequest2 === 'function') {

	        plentyAjaxRequest2('/WebAjaxBase.php?Object=article@BasketAddArticle&' + formData);

			// Change shopping cart amount text (until we know how plenty does it)
	        this.updateBasketQuantity(id);
	    } else {
	        throw "plentyAjaxRequest2() does not exist";
	    }

	}
};

// $Revision: 250 $
// Maximale Bildbreite für die Thumbnail-Bilder in der Liste
maxWidthThumb = 40;
// Maximale Bildhöhe für die Thumbnail-Bilder in der Liste
maxHeightThumb = 40;
// Maximale Bildbreite für die Vorschau-Bilder bei Mouseover
maxWidthPreview = 200;
// Maximale Bildhöhe für die Vorschau-Bilder bei Mouseover
maxHeightPreview = 200;

// Benennung der Kategorien in der Liste
itemLabel = {
	'title':'Produktname',
	'cat':'Kategorie',
	'vendor':'Hersteller',
	'ordernumber':'Artikelnummer'
};

function initAutocomplete(autocompleteField) {

		if (typeof autocompleteField == 'undefined' || autocompleteField.length == 0) {
			alert("Autocomplete: Search field not found");
			return;
		}		

		function format(item, autocompleteObject) {

			result = '';
			item.outputCat = itemLabel[item.cat];
			
			// Ausgabecode für die einzelnen Treffer
			if (item.image) {
				result += '<td class="ac_image" align="center" width="' + maxWidthThumb + 'px" height="' + maxHeightThumb + '"><img src="' + item.image + '" alt="Thumbnail" style="display:none;" /></td>';
			} else {
				result += '<td><span style="display:block;width:' + maxWidthThumb + 'px;height:' + maxHeightThumb + 'px;"> </span></td>';
			}
			result += '<td class="ac_name" valign="top">' + item.name + '</td>';

			// Only add basket columns, if cat is title
			if (item.cat === 'title') {

				// Price
				var price = item.price  ? item.price : "";
				result += '<td class="ac_price" valign="top">' + price + '</td>';

				// BasePrice
				var basePrice = item.basePrice ? item.basePrice : "";
				result += '<td class="ac_baseprice" valign="top">' + basePrice + '</td>';

				// Cart Button
				var productId = item.parameters.identifier;
				if (productId && autocompleteObject) {
					var cartHtml = autocompleteObject.getCartHtml(productId, item.priceId);
					result += '<td class="ac_cartOption" valign="top">' + cartHtml.html() + '</td>';
				}

			} else {

				result += '<td></td>';
				result += '<td></td>';
				result += '<td></td>';
			}

			return result;
		}

		autocompleteField.autocomplete('/$FL/structured_autocomplete.php', {
			max: 10,
			minChars: 2,
			dataType: "json",
			cacheLength: 1,
			matchSubset: false,
			autoFill: false,
			selectFirst: false,
			scrollHeight: 500,
			scroll: false,
			width: false,
			parse: function(data) {			

				suggestions = jQuery.map(data.suggestions, function(row) {
					row = row.split("|");
					lastCat = "";
					/* sometimes the suggestion text contains the | symbol, so re-join it manually, leaving out the last two parts */
					text = row.slice(0, row.length - 2).join("|");
					return {
						data: {name: text, cat: row[row.length - 2], count: row[row.length - 1]},
						value: text,
						result: text
					}
				})
	//			for (i in suggestions) {
	//				suggestions[i].data.image = data.images[i];
	//				suggestions[i].data.parameters = data.parameters[i];
	//				/* remove the .result if there are parameters present, so the submitted query is empty */
	//				if (!jQuery.isEmptyObject(data.parameters[i])) {
	//					suggestions[i].result = "";
	//				}
	//			}
				jQuery(suggestions).each(function(i) {
					suggestions[i].data.image = data.images[i];
                    suggestions[i].data.parameters = data.parameters[i];

                    if (data.prices) {
                       suggestions[i].data.price = data.prices[i];
                    } else {
                        suggestions[i].data.price = null;
                    }

                    if (data.basePrices) {
                       suggestions[i].data.basePrice = data.basePrices[i];
                    } else {
                        suggestions[i].data.basePrice = null;
                    }

                    if (data.priceIds) {
                       suggestions[i].data.priceId = data.priceIds[i];
                    } else {
                        suggestions[i].data.priceId = null;
                    }
                    
                    if (data.shoppingCart) {
                       suggestions[i].shoppingCart = data.shoppingCart;
                    } else {
                        suggestions[i].shoppingCart = false;
                    }

                    if (data.shopsystem) {
                       suggestions[i].shopSystem = data.shopsystem;
                    } else {
                        suggestions[i].shopSystem = null;
                    }

                    if (!jQuery.isEmptyObject(data.parameters[i])) {
                        suggestions[i].result = "";
                    }
				});
				return suggestions;
			},
			formatItem: function(item, autocompleteObject) {
				return format(item, autocompleteObject);
			},
			extraParams: getExtraParams(autocompleteField[0]),
			highlight: function(row, term) {
				/* remove trailing and leading whitespace, not relevant for highlighting */
				term = term.replace(/^\s+/, "");
				term = term.replace(/\s+$/, "");

                var titleColumn = row.find('.ac_name'),
                    value = titleColumn.html();

                /* 
                 * replace non-alphanumeric characters with the "|", effectively splitting the term so all words will be highlighted
                 * \W does not match Umlaute, so manually build a negated character class that includes them explicitly
                 */
                
                /* Cyrillic characters are not part of the \w character class so include them explicitly */
                var cyrillicCharacters = "\u0400-\u04FF";
                 
                var highlightRegex = term.replace("/[^\wäöüÄÖÜß" + cyrillicCharacters + "]+/g", "|");
                
                var highlightedTerm = value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + highlightRegex + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<span class='flHighlight'>$1</span>");

                titleColumn.html(highlightedTerm);

				return highlightedTerm;
			},
		})
}

jQuery(function() {	

	initAutocomplete(autocompleteField);

});

// preloads images
function preloadImages (dom, maxW, maxH) {
	jQuery(dom).preload({
		onComplete:completePL,
		treshhold:5
	});
	function completePL (data) {
		if(data.found) {
			resizeImage(jQuery(data.original), maxW, maxH);
		}
	}
}

// resizes image to given dimensions
function resizeImage (original, maxW, maxH) {
	if (original.width() > maxW || original.height() > maxH) {
                if (original.width() * maxH > original.height() * maxW) {
                        newW = new String(maxW) + "px";
                        newH = new String(maxW * original.height() / original.width()) + "px";
                } else {
                        newH = new String(maxH) + "px";
                        newW = new String(maxH * original.width() / original.height()) + "px";
                }
        } else {
                newW = new String(original.width()) + "px";
                newH = new String(original.height()) + "px";
        }
	original.css({width:newW,height:newH});
	original.show(); 
}

// starts preview on mouseover
function showPreview() {
	xOffset = -5;
	yOffset = 5;
	
	jQuery('.ac_results table tr').live('hover', 
		function(e){
			src = jQuery(this).children('td').children('img').attr('src'); 
			if (src && jQuery(this).children('td').children('img').css('display') != 'none') {
				if (e.type == 'mouseenter' || e.type == 'mouseover') {
					jQuery('body').append('<div id="preview"><img src="' + src + '" alt="Preview" style=display: none;" /></div>');
					preloadImages('#preview img', maxWidthPreview, maxHeightPreview);
					jQuery("#preview").css("top",(e.pageY - xOffset) + "px").css("left",(e.pageX + yOffset) + "px").fadeIn("fast");
				} else {
					jQuery('#preview').remove();
				}
			}
		}
	);
	jQuery('.ac_results table tr').live('mousemove', 
		function(e){
			var bodyW = jQuery('body').outerWidth();
			var bodyH = jQuery('body').outerHeight();
			var imgW = (e.pageX + yOffset + jQuery('#preview').width());
			var imgH = (e.pageY - xOffset + jQuery('#preview').height());
			var imgL = (e.pageX + yOffset);
			var imgT = (e.pageY - xOffset);
			if (imgH > bodyH) {
				imgT = (e.pageY-jQuery('#preview').height()+xOffset);
			}
			if (imgW > bodyW) {
				imgL = (e.pageX-jQuery('#preview').width()-yOffset);
			}
			jQuery("#preview").css("top",imgT + "px").css("left",imgL + "px");
		}
	);
	jQuery(document).keydown(function(e) {
		if (e.keyCode == 27) {
			jQuery("#preview").remove();
		}
	});
}

/* append filter parameters to the form before it is submitted */
function addParameters(form, data, name, depth) {
	
	/*
	 * Attributes that were added by the autocomplete need to be removed
	 * before new ones are added. Use a jQuery data attribute to store which
	 * attributes were added by the autocomplete
	 */
	var addedByAutocompleteKey = "added-by-autocomplete";
	
	/* Remove attributes added by the autocomplete */
	jQuery(form).find(":input").each(function() {
		var isAddedByAutocomplete = jQuery(this).data(addedByAutocompleteKey);
		if (isAddedByAutocomplete) {
			jQuery(this).remove();
		}
	});
	
	for (i in data) {
		var encodedName = encodeURI(i);
		subname = (depth > 0 ? name + "[" + encodedName + "]" : encodedName);
		if (typeof data[i] != "object") {
			var attributeElement = jQuery("<input/>").attr("type", "hidden").attr("name", subname).val(data[i]);
			/* mark it as being added by the autocomplete */
			attributeElement.data(addedByAutocompleteKey, true);
			jQuery(form).append(attributeElement);
		} else {
			addParameters(form, data[i], subname, depth + 1);
		}
	}
}

autocompleteField.result(function(event, data, formatted) {
	addParameters(event.target.form, data.parameters, "", 0);
});

function getExtraParams(input) {
	parameters = getFormValues(input);
	parameters.type = "structured";
	return parameters;
}

/* collect all form values to be submitted with the autocomplete request */
function getFormValues(input) {
	inputs = {};
	jQuery(jQuery(input.form).serializeArray()).each(function(i, j) {
		inputs[j.name] = j.value;
	});
	inputs.query = function() { return jQuery(input).val(); };
	return inputs;
}

showPreview();