function scPaging(variable, storeId) {

	//checking storeId
	
	//register paging
	$.sc.registerModule("scPaging",3.0);

	//default options that can be overriden during initialization
	this.defaultPagingOptions={
		enabled:true,
		addToCartButton:"", //add to cart button
		arrowPrev:"",//previous page image
		arrowNext:"",//next page image
		arrowPrevOff:"",//previous page image disabled
		arrowNextOff:"",//next page image disabled
		arrowFirst:"",//first page image 
		arrowLast:"",//last page image 
		arrowFirstOff:"",//first page image disabled
		arrowLastOff:"",//first page image disabled
		gridColumns:3,//number of columns for grid layout
		navigationVisible:true,//show top navigation 
		bottomNavigationVisible:true,//show bottom navigation
		defaultItemsPerPage:"12",	//show bottom navigation
		pagesNavigationVisible:true,//show arrows to first,next, previous, last page in navigation bar
		navigateToPageControlVisible:false,//show text box to jump to certain page
		navigateToPageControlReadOnly:false,//should it be just text or actual input box
		navigateToPagePrefix:"page",//prefix for pages
		navigateToPagePagesVisible:true,//show text 1 of 10 pages if readOnly true
		itemsPerPageVisible:true, //show items per page area
		itemsPerPagePrefix:"Items Per Page:",//prefix for items per page
		itemsPerPage:"12,45,75,all",//what values should be used for items per page, example, auto|10,20,40,all 
		itemsPerPageLinkSeparator:"|",//separator for items per page
		itemsPerPageLayout:"links", //layout supported values: links|select 
		pagesLinksVisible:true, //show pages links in navigation bar
		pagesLinksPrefix:"Pages", //prefix for pages links 
		pagesLinksSeparator:"|", //separator for pages links
		pagesLinksEllipsisAfter:"none", //number of pages after what ellipsis (...) should appear, supported values, none|numeric (like 3,5,7) 
		pagesLinksMax:"none", //maximum number of pages to show in navigation, after that number postfix text will appear, supported values none|numeric(10,15,18)
		pagesLinksMaxPostfix:"...",//text appear after pages links when maximum number of pages reached
		pagesLinksMaxPrefix:"...", //text appear before pages links when maximum number of pages reached
		pagesLinksMode:"pages", //what numbers to show in pages links (1,2,3 or 1-10, 11-20, 21-23), supported values: items|pages
		pagesLinksShowAll:false,//show - show all links - in pages	
		pagesLinksShowAllText:"Show All",
		totalItemsVisible:true, //show total of items text
		totalItemsPrefix:"Items", //prefix for total of items
		dataLayoutVisible:true, //show buttons to switch dataLayouts
		dataLayoutDefault:"grid", //default data layout, supported values: grid|list
		dataLayoutDelimiter:"|", //delimitter between buttons for switching layouts
		filterVisible:false, //show filter bar
		filterPrefix:"Filter By", //filter bar prefix 
		//configuration for default filter fields to show in filter bar
		//control types: checkbox|radio|select|links
		//filter mode: distinct|range|custom
		//extra options:
		//first: show text like "please select/show all"
		//min, max: control ranges that will be created
		//range: number of ranges that should be created
		/*
		examples
			"color":["select", "Color:", {mode:"distinct",first:"Please select"}],
			"price":["radio", "Price:", {mode:"range",min:0, range:3}]
			"price":["radio", "Price:", {mode:"custom",first:"hi",func:function() {
				return [["<10","below 10"],["-11-20","11-20"],[">20","above 20"]];
			}}]
		*/
		filterFields:{
		},
		filterLayout:"horizontal",//how to render filter bar, supported values: horizontal|vertical
		filterLabelAlign:"right",//how to align labels for radio and checkbox
		filterResetAllVisible:true,//show reset button
		sortVisible:true,//show sort bar
		//configuration for default sort bar, list of sort fields that will be created
		//initial is special field name that represent initial order
		//can be configured to show either bi-directional or single-directional. Bi-directional will work like toggle.
		sortFields:[
			["initial","ASC", "Default"],
			["name","ASC|DESC", "Name"],
			["price","ASC|DESC", "Price"]
		],
		sortLayout:"links", //type of layout for sorting buttons, supported values: links|select
		sortPrefix:"Sort By:", //prefix for sort links
		sortFieldDefault:"initial", //default sort field and default sort direction, initial|DESC
		sortSeparator:"|", //separator for sorting
		controlWidth:"", //width of control, if not empty will be applied for main container (scPaging), required if gridColumnWidth is px
		//grid column width, supported value:px|%|300px|30%,if it's px or %, actual numeric value calculated automatically
		//for px formula is gridColumnWidth / gridColumn - (gridSpacerWidth - 1)
		gridColumnWidth:"%",  
		gridSpacerWidth:"3", //width of vertical spacer in grid layout
		gridHorizontalSpacerVisible:true, //show horizontal spacer in grid layout
		gridVerticalSpacerVisible:true, //show vertical spacer in grid layout
		listHorizontalSpacerVisible:true, //show horizontal spacer in list layout
		gridItemImageTemplate:"${gridImage}", //template for image cell in grid layout
		gridItemInfoTemplate:"${keyIcons}${name}${regularPrice}${salePrice}${orderForm}", //template for info cell in grid layout		
		gridItemInfoTemplate2:"", //template for info cell 2 in grid layout		
		listItemTemplate:"${listImage}<div class=\"scpinfo\">${name}${code}${abstract}${keyIcons}${productReviews}</div><div class=\"scpatc\">${regularPrice}${salePrice}${saveNow}${orderForm}</div>",	//template for info cell in list layout	
		//template for top navigation
		topNavigationTemplate:"<div class=\"scpCtrlLeft\"><div id=\"scpTopSort\" class=\"scpSort\"></div></div><div class=\"scpCtrlCenter\"><div id=\"scpTopPages\" class=\"scpPages\"></div><div id=\"scpTopNumbers\" class=\"scpNumbers\"></div><div id=\"scpTopArrows\" class=\"scpArrows\"></div></div><div class=\"scpCtrlRight\"><div id=\"scpTopDataMode\"class=\"scpDataMode\"></div><div id=\"scpTopItemsPerPage\" class=\"scpItemsPerPage\"></div></div><div class=\"clear\"></div>",
		//template for bottom navigation
		bottomNavigationTemplate:"<div class=\"scpCtrlLeft\"><div id=\"scpBottomSort\" class=\"scpSort\"></div></div><div class=\"scpCtrlCenter\"><div id=\"scpBottomPages\" class=\"scpPages\"></div><div id=\"scpBottomNumbers\" class=\"scpNumbers\"></div><div id=\"scpBottomArrows\" class=\"scpArrows\"></div></div><div class=\"scpCtrlRight\"><div id=\"scpBottomDataMode\"class=\"scpDataMode\"></div><div id=\"scpBottomItemsPerPage\" class=\"scpItemsPerPage\"></div></div><div class=\"clear\"></div>",
		//template to show in case if there is no items
		noRowsTemplate:"No items",
		//template for vertical spacer in grid layout
		gridVerticalSpacerTemplate:"&nbsp;",
		//template for horizontal spacer in grid layout
		gridHorizontalSpacerTemplate:"&nbsp;",
		//template for horizontal spacer in list layout
		listHorizontalSpacerTemplate:"&nbsp;",
		//integrate automatically the following features
		autoIntegrate: {
			snapShop:true, 
			mapPricing:true,
			productReviews:true,
			googleAnalytics:true
		},
		autoCache:true,
		autoSaveState:true,
		regularPriceText:"Regular Price:", //regular price text used in templates
		salePriceText:"Sale Price:",//sale price text used in templates
		saveNowText:"Save Now:", //save now text used in templates
		outOfStockText:"Out Of Stock", //out of stock text used in templates
		clickToSeeOptionsText:"Click to See Options and to Order", //click to see options text used in templates
		quantityInputVisible:false, //show quantity input 
		hasProductReviews:false
	};

	this.variable=variable; //reference to paging object (usually scPaging)
	this.storeId=storeId; //reference to store id (yhst-xxxxx)

	this.items=[]; //array of items objects, not sorted and not filtered
	
	this.events=[]; //collection of events that can executed
	
	this.currentData=null; //current filtered, sorted, paged data
	this.currentPage=0;  // currentPage set by navigation control, starts from 0
	this.currentItemsTotal=0; // total number of items after filtering
	this.currentTotalPages=null; //total number of pages after filtering
	this.currentShowAll=false;

	this.dataLayout=null; //current data layout (gird or list)

	this.filters=[]; //collection of defined filters

	this.sortField=null; //current field to use during sorting
	this.sortOrder="ASC"; //current direction of sorting

	this.pagingHtmlElement=null; //reference to element with id scPaging - main block
	this.contentsHtmlElement=null; //reference to element with id scpContents - contents block
	this.filterHtmlElement=null; //reference to element with id scpFilter - filter bar
	this.navTopHtmlElement=null; //reference to element with id scpTopControls - top navigation
	this.bottomTopHtmlElement=null; //reference to element with id scpBottomControls - bottom navigation

	this.itemsPerPageText=null; // current items per page as text because ALL can be included that will be translated into length of currentData
	this.itemsPerPage=null; //current items per page as number
	this.publicConfig=null; // colleciton of values that can be used inside templates

	this.renderTimes=0;
	this.rendering=false;
	
	//sub templates configuration for items template
	this.itemTemplates={
		"gridImage":["<div class=scpimg><a href=${url}><img src=${gridImage} alt=\"${shortname}\" title=\"${shortname}\" /></a></div>","gridImage"],
		"listImage":["<div class=scpimg><a href=${url}><img src=${listImage} alt=\"${shortname}\" title=\"${shortname}\" /></a></div>","listImage"],
		"name": ["<div class=scpname><a href=${url}>${name}</a></div>","name"],
		"code": ["<div class=scpcode>code:${code}</div>","code"],
		"keyIcons": ["${keyicons}","keyicons"],
		"productReviews": function(index) {
			if (window.scpConfig.hasProductReviews) {
				var html="<div pid=\"" + this.id + "\" class=\"productReviews\"";
				if (this.reviewsRating) {
					if (this.reviewsRating.averageRating) {
						var styleDiv="";
						if (window.sprAverageStarsHeight) {styleDiv+="height:" + window.sprAverageStarsHeight + ";"};
						//if (window.sprAverageStarsWidth) styleDiv+="width:" + window.sprAverageStarsWidth  + ";";
						if (window.sprStarColor) {styleDiv+="background:url(" + window.sprStarColor + ") no-repeat center "};
						if (this.reviewsRating["averageRating"]!==0) {
							styleDiv+=this.reviewsRating["bgYPosition"];
						} else {
							styleDiv+="0px";//item is not reviewed
						}
						return html + " style=\"" + styleDiv + "\">&nbsp;</div>"; 
					}
				}
				html+=">&nbsp;</div>";
				return html;
			}
			return "";
		},
		"regularPrice":function(index) {
			if (this.mapPrice!=true) {
				if (!$.isEmptyString(this.regPrice)) 
					return "<div class=\"" + ((this.salePrice) ? "scpregprice price" : "scpregpriceonly price") + "\">${config.regularPriceText}<span>${regPrice:toPrice()}</span></div>"; 
			}
			return "";
		},
		"salePrice":function(index) {
			if (this.mapPrice!=true) {
				if (!$.isEmptyString(this.salePrice)) 
					return "<div class=\"scpsaleprice sale-price\">${config.salePriceText}<span>${salePrice:toPrice()}</span></div>";
			}
			return "";
		},
		"saveNow":function (index) {
			if (this.mapPrice!=true) {
				if (!$.isEmptyString(this.saveNow)) {
					return "<div class=scpsavenow>${config.saveNowText}<span>${saveNow}</span></div>";
				}
			}
			return "";
		},
		"abstract":["<div class=scpabstract>${abstract}</div>","abstract"],
		"orderForm":function(index, args) {
			if (this.price && this.price>0) {
				if (this.orderable) {
				   if (this.options!==true) {
						if (this.mapPrice) {
							return "<span class=map><form action=${config.orderFormAction}><input id=mapinput type=submit value=\"Click for instant price quote\" height=16 width=155 border=0><a href=\"javascript:makeMAPWin(300,350);\">(Why?)</a><br>${config.addToCartButton}<input type=hidden name=vwitem value=\"${id}\" /><input type=hidden name=vwcatalog value=\"${config.storeId}\" /></form></span>";
						} else {
							return "<form action=${config.orderFormAction}>${config.addToCartButton}<input type=hidden name=vwitem value=\"${id}\" /><input type=hidden name=vwcatalog value=\"${config.storeId}\" />" + ((args.config.quantityInputVisible) ? ("<br><input type=text class=scpquantity value=1 />") : "") + "</form>"
						}
				   }
				   else {
						return "<a href=${url} class=moreoptions>${config.clickToSeeOptionsText}</a>";
				   }
				} else {
					return "<div class=outofstock>${config.outOfStockText}</div>"
				}
			}
			return "";
		}
	}
	
	//control initializaiton, configuration is array of values that will override default settings
	this.init=function(configuration) {
		
		//merging configuration and default options, default options contains all possible names / values 
		this.options=function(os, defaultPagingOptions) {
			options=os || {};
			var a=[];
			for (var p in defaultPagingOptions) 
				a[p]=options[p]!=null ? options[p] : defaultPagingOptions[p];
			return a;
		}(configuration, this.defaultPagingOptions);

		//speed up access to options
		var ops=this.options;
	
		//assigning important settings for rendering controls
		this.dataLayout=ops.dataLayoutDefault;
		this.itemsPerPageText=ops.defaultItemsPerPage;
	
		//if default sort field set as delimitted value (ex. price|DESC) then that sort direction will be used otherwise default value is ASC
		var vs=ops.sortFieldDefault.split("|");
		if (vs.length==2) {
			this.sortField=vs[0];
			this.sortOrder=vs[1];
		} else {
			this.sortField=ops.sortFieldDefault;
		}

		//calculating total of items, items per page, pages count based on initial settings, required for proper rendering 				
		this.currentItemsTotal=this.items.length;	
		this.calculateItemsPerPage();
		this.calculateTotalPages();
		
		//setting references to dom elements 
		this.pagingHtmlElement=$("#scPaging").domElement();		
		this.contentsHtmlElement=$("#scPaging #scpContents").domElement();
		this.filterHtmlElement=$("#scpFilter").domElement();
		this.navTopHtmlElement=$("#scPaging #scpTopControls").domElement();
		this.navBottomHtmlElement=$("#scPaging #scpBottomControls").domElement();
		
		//assigning variables that can be used in templates, it could be extended on initialization
		this.publicConfig={
			"regularPriceText":ops.regularPriceText,
			"salePriceText":ops.salePriceText,
			"saveNowText":ops.saveNowText,
			"clickToSeeOptionsText":ops.clickToSeeOptionsText,
			"outOfStockText":ops.outOfStockText,
			"storeId":this.storeId,
			"addToCartButton":ops.addToCartButton,
			"orderFormAction":"http://order.store.yahoo.net/cgi-bin/wg-order?" + this.storeId,
			"quantityInputVisible":ops.quantityInputVisible,
			"hasProductReviews":ops.hasProductReviews
		}

		if (ops.autoSaveState) this.getStateFromCookie();
		
		//running init event
		this.runEvent("onInit");

		//init integrations
		this.initIntigration();

		//render
		
		//top navigation initialization
		if (ops.navigationVisible) {
			var template=$.template(ops.topNavigationTemplate);
			$(this.navTopHtmlElement).html(template.apply());
		} else {
			$(this.navTopHtmlElement).hide();
		}	
		var html="";	   
		//bottom navigation initialization
		if (ops.bottomNavigationVisible) {
			var template=$.template(ops.bottomNavigationTemplate);
			$(this.navBottomHtmlElement).html(template.apply());
		} else {
			$(this.navBottomHtmlElement).hide();
		}
		//creating data layout buttons, one time only, will not be re-rendered
		//html will be assigned to element with class scpDataMode inside #scPaging
		if (ops.dataLayoutVisible) {
			html="<a href=# class=\"scplistmode" + ((this.dataLayout=="list") ? " selected": "") + "\" onclick=\"return " + this.variable + ".changeDataLayout('list');\">List View</a>" + ops.dataLayoutDelimiter + "<a href=#  class=\"scpgridmode" + ((this.dataLayout=="grid") ? " selected": "") + "\" onclick=\"return " + this.variable + ".changeDataLayout('grid');\">Grid View</a>";
			$("#scPaging .scpDataMode").html(html);	
		}
		//creating items per page control, links or select box, one time only, will not be re-rendered
		//html will be assigned to element with class scpItemsPerPage inside #scPaging
		if (ops.itemsPerPageVisible) {
			html="";
			//adding prefix
			if (ops.itemsPerPagePrefix) {
				html+="<span class=prefix>" + ops.itemsPerPagePrefix + "</span>";
			}
			if (ops.itemsPerPageLayout=="select") {
				html+="<select onchange=\"return " + this.variable + ".changeItemsPerPage(this.options[this.selectedIndex].value);\">";		   
			}
			var iv=ops.itemsPerPage.split(",");
			for (var i=0;i<iv.length;i++) {
				if (ops.itemsPerPageLayout=="select") {
					html+="<option value=\"" + iv[i] + "\" " + ((iv[i]==this.itemsPerPageText) ? "selected" : "") + ">" + iv[i] + "</option>";
				} else {
					html+="<a href=# "  + ((iv[i]==this.itemsPerPageText) ? "class=\"selected\"": "") +  " onclick=\"" + this.variable + ".changeSelectClass(this, 'scpItemsPerPage'); return " + this.variable + ".changeItemsPerPage('" + iv[i] + "');\">" + iv[i] + "</a>" + ((i<iv.length-1) ? ops.itemsPerPageLinkSeparator : "");
				}
			}
			if (ops.itemsPerPageLayout=="select") {
				html+="</select>";
			}
			$("#scPaging .scpItemsPerPage").html(html);
		}
		
		//widths
		if (!$.isEmptyString(ops.controlWidth)) {
			$(this.pagingHtmlElement).css("width",ops.controlWidth);
		}
	
		//render filter bar, one time only
		this.renderFilterbar();
		
		//main reder function that will be used for re-rendering data
		this.render();
	}
	
	//intigration
	this.initIntigration=function() {
		//snapshop version 2.5
		if (this.options.autoIntegrate.snapShop) {
			if (window.scSnapShop && window.scSnapShop.convertImgLinks) {
			
				this.addEvent("onAfterRender",function() {
					try {
						if (this.renderTimes==0) {
							$(function() {
								window.scSnapShop.convertImgLinks();
							});
						} else {
							window.scSnapShop.convertImgLinks();
						}
					} catch (e) {
					
					}
				});
				this.addEvent("onAfterItemRender",function(args) {
						if (args) {
						var td=args["imageCell"] || args["infoCell"];
						//adding class to all images in cell ... assuming it's just one image... 
						$("div.scpimg img",td).addClass("popImg-0036");
						}
				});
				
			};
		};
		//google analytics
		if (this.options.autoIntegrate.googleAnalytics) {
			if (window._gat && window._gat._getTrackerByName("~0") && window._gat._getTrackerByName("~0")._trackEvent) {
				this.addEvent("onAfterRender",function() {
					try {
						$("#scPaging form .scp-order-button").click(function() {
							var item_id=$("input[name=vwitem]",$(this).parent("form").get(0)).value();
							this.trackGoogleAnalyticsEvent("sc paging","adding to the cart",item_id);
						});
						$("#scPaging a.moreoptions").click(function() {
							this.trackGoogleAnalyticsEvent("sc paging","open item page",this.href);
						});
						
					} catch (e) {
					}
				});
			};
		};
		//map pricing
		//key product icons
		//star product reviews
		if (this.options.autoIntegrate.productReviews) {
			if (this.options["hasProductReviews"]) {
				//add script to request stars
				var old=$("#scStarProductReviewsScriptWorker");
				if (old.length>0) {
					$(old).remove();
				}

				var script = document.createElement('script');
				script.id = 'scStarProductReviewsScriptWorker';
				script.type = 'text/javascript';
				
				var ids="";

				for (var i=0;i<this.items.length;i++) {
					if (ids!="") 
						 ids=ids + "|";
					ids=ids + this.items[i].id;
				}
				
				var url="http://admin.starproductreviews.com/scripts/customAverageRating.php?";
				url+="store=" + this.storeId + "&";
				url+="id=" + ids + "&";
				url+="callback=scPaging.starProductReviewsCallback";
				
				script.src = url;
				var heads=window.document.getElementsByTagName("head");
				if (heads && heads[0]) heads[0].appendChild(script);
			}
		}
	}
	
	this.trackGoogleAnalyticsEvent = function (category, action, opt_label, opt_value) {
		var trackerName="~0";//can be different!as default for solidcactus becuase we didnt specify it
		if (category==null) {
			return (window._gat && window._gat._getTrackerByName(trackerName) && window._gat._getTrackerByName(trackerName)._trackEvent?true:false);
		}
		if (window._gat && window._gat._getTrackerByName(trackerName) && window._gat._getTrackerByName(trackerName)._trackEvent) {
			window._gat._getTrackerByName(trackerName)._setVar($.yahoo.pageId);
			window._gat._getTrackerByName(trackerName)._trackEvent(category,action,opt_label,opt_value);
		}	
	}
	
	this.starProductReviewsCallback=function() {
		//update items and render stars
		if (window.sprItems) {
			for (var p in window.sprItems) {
				var anyThing=false;
				for (var i=0;i<this.items.length;i++) {
					if (this.items[i].id==p) {
						var sprItem=window.sprItems[p];
						this.items[i].averageReviewsRating=$.forceInt(sprItem.averageRating);
						this.items[i].reviewsRating={
							"averageRating" : $.forceInt(sprItem.averageRating),
							"bgYPosition" : sprItem.bgYPosition
							};
						anyThing=true;
					}
				}
				if (!anyThing) {
					this.items[i].reviewsRating={averageRating: 0, bgYPosition:""};
				}
			}
		}
		//first time rendering, needed only if it was rendered already
		if (this.renderTimes>0) {
			$(".productReviews").each(function() {
				if (this.getAttribute("pid") && window.sprItems[this.getAttribute("pid")]) {
					$(this).css({
						//"width":window.sprAverageStarsWidth,
						"height":window.sprAverageStarsHeight,
						"background-image":"url("+window.sprStarColor+")",
						"background-position":"center " + window.sprItems[this.getAttribute("pid")]["bgYPosition"]});
				} else {
					//no reviews
				}
			});
		}
	}
	
	// helper to recalculate itemsPerPage variable, as it could be value ALL, then itemsPerPage will be set to total of filtered data
	this.calculateItemsPerPage=function() {
		if (this.itemsPerPageText==null) 
			this.itemsPerPageText=this.options.itemsPerPage;
		if (this.itemsPerPageText.toLowerCase()=="all") 
			this.itemsPerPage=this.currentItemsTotal; 
		else
			this.itemsPerPage=$.forceInt(this.itemsPerPageText);
	}
	
	//helper to update just single item template
	this.updateItemTemplate=function(name, value) {
		this.itemTemplates[name]=value;
	}
	
	// helper to recalculate total page number based on items per page (that always should be recalculated before use this method) and total of items
	this.calculateTotalPages=function() {
		this.currentTotalPages=Math.floor(this.currentItemsTotal / this.itemsPerPage);
		if (this.currentItemsTotal % this.itemsPerPage!=0)
			this.currentTotalPages++;
	}
	
	//adding event to event collection
	this.addEvent=function(eventName, func) {
		if (this.events[eventName]==null)
			this.events[eventName]=[];
		
		this.events[eventName].push(func);
	}
	
	//run event with eventName and passing args, return value of registered event handler
	this.runEvent=function(eventName, args) {
		if (this.events[eventName]) {
			var funcs=this.events[eventName];
			for (var i=0;i<funcs.length;i++) {
				if ($.typeOf(funcs[i])=="function") {
					
					args=args || [];
					
					args["sender"]=this;
					
					var result=funcs[i].call(this, args);
					
					if (funcs.length==1)
						return result;
				
				}
			}
			return true;
		}
	}
	
	//add item to items collection, setting originalIndex and paging reference to the control for each item
	//cloning items into new array, x is object to be cloned 
	this.addItem=function(x) {
		var a=[];
		for (var p in x) {
			a[p]=x[p];
		}
		a.paging=this;
		a.originalIndex=this.items.length;
		a.cache=null;
		this.items.push(a);
	}
	
	this.clearCache=function() {
		for (var i=0;i<this.items.length;i++) {
			this.items[i].cache=null;
		}
	}
	
	//filter functions	
	
	//helper to get distinct values from all items for specific field
	//can be used to get list of colors or brands, like scPaging.distinctItemValues("color")
	this.distinctItemValues=function (fieldName) {
		var a=[];
		for (var i=0;i<this.items.length;i++) {
			if (this.items[i][fieldName]!=null) {
				var vs=this.items[i][fieldName].split("|");
				for (var u=0;u<vs.length;u++) {
					var b=true;
					var curr=vs[u];
					for (var j=0;j<a.length;j++) {
						if (curr==a[j]) {
							b=false;
							break;
						}
					}
					if (b && curr!=null)
						a.push(curr);
				}
			}
		}
		return a;
	}
	
	//function that determine if item passes all filters
	this.matchFilters=function (item, index) {
		
		//collection of filters
		var filters=item.paging.filters;
		
		//for each filter that registered in controls
		for (var fieldName in filters) {
			if ($.typeOf(filters[fieldName])!="function") {
				var value=item[fieldName]; //value of fieldName inside item, if not null then evaluate filter
				if (value!=null) {
					var b=0;
					//for each fieldName, multiple conditions could be set, like color red and blue
					//so for each condition
					for (var i=0,len=filters[fieldName].length;i<len;i++) {
						var op=filters[fieldName][i][0]; //operator to perform certain operation like -,=,>,<
						var x=filters[fieldName][i][1]; //value to be checked
						
						if (x!=null && op!=null) {
							//for not range operation types of values, we need to use the same types of variables to evaluate condition
							//conversion for the same type
							if (op!="-" && $.typeOf(value)!=$.typeOf(x)) {
								switch ($.typeOf(value).toLowerCase()) {
									case "string": x=new String(x);break;
									case "number": x=$.forceFloat(x);break;
								}
							}
							var ev=null; //condition
							switch(op) {
								case "=": ev = value==x; break; // equal
								case "~": 
									ev = false; 
									var vs=value.split("|");
									for (var k=0;k<vs.length;k++) {
										if (vs[k]==x) {
											ev=true;
											break;
										}
									}
									break;
								//case ">=": ev = value>=x; break;
								//case "<=": ev = value<=x; break;
								case ">": ev = value>x; break; //greater 
								case "<": ev = value<=x; break; //less
								case "-": //range, shohuld be done just for numbers, bounderies are included 
									if (x.indexOf("-")>0 && x.substring(x.length-1)!="-") {
									
										var x1=$.forceFloat(x.substr(0,x.indexOf("-")));
										var x2=$.forceFloat(x.substr(x.indexOf("-")+1));
										
										ev = value>=x1 && value<=x2;
									}
									break;
							}
							if (ev!=null) {
								if (ev==false)
									b=1; // one of the values (red OR blue) did not match 
								else {
									b=2; //one of the values (red OR blue) matched 
									break; //OR - one of the value should match, that could be conditional, depending on client's specs
								}
							}
						}
					}
					if (b==1)
						return false; //AND - one of the property did not match
						
				} else {
					return false; //if property is null
				}
			}
		}
	
		return true; // by default include item
	}
	
	//adding filter for certain field, operator and value
	//like adding filter for color equals red, .addFilter("color","=","red");
	this.addFilter= function (field, operator, value) {
		if (this.filters[field]==null)
			this.filters[field]=[];
		this.filters[field].push([operator, value]);
	}
	
	//remove all filters
	this.removeAllFilters = function () {
		this.filters=[];
	}
	
	//remove filter, if value is null then remove all conditions for that field
	//for example, it's possible to define 2 conditions, color=red and color=blue, so filter all items with color red and blue
	//now to remove all filter for color use this code - .removeFilter("color"), 
	//and to remove condition for just red use that code - .removeFilter("color","red");
	this.removeFilter = function (field, value) {
	
		if (this.filters[field]!=null) {
			if (value==null) {
				delete this.filters[field];
			}
			else 
				for (var i=0,len=this.filters[field].length;i<len;i++) {
					if (this.filters[field][i][1]==value) {
						this.filters[field].splice(i,1);
						if (this.filters[field]!=null && this.filters[field].length==0)
							delete this.filters[field];
						break;
					}
				}
		}
	}
	
	//filter data, reset currentItemsTotal variable, orig is initial colleciton of items
	this.filterData = function (orig) {
		if (this.filters==null) {
			this.currentItemsTotal=orig.length;
			return orig;
		}   
		var a=$.grep(orig, this.matchFilters);
		this.currentItemsTotal=a.length;
		this.calculateTotalPages();
		return a;
	}
	
	//sort functions	   
	this.sortData=function (orig) {
	
		//if sortField is not defined then return initial array
		if (this.sortField==null || orig==null || orig.length==0)
			return orig;

		//clone array for sorting
		var data=orig.slice(0);

		//initial is special field that determines original order, meaning it should not be sorted in that case
		if (this.sortField!="initial") {
			
			//using sort javascript function
			data.sort(function(x,y) {
		
				var a=x[x.paging.sortField];
				var b=y[y.paging.sortField];
		
				if (a==null && b==null)
					return 0;
				
				if (a!=null && b!=null) {
			
					if (a < b) {return -1;}
					if (a > b) {return 1;}
					if (a == b) {return 0;}
					
				} else {
					if (a!=null)
						return 1;
					else
						return -1;
				}
			
			});
		}
		
		//if sortOrder DESC then reverse array otherwise return it
		if (this.sortOrder=="DESC")
		   data.reverse();
		
		return data;
	}
	
	//pages functions
	
	//data after filtering, sorting should be sliced to return and work with just necessary data
	//based on currentPage and items per page we can slice entire filtered and sorted data to return just necessary page
	this.pageData = function (orig) {
		if (orig==null || orig.length<this.itemsPerPage || this.currentShowAll)
			return orig;
		var startItemIndex=this.currentPage * this.itemsPerPage;
		if (startItemIndex<0 || startItemIndex>=orig.length)
			return [];
		var data=orig.slice(startItemIndex, startItemIndex + this.itemsPerPage);
		return data;
	}
	
	//return data, by filtering, sorting and paging original items array, reassining currentData property
	this.getData=function () {
		var data=this.items;
		data=this.pageData(this.sortData(this.filterData(data)));
		if (data==null)
			data=[];
		this.currentData=data;
		return data;
	}
	
	this.getPageId=function() {
		return document.location.href.substr(document.location.href.lastIndexOf("/")+1);
	}
	
	this.getStateFromCookie=function() {
		var pageId=this.getPageId();
		var cookiePageId=$.cookie("scp-id");
		
		if (pageId==cookiePageId) {
			
			this.currentPage=$.cookie("scp-num");

			var cookieSort=$.cookie("scp-sort");
			var cookieFilter=$.cookie("scp-filter");
			var cookieDataLayout=$.cookie("scp-data-layout");
			var cookieItemsPerPage=$.cookie("scp-items-per-page");
			
			if (!$.isEmptyString(cookieSort)) {
				var vs=cookieSort.split("|");
				if (vs.length==2) {
					this.sortField=vs[0];
					this.sortOrder=vs[1];
				} else {
					this.sortField=cookieSort;
				}
			}

			if (!$.isEmptyString(cookieFilter)) {
				this.filters=$.parseJSON(cookieFilter);
			}
			
			if (!$.isEmptyString(cookieDataLayout)) {
				this.dataLayout=cookieDataLayout;
			}
			
			if (!$.isEmptyString(cookieItemsPerPage)) {
				this.itemsPerPageText=cookieItemsPerPage;
				this.calculateItemsPerPage();
				this.calculateTotalPages();
			}
		}
	}
	
	this.setStateInCookie=function() {
	
		$.cookie("scp-id",this.getPageId());
		
		$.cookie("scp-num",this.currentPage);
		$.cookie("scp-sort",this.sortField + "|" + this.sortOrder);
		$.cookie("scp-filter",$.toJSON(this.filters));
		$.cookie("scp-data-layout",this.dataLayout);
		$.cookie("scp-items-per-page",this.itemsPerPageText);
		
	}
	
	//helper to toggle selected class for links inside certain container
	this.changeSelectClass=function(obj, insideClass) {
		$('#scPaging .' + insideClass + ' a.selected').removeClass('selected');
		$(obj).addClass('selected');
	}
	
	//helper for navigation control to fire on blur event after hitting enter (not just tab) 
	this.navigateCtrlKeyed=function(e) {
		if(!e) e=window.event;
		var key = e.keyCode ? e.keyCode : e.which;
		var target = e.target;
		if (e.srcElement) target = e.srcElement;
		if (key==13) target.blur();
	}
	
	//allow changes in navigation control (if they enter invalid value control will not be changed and page will not be rerendered)
	this.navigateCtrlChanged=function(target, nV, oV) {
		if ($.isPositiveInt(nV)) {
			var x=$.forceInt(nV)-1;
			if (x>=0 && x<this.currentTotalPages)
				this.changePage(x);
			else 
				target.value=oV;
		} else {
			target.value=oV;
		}
	}
	
	//change page to certain number, used in links, navigation controls and etc
	//re-render control
	this.changePage=function(x) {
		var x=$.forceInt(x);
		//ga init
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change page","page " + (x+1));
		}
		this.currentShowAll=false;
		this.currentPage=$.forceInt(x);
		this.render();
		return false;
	}
	
	//show all ... but keep navigation paged
	this.changeToShowAll=function() {
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change page","all");
		}

		this.currentShowAll=true;
		this.render();
		this.tryChangeItemsPerPage();
		return false;
	}
	
	this.tryChangeItemsPerPage=function() {
		
	}
	
	//changes number of items per page, then render first page of current data
	//used in navigation control, re-render control
	this.changeItemsPerPage=function(x) {
		if (x==null) return;
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change items per page",x);
		}
		this.currentShowAll=false;
		this.itemsPerPageText=x;
		this.calculateItemsPerPage();
		this.currentPage=0;
		this.calculateTotalPages();
		this.render();
		return false; 
	}
	
	//change data layout (grid or list)
	//used in navigation control, re-render control
	this.changeDataLayout=function(dataLayout) {
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change layout",dataLayout);
		}
		$(".scpDataMode a.selected",this.pagingHtmlElement).removeClass("selected");
		$("a.scp" + dataLayout + "mode",this.pagingHtmlElement).addClass("selected");
		this.dataLayout=dataLayout;
		this.render();
		return false; 	
	}
	
	//change sorting, changeTo is new field, if it contains delimitted piped data then that sort order will be used otherwise it will be reversed
	//for example, if link should be used for bi-directional sorting, then just fieldName should be provided ("color")
	//and if link is used to sort in specific direction then value should be "price|ASC" then items will be sorted from low to high 
	//used in sorting control, re-render control
	this.changeSort=function(changeTo) {
		var vs=changeTo.split("|");
		if (vs.length==2) {
			this.sortField=vs[0];
			this.sortOrder=vs[1];
		} else {
			this.sortField=changeTo;
			if (this.sortOrder=="ASC") this.sortOrder="DESC"; else this.sortOrder="ASC";
		}
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change sort",this.sortField + " | " + this.sortOrder);
		}
		this.render();
		return false; 	
	}
	
	//uses to change/add filter
	//fieldName - specific field
	//value - first symbol determine operation, if it's not set to -,=,> or < then equal operator is used
	//exclusive - then all other filters for that filed should be removed, useful for dropdowns or radio buttons with exclusive choices
	//clear - reset all filters for this field without adding new one, useful for "show all" choice
	//used in filter controls, re-renders control
	this.changeFilter=function(fieldName, value, exclusive, clear) {
		if (this.options.autoIntegrate && this.options.autoIntegrate.googleAnalytics) {
			this.trackGoogleAnalyticsEvent("sc paging","change filter",fieldName + "=" + (value!=null)?value:"");
		}
		var v=value, op="=";
		
		if (v!=null) {
			if ($.startWith(v,"=") || $.startWith(v,">") || $.startWith(v,"<") || $.startWith(v,"-") || $.startWith(v,"~")) {
				op=v.substr(0,1);
				v=v.substr(1);
			}
		}
		
		if (exclusive) 
			this.removeFilter(fieldName);
		else 
			if (clear) this.removeFilter(fieldName, v);
		
		if (clear!=true)
			this.addFilter(fieldName,op,v);
		
		this.render();
		
		return false;
	}
	
	//render control, main method to render different control's parts
	this.render=function () {
	
		this.rendering=true;
	
		this.getData(); // get new data based on filters, sorts and page configuration
		if (this.options.autoSaveState) this.setStateInCookie(); //set cookies to keep state of page
		this.renderSortbar(); //render sort bar
		this.renderNavbar(); //render navigation bar
		this.renderData(); //render data
		
		this.renderTimes++;
		this.rendering=false;
	}
	
	//used to assembelled templates for specific item, assembled set of templates named itemConfig
	//itemConfig is accessible in templates
	this.getItemConfig=function(item, index) {
		var a=[];

		var itemConfig=[];
		
		//cloning item into itemConfig, ommitting reference to paging control
		for (var p in item) {
			if (p=="paging")
				continue;
			itemConfig[p]=item[p];
		}

		//extra settings to make accessible in templates 
		itemConfig["config"]=this.publicConfig; //public configuration
		itemConfig["index"]=index; //index of item
	
		//each template should be evaluated 
		for (var itemTemplate in this.itemTemplates) {

			var templateConfig=this.itemTemplates[itemTemplate]; //item template configuraion 
			var templateText=null; // text of template to be evaluated
			
			if ($.typeOf(templateConfig)=="string") {
				templateText=templateConfig;
			} else {
				if ($.typeOf(templateConfig)=="function") {
					var test_func=templateConfig;
					templateText=test_func.call(item,index,itemConfig);
				} else {
					if ($.typeOf(templateConfig)=="array") {
						templateText=templateConfig[0];
						var test_func=templateConfig[1];
						var false_template=templateConfig[2];
						var test_result=true;
						if (test_func) {
							if ($.typeOf(test_func)=="function") {
								test_result=test_func.call(item,index,itemConfig);
							} else {
								if ($.typeOf(test_func)=="string") {
									test_result=item[test_func];
									if (test_result==null || test_result=="")
										test_result=false;
								}
							} 
							if (test_result==false) 
								templateText=false_template;
						}
					}
				}
			}
			
			if (!$.isEmptyString(templateText)) {
				var template=$.template(templateText);
				a[itemTemplate]=template.apply(itemConfig);
			}
		}
		
		a["item"]=item;
		a["config"]=itemConfig;
		a["index"]=index;
		
		return a;
	}
	
	//render items
	this.renderData=function () {

		//run on before render event
		this.runEvent("onBeforeRender");
	
		var data=this.currentData;
		var dc=this.currentData.length;
		var ops=this.options;

		//it's requred that paging container should have contents Element 
		if (this.contentsHtmlElement==null)
			return;
		
		//clear contents element, by setting innerHTML to nothing
		$(this.contentsHtmlElement).empty();
		
		//setting new class for container as it could be grid or list
		$(this.contentsHtmlElement).removeClass("gridContents").removeClass("listContents").addClass(this.dataLayout + "Contents");

		var itemIndex=0;
		var assembledConfigs=[];
		
		//evaluating configuration for each item	
		for (itemIndex=0;itemIndex<dc;itemIndex++) {
		
			if (ops.autoCache) {
				if (data[itemIndex].cache==null) {
					assembledConfigs[itemIndex]=this.getItemConfig(data[itemIndex], itemIndex);
					data[itemIndex].cache=assembledConfigs[itemIndex];
				} else {
					assembledConfigs[itemIndex]=data[itemIndex].cache;
				}
			} else {
				assembledConfigs[itemIndex]=this.getItemConfig(data[itemIndex], itemIndex);
			}
		}

		//creating dom elements to work with
		var tbody=$("<table>").attr({cellPadding:0,cellSpacing:0}).append("<tbody/>").domElement();
		var rowsIndex=0;
		
		//if empty use empty template 
		if (dc==0) {
			var tr=$("<tr>").addClass("scpEmptyRow").appendTo(tbody);
			var td=$("<td>").appendTo(tr);
			td.html(ops.noRowsTemplate);
		}
		
		if (this.dataLayout=="grid") {
			//grid layout

			//calculating total of rows, if it will be some reminder than add extra row
			var totalRows=Math.floor(dc/ops.gridColumns);
			if (dc % ops.gridColumns!=0)
				totalRows=totalRows + 1;
		
			for (var rowsIndex=0;rowsIndex<totalRows;rowsIndex++) {

				//grid layout will have 2 rows : image and info
				var tr1=$("<tr>").addClass("scpImageGridRow").appendTo(tbody);
				var tr2=$("<tr>").addClass("scpInfoGridRow").appendTo(tbody);
				var tr3=null;
				
				if (!$.isEmptyString(ops.gridItemInfoTemplate2)) {
					tr3=$("<tr>").addClass("scpInfoGridRow2").appendTo(tbody);
				}

				var tr4=null;
				
				//horizontal spacers will be on extra row
				if (ops.gridHorizontalSpacerVisible) {
					tr4=$("<tr>").addClass("scpHorizontalGridSpacerRow").appendTo(tbody);
				}
				
				//if last row then add special classes for each row
				if (rowsIndex==totalRows-1) {
					if (tr1) tr1.addClass("last");
					if (tr2) tr2.addClass("last");
					if (tr3) tr3.addClass("last");
					if (tr4) tr4.addClass("lastSpacer");
				}
				
				//initial value will be first cell of specific row
				itemIndex=rowsIndex * ops.gridColumns;

				//for gridColumns 
				for (var i=0,cols=ops.gridColumns;i<cols;i++) {
					
					var td1=td2=td3=td4=td5=td6=null;
					
					this.runEvent("onBeforeItemRender",[assembledConfigs[itemIndex]]);
					
					if (!$.isEmptyString(ops.gridItemImageTemplate)) {
						var template=$.template(ops.gridItemImageTemplate);
						td1=$("<td>").addClass("scpimgcell").appendTo(tr1);
						if (assembledConfigs[itemIndex]) {
							td1.html(template.apply(assembledConfigs[itemIndex]));
						} else {
							td1.addClass("empty");
						}
					}
					
					if (!$.isEmptyString(ops.gridItemInfoTemplate)) {
							var template=$.template(ops.gridItemInfoTemplate);
							td2=$("<td>").addClass("scpinfocell").appendTo(tr2);
							if (assembledConfigs[itemIndex]) {
								td2.html(template.apply(assembledConfigs[itemIndex]));
							} else {
								td2.addClass("empty");
							}
					}
					
					if (!$.isEmptyString(ops.gridItemInfoTemplate2)) {
							var template=$.template(ops.gridItemInfoTemplate2);
							td6=$("<td>").addClass("scpinfocell").appendTo(tr3);
							if (assembledConfigs[itemIndex]) {
								td6.html(template.apply(assembledConfigs[itemIndex]));
							} else {
								td6.addClass("empty");
							}
					}

					if (ops.gridHorizontalSpacerVisible) {
						td4=$("<td>").addClass("gridHorizontalSpacer").appendTo(tr4);
						td4.html(ops.gridHorizontalSpacerTemplate);
					}					
					
					if (i+1<cols) {
						if (ops.gridVerticalSpacerVisible) {
							td3=$("<td>").addClass("gridVerticalSpacer").appendTo(tr1);
							if (!$.isEmptyString(ops.gridItemInfoTemplate2)) {
								td3.html(ops.gridVerticalSpacerTemplate).attr("rowSpan",3);
							} else {
								td3.html(ops.gridVerticalSpacerTemplate).attr("rowSpan",2);
							}
						};
						if (ops.gridHorizontalSpacerVisible) {
							td5=$("<td>").addClass("gridHorizontalCrossSpacer").appendTo(tr4);
							td5.html(ops.gridHorizontalCrossTemplate);
						}
					}

					if (!$.isEmptyString(ops.gridColumnWidth)) {
						var ws=ops.gridColumnWidth.toLowerCase();
						if (ws=="px") {
							var wc=null;
							if (!$.isEmptyString(ops.controlWidth)) {
								wc=$.forceInt(ops.controlWidth);
							} else {
								wc=$(this.pagingHtmlElement).width();
							}
							if (wc) {
								var wspacer=0;
								if (!$.isEmptyString(ops.gridSpacerWidth)) 
									wspacer=$.forceInt(ops.gridSpacerWidth);
								var calcWidth=($.forceInt(wc)-wspacer*(cols-1)) / cols;
								td1.attr("width",calcWidth);
								td2.attr("width",calcWidth);
							}
						} else {
							if (ws=="%") {
								td1.attr("width",$.forceInt(100 / cols) + "%");
								td2.attr("width",$.forceInt(100 / cols) + "%");
							} else {
								td1.attr("width",ws);
								td2.attr("width",ws);
							}
						}
					}
					this.runEvent("onAfterItemRender",{imageCell:td1,infoCell:td2,infoCell2:td6});
					itemIndex++;
				}
			}
		} else {
			//list layout
		
			var totalRows=dc;
		
			for (var rowsIndex=0;rowsIndex<totalRows;rowsIndex++) {

				if (!$.isEmptyString(ops.listItemTemplate)) {

					itemIndex=rowsIndex;

					var td=td4=null;

					this.runEvent("onBeforeItemRender",[assembledConfigs[itemIndex]]);

					var tr=$("<tr>").addClass("scpListRow").appendTo(tbody);
					var tr4=null;
					
					if (ops.listHorizontalSpacerVisible) {
						tr4=$("<tr>").addClass("listHorizontalSpacerRow").appendTo(tbody);
					}
					var template=$.template(ops.listItemTemplate);
					td=$("<td>").addClass("scpListCell").appendTo(tr);
					td.html(template.apply(assembledConfigs[itemIndex]));
					if (ops.listHorizontalSpacerVisible) {
						td4=$("<td>").addClass("listHorizontalSpacer").appendTo(tr4);
						td4.html(ops.listHorizontalSpacerTemplate);
					}
					if (rowsIndex==totalRows-1) {
						tr.addClass("last");
						if (tr4) tr4.addClass("lastSpacer");
					}
					this.runEvent("onAfterItemRender",{infoCell:td});
				}
			}
		}
		$(this.contentsHtmlElement).append(tbody);
		this.runEvent("onAfterRender",{infoCell:td});
	}
	//render nav bar
	this.renderNavbar = function () {
		
		this.runEvent("onBeforeNavRender");
		
		var data=this.currentData;
		
		var ops=this.options;
		
		var htmlConfigs={};
		
		var min=(this.currentPage * this.itemsPerPage) + 1;
		var max=((min + this.itemsPerPage - 1)>this.currentItemsTotal) ? this.currentItemsTotal : (min + this.itemsPerPage - 1);
		
		if (this.currentItemsTotal==0) min=0;
		
		if (this.currentShowAll) {
			min=1; 
			max=this.currentItemsTotal;
		}
		
		//pages
		if (ops.pagesLinksVisible) {
		
			var html="";
			
			if (this.currentTotalPages>0) {

				var startPage=0;
				var maxPages=this.currentTotalPages;
				
				var ellipseAfter=$.forceInt(ops.pagesLinksEllipsisAfter);
				var pagesLinksMax=$.forceInt(ops.pagesLinksMax);
				var exitFor=false;
				
				if (ops.pagesLinksEllipsisAfter!="none") {
					startPage=this.currentPage - ellipseAfter + 2;
					if (startPage>(this.currentTotalPages-ellipseAfter)) startPage=this.currentTotalPages-ellipseAfter;
				} else {
					if (ops.pagesLinksMax!="none") {
							if (pagesLinksMax+this.currentPage<this.currentTotalPages) 
								startPage=this.currentPage-1; 
							else {
								startPage=this.currentTotalPages-pagesLinksMax;
								if (this.currentPage==startPage) {
									startPage=startPage-1;
								}
							}
					}
				}
				if (startPage<0) startPage=0;
				if (ops.pagesLinksMax!="none") maxPages=startPage + pagesLinksMax;
				if (maxPages>this.currentTotalPages) maxPages=this.currentTotalPages;
				for (var i=startPage;i<maxPages && exitFor==false;i++) {
					if (ops.pagesLinksEllipsisAfter!="none" && (i-startPage)>=ellipseAfter) {
						html+="<span class=\"ellipse\">...</span>";
						i=maxPages - 1;
						exitFor=true;
					}
					var cssClass=(i==this.currentPage && this.currentShowAll==false) ? "class=\"selected\"" : "";
					if (ops.pagesLinksMode=="items") {
						var cMin=(i * this.itemsPerPage) + 1;
						var cMax=((cMin + this.itemsPerPage - 1)>this.currentItemsTotal) ? this.currentItemsTotal : (cMin + this.itemsPerPage - 1);

						html+="<a href=# " + cssClass + " onclick=\"return " + this.variable + ".changePage(" + i + ");\">" + cMin + "-" + cMax+ "</a>";
					} else {
						html+="<a href=# " + cssClass + " onclick=\"return " + this.variable + ".changePage(" + i + ");\">" + (i+1) + "</a>";
					}
					if (i<maxPages-1 && !(ops.pagesLinksEllipsisAfter!="none" && (i+1-startPage)>=ellipseAfter)) html+=ops.pagesLinksSeparator;
					if (exitFor)
						break;
				}
				if (ops.pagesLinksMax!="none") {
					if (startPage>0) html="<span class=maxprefix>" + ops.pagesLinksMaxPrefix + "</span>"+html;
					if (startPage + pagesLinksMax<this.currentTotalPages) html+="<span class=maxpostfix>" + ops.pagesLinksMaxPostfix + "</span>";
				}
				if (maxPages>1) {
					if (ops.pagesLinksShowAll) {
						html+=ops.pagesLinksSeparator
						html+="<a href=# "+ ((this.currentShowAll) ? "class=\"selected\"" : "") + " onclick=\"return " + this.variable + ".changeToShowAll();\">" + ops.pagesLinksShowAllText + "</a>";
					}
				}
			} else {
				html+="0";
			}
			html="<span class=\"prefix\">" + ops.pagesLinksPrefix + "</span>" + html;
			$(".scpPages",this.pagingHtmlElement).html(html);
		}
		//item of
		if (ops.totalItemsVisible) {
			$(".scpNumbers",this.pagingHtmlElement).html("<span class=\"prefix\">" + ops.totalItemsPrefix + "</span><span class=\"currentItems\">" + min + "-" + max + "</span> of <span class=\"total\">" + this.currentItemsTotal + "</span>");
		}
		//navigation arrows
		if (ops.pagesNavigationVisible) {
			html="";
			if (this.currentPage!=0) {
				if (!$.isEmptyString(ops.arrowFirst)) html+="<a href=# class=\"scpFirst\" onclick=\"return " + this.variable + ".changePage(0);\">" + ops.arrowFirst + "</a>";
				if (!$.isEmptyString(ops.arrowPrev)) html+="<a href=# class=\"scpPrev\" onclick=\"return " + this.variable + ".changePage(" + (this.currentPage-1) + ");\">" + ops.arrowPrev + "</a>";
			} else {
				if (!$.isEmptyString(ops.arrowFirstOff) || this.currentShowAll) html+=ops.arrowFirstOff;
				if (!$.isEmptyString(ops.arrowPrevOff) || this.currentShowAll) html+=ops.arrowPrevOff;
			}
			if (ops.navigateToPageControlVisible) {
				if (!this.currentShowAll) {
					if (ops.navigateToPageControlReadOnly) {
						var pagesText=(ops.navigateToPagePagesVisible) ? (this.currentPage + 1) + " of " + this.currentTotalPages : this.currentPage + 1;
						html+="<span class=scpNavigateToPage><span class=prefix>" + ops.navigateToPagePrefix + "</span>" + pagesText + "</span>";
					} else {
						html+="<input type=text class=scpNavigateToPage value=\"" + (this.currentPage+1) + "\" onblur=\"if (this.orig!=this.value) " + this.variable + ".navigateCtrlChanged(this, this.value, this.orig);\" onfocus=\"this.orig=this.value\" onkeyup=\"this.value=this.value;" + this.variable + ".navigateCtrlKeyed(event)\" />";
					}
				} else {
					html+="<span class=scpNavigateToPage><span class=prefix>" + ops.navigateToPagePrefix + "</span>Viewing all</span>";
				}
			} else {
				html+="<span class=sep></span>";
			}
			if (this.currentPage!=this.currentTotalPages-1) {
				if (!$.isEmptyString(ops.arrowNext)) html+="<a href=# class=\"scpNext\" onclick=\"return " + this.variable + ".changePage(" + (this.currentPage+1) + ");\">" + ops.arrowNext + "</a>";
				if (!$.isEmptyString(ops.arrowLast)) html+="<a href=# class=\"scpLast\" onclick=\"return " + this.variable + ".changePage(" + (this.currentTotalPages-1) + ");\">" + ops.arrowLast + "</a>";
			} else {
				if (!$.isEmptyString(ops.arrowNextOff) || this.currentShowAll) html+=ops.arrowNextOff;
				if (!$.isEmptyString(ops.arrowLastOff) || this.currentShowAll) html+=ops.arrowLastOff;
			}
			
			$(".scpArrows",this.pagingHtmlElement).html(html);
		}
		
		this.runEvent("onAfterNavRender");
		
	}
	//render sort bar
	this.renderSortbar = function () {
		if (!this.options.sortVisible) return;
		var html="";
		if (!$.isEmptyString(this.options.sortPrefix)) html+="<span class=\"prefix\">" + this.options.sortPrefix + "</span>";
		if (this.options.sortLayout=="select") html+="<select onchange=\"" + this.variable + ".changeSort(this.options[this.selectedIndex].value)\">";
		for (var i=0, count=this.options.sortFields.length;i<count;i++) {
			var fc=this.options.sortFields[i], f=fc[0], order=fc[1], changeTo=f;
			var biDir=order.indexOf("|")>0
			if (!biDir) changeTo=f + "|" + order;
			if (this.options.sortLayout=="select") {
				html+="<option value=\"" + changeTo +"\" " + (((f==this.sortField || (this.sortField=="" && f=="initial")) && (biDir || this.sortOrder==order)) ? " selected=\"selected\" selected=\"true\" " : "") + ">" + fc[2] + "</option>";
			} else {
				if (i!=0) html+=this.options.sortSeparator;
				var cssClass="";
				if ((f==this.sortField || (this.sortField=="" && f=="initial")) && (biDir || this.sortOrder==order)) {
					cssClass="selected";
					if (biDir) {if (this.sortOrder=="ASC") cssClass+=" asc"; else cssClass+=" desc";}
				}
				if (i==count-1) {if (cssClass!="") cssClass+=" last"; else cssClass="last"};
				
				if (cssClass!="") cssClass=" class=\"" + cssClass + "\"";
				
				html+="<a href=# " + cssClass + " onclick=\"return " + this.variable + ".changeSort('" + changeTo + "');\">" + fc[2] + "</a>";
			}
		}
		if (this.options.sortLayout=="select") html+="</select>";
		$(".scpSort",this.pagingHtmlElement).each(function() {
			this.innerHTML=html;
		});
	}
	//render filter bar
	this.renderFilterbar = function () {
		this.runEvent("onBeforeFilterRender");
		if (!this.options.filterVisible || this.filterHtmlElement==null) {
			$("#scpFilter").hide();
			return;
		}
		var html="";
		if (!$.isEmptyString(this.options.filterPrefix)) html+="<h1 class=\"prefix\">" + this.options.filterPrefix + "</h1>";
		$(this.filterHtmlElement).html(html);
		var i=0;
		if (this.options.filterLayout=="vertical") $(this.filterHtmlElement).addClass("vertical"); 
		for (var f in this.options.filterFields) {
			var fc=this.options.filterFields[f], ctype=fc[0], ftitle=fc[1], ops=fc[2];
			var div=$("<div>").addClass("scpFilter").appendTo(this.filterHtmlElement);
			$("<span>").addClass("prefix").html(ftitle).appendTo(div);
			var ul=$("<ul>").addClass("scpFilterList").appendTo(div);
			var vs=function(ob, field, fc){
				var ops=fc[2] || {mode:"distinct"};
				var a=[];
				if (ops["mode"]==null) ops["mode"]="distinct";
				switch (ops["mode"]) {
					case "distinct": 
						a=ob.distinctItemValues(field);
						if (ops["set"]) {
							for (var i=0;i<a.length;i++) {
								a[i]="~" + a[i];
							}
						}
						break;
					case "range":
						var min=null, max=null;
						for (var i=0;i<ob.items.length;i++) {
							var v=ob.items[i][field];
							if (v!=null) {
								if ($.isFloat(v)) {
									var vc=$.forceFloat(v);
									if (min==null || min>vc) min=vc;
									if (max==null || max<vc) max=vc;
								}
								else return [];
							} 
						}
						var range=ops["range"] || 5;
						var start=min;
						if (ops["min"]!=null) start=ops["min"];
						var end=ops["max"] || max;
						var inc=Math.ceil((end - start) / range);
						for (var i=start;i<end;i=i+inc) {
							var n=i, m=n + inc
							if (m>end) m=end;
							if (ops["format"]=="price") 
								a.push(["-" + n+"-"+m, $.toPrice(n) + "-" + $.toPrice(m)]);
							else 
								a.push("-" + n+"-"+m);
						}
						break;
					case "custom": if(ops["func"]!=null) a=ops["func"](ob, field);break;
				}
				return a;
			}(this, f, fc); 
			var select=null;
			if (ctype=="select") {
				select=$("<select>").appendTo($("<li>").addClass("scpFilterLine").appendTo(ul));
				$(select).domElement().onchange=function(obj, field) {
					return function() {
						var f=field;
						var v=this.options[this.selectedIndex].value;
						if (v!="") 
							obj.changeFilter(f,v,true);
						else {
							obj.changeFilter(f,"",true,true);
						}
					}
				}(this, f);
			} else {
			}
			var el=label=null;
			if (ops.first) {
				switch(ctype) {
					case "radio": 
						el="<input type=radio name=\"filter-" + f + "\" onclick=\"" + this.variable + ".changeFilter('" + f + "',null,true,true);\" />";
						label=ops.first;
						break;
					case "links": 
						el="<a href=# onclick=\"return " + this.variable + ".changeFilter('" + f + "',null,true,true);\" >" + ops.first + "</a>";
						break;
					case "select": 
						$("<option>").attr("value","").html(ops.first).appendTo(select);
						break;
				}
				if (ctype!="select") {
					var li=$("<li>").addClass("scpFilterLine").appendTo(ul);
					if (this.options.filterLabelAlign=="right") {
						$(li).append(el).append(label);
					}
					else {
						$(li).append(label).append(el);
					}
				}
			}
			for (var j=0;j<vs.length;j++) {
				var v=null;
				el=null;
				label=null;
				if ($.typeOf(vs[j])=="array") {
					v=vs[j][0];
					label=vs[j][1];
				} else {
					v=label=vs[j];
					if ($.startWith(v,"=") || $.startWith(v,">") || $.startWith(v,"<") || $.startWith(v,"-") || $.startWith(v,"~")) {
						label=v.substr(1);
					}
				}
				switch(ctype) {
					case "radio":
						el="<input type=radio name=\"filter-" + f + "\" onclick=\"" + this.variable + ".changeFilter('" + f + "','" + v + "',true);\" />";
						break;
					case "checkbox":
						el="<input type=checkbox onclick=\"" + this.variable + ".changeFilter('" + f + "','" + v + "',false, !this.checked); \" />";
						break;
					case "links":
						el="<a href=# onclick=\"return " + this.variable + ".changeFilter('" + f + "','" + v + "',true);\" >" + label + "</a>"; 
						label=null;
						break;
					case "select":
						$("<option>").attr("value",v).html(label).appendTo(select);
						break;
				}
				if (ctype!="select") {
					var li=$("<li>").addClass("scpFilterLine").appendTo(ul);
					if (j==vs.length-1) $(li).addClass("last");
					if (this.options.filterLabelAlign=="right") {
						$(li).append(el).append(label);
					}
					else {
						$(li).append("<span>" + label + "</span>").append(el);
					}
				}
			}
			i++;
		}
		if (this.options.filterResetAllVisible) {
		}
		this.runEvent("onAfterFilterRender");
	}
}


