﻿// Fusion Javascript Library 
// (c) Webfuel

if (typeof (Fsn) == 'undefined') Fsn = {};

Fsn.Grid = function(settings, my) {
	my = my || {};
	var IGrid = Fsn.Control(settings, my);

	$.extend(my, {
		dataSource: settings.dataSource,

		rows: [],
		columns: [],
		templates: {},

		header: Fsn.GridHeader(settings, { grid: IGrid }),
		footer: Fsn.GridFooter(settings, { grid: IGrid }),
		filter: Fsn.GridFilter(settings, { grid: IGrid }),

		// <<< Presentation >>>
		hideHeader: settings.hideHeader,

		// <<< Local Page Properties >>>
		page: [],
		pageSize: 0,
		pageIndex: 0,
		itemCount: 0,

		orderCol: null,
		orderDir: 0, 						// 0 = no order, 1 = asc, 2 = dsc

		selectedIndex: -1,
		filterOn: false,

		click: settings.click || null,

		// <<< Render Attributes >>>
		width: settings.width || '100%',
		maxRows: settings.maxRows || 10			// Maximum number of rows the grid can display
	});
	my.addEvents('refresh');
	my.addGetters('columns;maxRows;pageSize;pageIndex;itemCount;selectedIndex;click;filterOn;');
	my.addSetters('orderCol;orderDir');

	// Process the columns
	settings.columns = settings.columns || [];
	for (var i = 0; i < settings.columns.length; i++) {
		my.columns.push(Fsn.GridColumn(settings.columns[i], { grid: IGrid }));
	}

	// Process the templates
	settings.templates = settings.templates || [];
	for (var i = 0; i < settings.templates.length; i++) {
		my.templates[settings.templates[i].name] = (new Fsn.GridTemplate(settings.templates[i], { grid: IGrid }));
	}

	// Process the rows
	for (var i = 0; i < my.maxRows; i++) {
		my.rows.push(Fsn.GridRow({ index: i }, { grid: IGrid }));
	}

	$.extend(IGrid, {

		// Methods

		refresh: function(page, pageSize, pageIndex, itemCount) {
			if (page) {
				my.page = page;
				my.pageSize = pageSize;
				my.pageIndex = pageIndex;
				my.itemCount = itemCount;
			}

			// If we are bound to a dataSource then see if any of the items on this page are the current item
			if (my.dataSource) {
				my.selectedIndex = -1;
				var dataSource = $PageManager.getDataSource(my.dataSource);
				for (var i = 0; i < my.page.length; i++) {
					if (dataSource.isItem(my.page[i])) {
						my.selectedIndex = i;
						break;
					}
				}
			}

			// Refresh the header
			my.header.refresh();
			// Refresh the filter
			my.filter.refresh();
			// Refresh each row
			for (var i = 0; i < my.maxRows; i++) {
				my.rows[i].refresh(my.page.length > i ? my.page[i] : null);
			}
			// Render the footer
			my.footer.refresh();
		},

		getTemplate: function(name) {
			return my.templates[name];
		},

		pageTo: function(pageIndex) {
			if (my.dataSource) {
				// If we have a data source then that will handle the paging
				$PageManager.getDataSource(my.dataSource).selectPage(pageIndex);
			} else {
				// Otherwise you'd better have some event handler set up to responed to this!
				IDataSource.set('pageIndex', pageIndex);
			}
		}
	});

	// Render the main body of the grid (TODO: Move this into an overridable utility method for special rendering)
	$(my.element).addClass("fui-grid").append("<table></table>").width(my.width);
	// Render the header
	my.header.render();
	// Render the filter
	my.filter.render();
	// Render each row
	for (var i = 0; i < my.rows.length; i++) {
		my.rows[i].render();
	}
	// Render the footer
	my.footer.render();

	if (my.dataSource) {
		// If we have set a dataSource then bind to it's refreshedPage event
		$PageManager.getDataSource(my.dataSource).addEventHandler('refreshedPage', function(sender, eventArgs) {
			IGrid.refresh(eventArgs.page, eventArgs.pageSize, eventArgs.pageIndex, eventArgs.itemCount);
		});
		$PageManager.getDataSource(my.dataSource).addEventHandler('refreshedItem', function(sender, eventArgs) {
			IGrid.refresh();
		});
		// And link up a finq source
		$PageManager.getDataSource(my.dataSource).addFinqSource($.delegate(function() {
			var finqNodes = my.filter.filterNodes();
			my.filterOn = finqNodes.length > 0;
			if (isFinite(my.orderCol) && my.orderDir != 0) {
				finqNodes.push($.finqNode(settings.columns[my.orderCol].dataField, my.orderDir, $.finqNodeTypes.OrderBy));
			}
			return finqNodes;
		}, this));
	}
	if (my.hideHeader)
		my.header.hide();

	return IGrid;
};

Fsn.GridColumn = function(settings, my) {
	my = my || {};
	var IGridColumn = Fsn.Component(my);

	// We keep all settings in settings, not my.  Easier to pass to the field constructor.
	settings.header = settings.header || $.formatHeader(settings.dataField);
	settings.columnAlign = settings.columnAlign || (settings.dataField ? "left" : "right");

	/// <<< Internal Methods >>>

	var styleDataCell = function($cell) {
		var style = "";
		if (settings.columnWidth)
			style += "width:" + settings.columnWidth + ";";
		if (settings.columnAlign)
			style += "text-align:" + settings.columnAlign + ";";
		$cell.attr("style", style);
	};

	$.extend(IGridColumn, {

		// <<< Methods >>>

		renderDataCell: function($cell, index) {
			if (settings.dataCellTemplate) {
				// Nothing to do,  we render templates on refresh
			} else if (settings.dataField) {
				settings.isEditable = false;
				$cell.field(settings);
			}
			styleDataCell($cell);
		},

		renderHeaderCell: function($cell, index) {
			if (settings.headerCellTemplate) {
				// Nothing to do,  we render templates on refresh
			} else if (settings.header) {
				$cell.html(settings.header + "&nbsp;<span style='display:inline-block' class='ui-icon'></span>");
				$cell.click($.delegateEvent(this.reorder, this, { index: index }));
			}
			styleDataCell($cell);
		},

		renderFilterCell: function($cell, index) {
			if (settings.dataCellTemplate) {
				// Nothing to do,  template columns have no filter
			} else if (settings.dataField) {
				var field = $.extend({}, settings);
				field.isEditable = true;
				field.isNullable = true;
				$cell.field(field);
			}
			styleDataCell($cell);
		},

		refreshDataCell: function($cell, item, index) {
			if (settings.dataField) {
				// TODO: Can we put in a short cut here?
				$cell.field("setValues", item);
			} else if (settings.dataCellTemplate) {
				$cell.empty();
				var template = my.grid.getTemplate(settings.dataCellTemplate);
				if (!template)
					return;
				template.instantiateIn($cell, item);
			}
		},

		refreshHeaderCell: function($cell, index) {
			if (settings.headerCellTemplate) {
				$cell.empty();
				var template = my.grid.getTemplate(settings.headerCellTemplate);
				if (!template)
					return;
				template.instantiateIn($cell, null);
			} else {
				var orderDir = my.grid.get('orderDir');
				var orderCol = my.grid.get('orderCol');
				if (index != orderCol || orderDir == 0) $("span", $cell).removeClass("ui-icon-triangle-1-n ui-icon-triangle-1-s");
				else if (orderDir > 0) $("span", $cell).removeClass("ui-icon-triangle-1-s").addClass("ui-icon-triangle-1-n");
				else $("span", $cell).removeClass("ui-icon-triangle-1-n").addClass("ui-icon-triangle-1-s");
			}
		},

		refreshFilterCell: function($cell, index) {
		},

		reorder: function(event, args) {
			var orderDir = my.grid.get('orderDir');
			var orderCol = my.grid.get('orderCol');
			orderDir = (args.index === orderCol) ? ((orderDir + 2) % 3) - 1 : 1;
			orderCol = args.index;
			my.grid.set('orderDir', orderDir);
			my.grid.set('orderCol', orderCol);
			my.grid.pageTo();
		}
	});
	return IGridColumn;
};

Fsn.GridRow = function(settings, my) {
	my = my || {};
	var IGridRow = Fsn.Component(settings, my);

	$.extend(my, {
		index: settings.index,
		// <<< Internal Rendering Elements >>>
		$row: null,
		$cells: [],
		// <<< Internal Row Event Handlers >>>
		click: null
	});

	// <<< Internal Method >>>

	// <<< Template Variables >>>

	var Item = null;

	$.extend(IGridRow, {

		// <<< Methods >>>

		render: function() {
			var $element = $(my.grid.get('element'));
			var $grid = $("table", $element);
			var columns = my.grid.get('columns');

			my.$row = $("<tr></tr>").appendTo($grid);
			for (var c = 0; c < columns.length; c++) {
				var $cell = $("<td></td>").appendTo(my.$row);
				columns[c].renderDataCell($cell, c);
				my.$cells.push($cell);
			}
			// Apply css classes to the row
			if (isFinite(my.index)) {
				my.$row.addClass("r" + (my.index));
				if ((my.index % 2) == 1)
					my.$row.addClass("alt");
				if (my.index == columns.length - 1)
					my.$row.addClass("rN");
			}
			// Bind the row event handlers
			my.$row.bind('click', function(Event) { if (my.click) return my.click(Event); });
		},

		refresh: function(item) {
			var columns = my.grid.get('columns');
			if (!item) {
				my.$row.hide();
			} else {
				my.$row.show();
				for (var c = 0; c < columns.length; c++) {
					columns[c].refreshDataCell(my.$cells[c], item, c);
				}
				// Add/Remove the selected item class
				my.$row[my.index === my.grid.get('selectedIndex') ? 'addClass' : 'removeClass']("sel");
				// Bind the click event
				var click = my.grid.get('click');
				if (!click) {
					my.$row.unbind('click');
					my.$row.attr('style', 'cursor:default');
				} else {
					Item = item;
					eval("my.click = function(Event) { " + $.evaluateTemplate(click, function(binding) { return eval(binding); }) + "; return false }");
					my.$row.attr('style', 'cursor:pointer');
				}
			}
		}

	});
	return IGridRow;
};

Fsn.GridHeader = function(settings, my) {
	my = my || {};
	var IGridHeader = Fsn.Component(settings, my);

	$.extend(my, {
		// <<< Internal Rendering Elements >>>
		$row: null,
		$cells: []
	});

	$.extend(IGridHeader, {

		// <<< Methods >>>

		render: function() {
			var $element = $(my.grid.get('element'));
			var $grid = $("table", $element);
			var columns = my.grid.get('columns');

			my.$row = $("<tr></tr>").appendTo($grid);
			for (var c = 0; c < columns.length; c++) {
				var $cell = $("<td></td>").appendTo(my.$row);
				columns[c].renderHeaderCell($cell, c);
				my.$cells.push($cell);
			}
			// Apply css classes to the row
			my.$row.addClass("rH");
		},

		refresh: function() {
			var columns = my.grid.get('columns');
			for (var c = 0; c < columns.length; c++) {
				columns[c].refreshHeaderCell(my.$cells[c], c);
			}
		},

		hide: function() {
			my.$row.hide();
		},

		show: function() {
			my.$row.show();
		}

	});
	return IGridHeader;
};

Fsn.GridFilter = function(settings, my) {
	my = my || {};
	var IGridFilter = Fsn.Component(settings, my);

	$.extend(my, {
		// <<< Internal Rendering Elements >>>
		$row: null,
		$cells: []
	});

	$.extend(IGridFilter, {

		// <<< Methods >>>

		render: function() {
			var $element = $(my.grid.get('element'));
			var $grid = $("table", $element);
			var columns = my.grid.get('columns');

			my.$row = $("<tr></tr>").appendTo($grid);
			for (var c = 0; c < columns.length; c++) {
				var $cell = $("<td></td>").appendTo(my.$row);
				columns[c].renderFilterCell($cell, c);
				my.$cells.push($cell);
			}
			// Apply css classes to the row
			my.$row.addClass("rH");
		},

		refresh: function() {
			var pageSize = my.grid.get('pageSize');
			var itemCount = my.grid.get('itemCount');
			if (itemCount > pageSize || my.grid.get('filterOn')) {
				this.show();
				var columns = my.grid.get('columns');
				for (var c = 0; c < columns.length; c++) {
					columns[c].refreshFilterCell(my.$cells[c], c);
				}
			} else {
				this.hide();
			}
		},

		hide: function() {
			my.$row.hide();
		},

		show: function() {
			my.$row.show();
		},

		// Returns the finqNodes that make up the filter expression
		filterNodes: function() {
			var finqNodes = [];
			$(".fui-field", my.$row).field("getFilters", finqNodes);
			return finqNodes;
		}

	});
	return IGridFilter;
};

Fsn.GridFooter = function(settings, my) {
	my = my || {};
	var IGridFooter = {};

	$.extend(my, {
		// <<< Internal Rendering Elements >>>
		$row: null,
		$cell: null,

		$pager: null,
		$message: null,

		$first: null,
		$prev: null,
		$next: null,
		$last: null

	});

	$.extend(IGridFooter, {

		// <<< Methods >>>

		render: function() {
			var $element = $(my.grid.get('element'));
			var $grid = $("table", $element);
			var columns = my.grid.get('columns');

			my.$row = $("<tr></tr>").appendTo($grid);
			my.$cell = $("<td colspan='" + columns.length + "'></td>").appendTo(my.$row);

			my.$pager = $("<span style='float:right; text-align:right'></span>").appendTo(my.$cell);
			my.$first = $("<span title='First Page' class='ui-icon ui-icon-seek-first'></span>").appendTo(my.$pager).click(function(Event) { my.grid.pageTo(0); Event.stopPropagation(); });
			my.$prev = $("<span title='Previous Page' class='ui-icon ui-icon-seek-prev'></span>").appendTo(my.$pager).click(function(Event) { my.grid.pageTo(Math.max(my.grid.get('pageIndex') - 1, 0)); Event.stopPropagation(); });
			my.$next = $("<span title='Next Page' class='ui-icon ui-icon-seek-next'></span>").appendTo(my.$pager).click(function(Event) { my.grid.pageTo(my.grid.get('pageIndex') + 1); Event.stopPropagation(); });
			my.$last = $("<span title='Last Page' class='ui-icon ui-icon-seek-end'></span>").appendTo(my.$pager).click(function(Event) { my.grid.pageTo(-1); Event.stopPropagation(); });

			my.$message = $("<span></span>").appendTo(my.$cell);
		},

		refresh: function() {
			var pageSize = my.grid.get('pageSize');
			var pageIndex = my.grid.get('pageIndex');
			var itemCount = my.grid.get('itemCount');

			if (itemCount == 0) {
				my.$pager.hide();
				my.$message.html("There are no items to display.");
			} else {
				var pageCount = Math.ceil(itemCount / pageSize);
				if (pageCount > 1) {
					my.$pager.show();
					if (pageIndex == 0) {
						my.$first.removeClass("ui-icon-seek-first").addClass("ui-icon-seek-first-gray");
						my.$prev.removeClass("ui-icon-seek-prev").addClass("ui-icon-seek-prev-gray");
					} else {
						my.$first.addClass("ui-icon-seek-first").removeClass("ui-icon-seek-first-gray");
						my.$prev.addClass("ui-icon-seek-prev").removeClass("ui-icon-seek-prev-gray");
					}
					if (pageIndex == pageCount - 1) {
						my.$next.removeClass("ui-icon-seek-next").addClass("ui-icon-seek-next-gray");
						my.$last.removeClass("ui-icon-seek-end").addClass("ui-icon-seek-end-gray");
					} else {
						my.$next.addClass("ui-icon-seek-next").removeClass("ui-icon-seek-next-gray");
						my.$last.addClass("ui-icon-seek-end").removeClass("ui-icon-seek-end-gray");
					}
				} else {
					my.$pager.hide();
				}
				my.$message.html("<span>Page " + (pageIndex + 1) + " of " + pageCount + " (" + itemCount + " items)</span>");
			}
		}

	});
	return IGridFooter;
};


Fsn.GridTemplate = function(settings, my) {
	my = my || {};
	var IGridTemplate = {};

	$.extend(my, {
		name: settings.name,
		content: settings.content
	});

	// <<< Template Variables >>>

	var Item = null;

	$.extend(IGridTemplate, {
		instantiateIn: function($cell, item) {
			Item = item;
			$cell.html($.evaluateTemplate(my.content, function(binding) { return eval(binding); }));
		}
	});
	return IGridTemplate;
};
