﻿// Fusion Javascript Library 
// (c) Webfuel

if (typeof (Fsn) == 'undefined') Fsn = {};

Fsn.Event = function(sender, globalArgs) {
	var IEvent = {};

	// <<< Internal Properties >>>

	var my = {
		handlers: null
	};

	// <<< Public Interface >>>

	$.extend(IEvent, {

		// <<< Methods >>>

		addHandler: function(handler) {
			if (!my.handlers) my.handlers = [];
			my.handlers.push(handler);
		},

		raise: function(localArgs) {
			if (!my.handlers) return;
			var result, temp, args, l = my.handlers.length;
			if (!globalArgs) args = localArgs || {};
			else args = $.extend(globalArgs || {}, localArgs);
			for (var i = 0; i < l; i++) {
				temp = my.handlers[i](sender, args);
				if (result === undefined)
					result = temp;
			}
			return result;
		}
	});
	return IEvent;
};

Fsn.EventContainer = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IEventContainer = {};

	$.extend(my, {
		events: {}
	});

	my.addEvents = function(events) {
		var parts = events.split(';');
		var l = parts.length;
		for (var i = 0; i < l; i++)
			my.events[parts[i]] = Fsn.Event(IEventContainer);
	};

	my.addEvent = function(event, sender, globalArgs) {
		my.events[event] = Fsn.Event(sender || IEventContainer, globalArgs);
	};

	$.extend(IEventContainer, {
		addEventHandler: function(event, handler) {
			if (my.events[event])
				my.events[event].addHandler(handler);
			return IEventContainer;
		},
		raiseEvent: function(event, eventArgs) {
			if (my.events[event])
				return my.events[event].raise(eventArgs)
		}
	});
	return IEventContainer;
};

Fsn.Component = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IComponent = Fsn.EventContainer(settings, my);

	$.extend(my, {
		id: settings.id || my.id || null,
		isEnabled: settings.isEnabled !== false,
		accessors: {},
		getters: ";",
		setters: ";"
	});

	my.addGetters = function(getters) {
		if (!getters.match(/;$/))
			getters = getters + ";";
		// Check all the getters are valid
		var parts = getters.split(";");
		var l = parts.length;
		for (var i = 0; i < l; i++) {
			if (parts[i].length > 0 && my.accessors['get_' + parts[i]] === undefined && my[parts[i]] === undefined)
				alert("Fsn.Component.addGetters(): Invalid property '" + parts[i] + "'");
		}
		my.getters = my.getters + getters;
	};

	my.addSetters = function(setters) {
		if (!setters.match(/;$/))
			setters = setters + ";";
		// Check all the setters are valid
		var parts = setters.split(";");
		var l = parts.length;
		for (var i = 0; i < l; i++) {
			if (parts[i].length > 0 && my.accessors['set_' + parts[i]] === undefined && my[parts[i]] === undefined)
				alert("Fsn.Component.addSetters(): Invalid property '" + parts[i] + "'");
		}
		my.setters = my.setters + setters;
	};

	my.addGetters("id");
	my.addSetters("isEnabled");
	my.addEvents("propertyChanged");

	$.extend(IComponent, {
		get: function(prop) {
			if (my.accessors['get_' + prop])
				return my.accessors['get_' + prop]();
			if (my.getters.indexOf(';' + prop + ';') > -1 || my.setters.indexOf(';' + prop + ';') > -1)
				return my[prop];
			alert("Fsn.Component.get(): Attempt to access invalid property '" + prop + "'");
		},
		set: function(prop, value) {
			if (my.accessors['set_' + prop]) {
				my.accessors['set_' + prop](value);
				IComponent.raiseEvent('propertyChanged', { property: prop });
			} else if (my.setters.indexOf(';' + prop + ';') > -1) {
				my[prop] = value;
				IComponent.raiseEvent('propertyChanged', { property: prop });
			} else {
				alert("Fsn.Component.set(): Attempt to access invalid property '" + prop + "'");
			}
			return IComponent;
		}
	});
	return IComponent;
};

Fsn.Dictionary = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IDictionary = Fsn.EventContainer(settings, my);

	$.extend(my, {
		items: {}
	});
	my.addEvents("insertedItem;removedItem");

	// <<< Public Interface >>>

	$.extend(IDictionary, {

		// <<< Methods >>>

		add: function(name, item) {
			my.items[name] = item;
			IDictionary.raiseEvent('insertedItem', { item: item, name: name });
			return item;
		},

		remove: function(name) {
			var item = my.items[name];
			if (item === undefined) return;
			delete my.items[name];
			IDictionary.raiseEvent('removedItem', { name: name, item: item });
		},

		clear: function() {
			my.items = {};
			var keys = [];
			for (var key in my.items) {
				keys.push(key);
			}
			for (var i = 0; i < key.length; i++) {
				delete my.items[keys[i]];
				IDictionary.raiseEvent('removedItem', { name: keys[i] });
			}
		},

		itemAt: function(name) {
			return my.items[name];
		},

		// Use as readonly collection only!
		items: my.items
	});
	return IDictionary;
};

Fsn.List = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IList = Fsn.EventContainer(settings, my);

	$.extend(my, {
		items: []
	});
	my.addEvents("insertedItem;removedItem");

	$.extend(IList, {
		add: function(item) {
			my.items.splice(my.items.length, 0, item);
			IList.raiseEvent('insertedItem', { item: item, index: my.items.length - 1 });
			return item;
		},

		insert: function(item, index) {
			if (index < 0 || index > my.items.length)
				index = my.items.length;
			my.items.splice(index, 0, item);
			IList.raiseEvent('insertedItem', { item: item, index: index });
			return item;
		},

		itemAt: function(index) {
			if (index < 0 || index >= my.items.length) return null;
			return my.items[index];
		},

		remove: function(index) {
			if (index < 0 || index >= my.items.length) return;
			var item = my.items[index];
			my.items.splice(index, 1);
			IList.raiseEvent('removedItem', { index: index, item: item });
		},

		clear: function() {
			while (my.items.length > 0) IList.remove(my.items.length - 1);
		},

		length: function() {
			return my.items.length;
		},

		find: function(item) {
			return $.inArray(item, my.items);
		},

		// Use as readonly collection only!
		items: my.items
	});
	return IList;
};

Fsn.Control = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IControl = Fsn.Component(settings, my);

	$.extend(my, {
		element: settings.element ? $(settings.element) : null,
		isPopup: settings.isPopup === true,
		isModal: settings.isModal === true,
		isEnabled: settings.isEnabled !== false,
		modalOptions: settings.modalOptions || {}
	});
	my.isVisible = (my.isPopup === true) ? settings.isVisible === true : settings.isVisible !== false
	my.addGetters("element;isVisible;isPopup");

	$.extend(IControl, {
		_show: function(options) {
			if (my.isVisible) return;
			if (my.isModal) {
				$PageManager.modalLock(my.modalOptions);
			}
			if (!my.element) return;
			options = options || {};
			if (options.left !== undefined) my.element.css('left', options.left);
			if (options.top !== undefined) my.element.css('top', options.top);
			my.element.show();
			my.isVisible = true;
		},
		_hide: function() {
			if (!my.isVisible) return;
			if (my.isModal) {
				$PageManager.modalUnlock();
			}
			if (!my.element) return;
			my.element.hide();
			my.isVisible = false;
		}
	});
	IControl.show = function(options) { IControl._show(options); };
	IControl.hide = function(options) { IControl._hide(); };

	if (my.isPopup)
		my.element = $("<div></div>").prependTo($("body"));
	if (!my.element)
		my.isVisible = false;
	if (!my.isVisible && my.element)
		my.element.hide();

	return IControl;
};

Fsn.PageManager = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IPageManager = Fsn.Component(settings, my);

	// <<< Internal Properties >>>

	$.extend(my, {
		numAjaxCalls: 0,
		pageData: settings.pageData || {},
		dataSources: Fsn.Dictionary(),
		modalElement: null,
		trustLevel: settings.trustLevel || "Low",
		// <<< Optimisations >>>
		$fields: null,
		$tabs: null,
		$panels: null
	});
	my.addEvents("init;load;ready;layout;");

	// <<< Public Interface >>>

	$.extend(IPageManager, {

		// <<< Optimisations >>>

		$fields: function() {
			if (!my.$fields) return $(".fui-field");
			return my.$fields;
		},

		$tabs: function() {
			if (!my.$tabs) return $(".fui-tab");
			return my.$tabs;
		},

		$panels: function() {
			if (!my.$panels) return $(".fui-panel");
			return my.$panels;
		},

		// <<< Properties >>>

		PageData: my.pageData,

		// <<< Methods >>>

		getDataSource: function(name) {
			return my.dataSources.itemAt(name);
		},

		addDataSource: function(name, settings) {
			settings = settings || {};
			settings.name = name;
			my.dataSources.add(name, Fsn.DataSource(settings)).refreshItem();
		},

		redirectTo: function(url, data) {
			$.redirectTo(url, data);
		},

		call: function(s) {
			my.numAjaxCalls++;
			// Comment this out for release code....
			document.title = "Ajax Calls: " + my.numAjaxCalls;
			$.call(s);
		},

		invoke: function(entity, method, parameters, modalClass, success) {
			modalClass = modalClass || "processing";
			modalInterval = (modalClass == "saving") ? 1500 : undefined;
			$PageManager.call({
				modal: { modalClass: modalClass, modalInterval: modalInterval },
				url: "/WebServices/" + my.trustLevel + "/FusionWebService.asmx/Invoke",
				data: { entity: entity, method: method, parameters: parameters },
				success: success
			});
		},

		// modelClass	: 'loading', 'saving', 'processing', 'lock' (default)
		// fadeSpeed	: time to fade to full opacity
		// opacity		: full opacity level
		// onClick		: onClick event handler
		// minLockTime	: minimum time to hold the lock
		modalLock: function(options) {
			if (!my.modalElement) {
				if (!(my.modalElement = document.getElementById("_modalElement"))) return;
				my.modalElement = $(my.modalElement);
				my.modalElement.lock = 0;
			}
			options = options || {};
			if (my.modalElement.lock == 0) {
				my.modalElement.removeClass().addClass("fui-modal fui-modal-" + options.modalClass || "lock")
				my.modalElement.fadeTo(options.fadeDelay === undefined ? 'slow' : options.fadeDelay, options.opacity === undefined ? 0.6 : options.opacity);
			}
			if (options.onClick)
				my.modalElement.mousedown(options.onClick);
			my.modalElement.lock++;
			$(my.modalElement).show();
			my.modalElement.width($(window).width());
			my.modalElement.height($(window).height());
			if (options.minLockTime) {
				my.modalElement.lock++;
				setTimeout(IPageManager.modalUnlock, options.minLockTime);
			}
		},

		modalUnlock: function(onClick, unlock) {
			if (!my.modalElement)
				return;
			if (onClick)
				my.modalElement.unbind('mousedown', onClick);
			my.modalElement.lock--;
			if (my.modalElement.lock <= 0 || unlock) {
				my.modalElement.stop().fadeTo('fast', 0.0).hide().unbind('mousedown');
				my.modalElement.lock = 0;
			}
		}

	});

	// Fire the layout whenever the window is resized
	$(window).resize(function() {
		setTimeout(function() { $PageManager.raiseEvent('layout', { windowResize: true }); }, 100);
	});

	// Fire the init/load/ready once the document is ready
	$(document).ready(function() {

		// <<< Process Page Data >>>
		// First look for server side data
		$(".__pagedata__").each(function(index, dom) {
			var value = $(this).attr("fusion:value");
			if (value) {
				var data = $.decrypt(value);
				$.extend(my.pageData, data);
			}
		});
		// Now parse the query string
		var s = window.location.search.substring(1);
		var parts = s.split('&');
		for (var i = 0; i < parts.length; i++) {
			var bits = parts[i].split('=');
			// TODO: Check bits[0] is a valid identifier - if not ignore it
			bits[0] = unescape(bits[0]);
			bits[1] = unescape(bits[1]);
			if (bits[0] == "$") {
				var data = $.decrypt(bits[1]);
				$.extend(my.pageData, data);
			} else {
				my.pageData[unescape(bits[0])] = unescape(bits[1]);
			}
		}

		// Initialise Widgets
		$(".fui-menu").menu({});
		$(".fui-section").section();
		$(".fui-splitter").splitter();

		my.$tabs = $(".fui-tab").tab();
		my.$panels = $(".fui-panel").panel();
		my.$fields = $(".fui-field").field();

		IPageManager.raiseEvent('init');
		IPageManager.raiseEvent('load');
		IPageManager.raiseEvent('ready');

		// Unlock the modal splash screen and layout
		$.modalUnlock();
		IPageManager.raiseEvent('layout', { windowResize: false });

	});

	return IPageManager;
};

Fsn.DataSource = function(settings, my) {
	my = my || {};
	settings = settings || {};
	var IDataSource = Fsn.Component(settings, my);

	// <<< Internal Properties >>>

	$.extend(my, {
		name: settings.name,
		entity: settings.entity || settings.name,
		keyField: settings.keyField || "Id",
		item: null,
		// <<< Foreign Key >>>
		foreignKeyField: settings.foreignKeyField || null,
		foreignKeyValue: settings.foreignKeyValue || null,
		foreignKeySource: settings.foreignKeySource || null,
		// <<< Page Properties >>>
		page: null,
		pageSize: settings.pageSize || 10,
		pageIndex: -1,
		itemCount: 0,
		// <<< Arbitrary Finq node sources for SelectPage >>>
		finqSources: settings.finqSources || [],
		// <<< Web Methods >>>
		selectItemWebMethod: settings.selectItemWebMethod || "Select",
		updateItemWebMethod: settings.updateItemWebMethod || "Update",
		insertItemWebMethod: settings.insertItemWebMethod || "Insert",
		deleteItemWebMethod: settings.deleteItemWebMethod || "Delete",
		// <<< Projection Properties >>>
		projection: null, 			// If false projection is loading, otherwise this is the projection
		projectionCallbacks: [], // Array of callbacks registered to receive this projection
		// << Configuration >>>
		selectItemFromPage: settings.selectItemFromPage !== false,
		deleteConfirmation: settings.deleteConfirmation || "Are you sure you want to delete this item?"
	});
	// <<< Accessor >>>
	my.addGetters("item;page;itemCount;keyField");
	my.addSetters("foreignKeyValue;pageSize;pageIndex;");
	// <<< Item Pre-CRUD event >>>
	my.addEvents("selectingItem;updatingItem;insertingItem;deletingItem;");
	// <<< Item Post-CRUD event >>>
	my.addEvents("selectedItem;updatedItem;insertedItem;deletedItem;");
	// <<< Page Selection >>>
	my.addEvents("selectingPage;selectedPage;");
	// <<< Projection Selection >>>
	my.addEvents("selectingProjection;selectedProjection;");
	// <<< Refresh >>>
	my.addEvents("refreshedItem;refreshedPage;");

	// <<< Internal Methods >>>

	var prefixGroup = function(group) {
		if (group && group.indexOf(':') > -1)
			return group;
		return my.name + (group ? ":" + group : "");
	};

	var refreshItem = function(group) {
		group = prefixGroup(group);
		$("." + my.name + "-show")[my.item ? "show" : "hide"]();
		$("." + my.name + "-hide")[my.item ? "hide" : "show"]();
		$PageManager.$fields().field("setValues", my.item, group, true);
		IDataSource.raiseEvent('refreshedItem', { group: group, item: my.item });
	};

	var setItem = function(item, group) {
		my.item = $.fixAjaxObject(item || null);
		IDataSource.raiseEvent('propertyChanged', { property: 'item' });
		refreshItem(group);
	};

	var refreshPage = function() {
		IDataSource.raiseEvent('refreshedPage', { page: my.page, pageSize: my.pageSize, pageIndex: my.pageIndex, itemCount: my.itemCount });
	};

	var setPage = function(page, itemCount) {
		if (itemCount !== undefined)
			my.itemCount = itemCount;
		my.page = page;
		if (my.pageIndex <= 0)
			IDataSource.set('pageIndex', 0);
		else if (my.pageIndex >= Math.ceil(my.itemCount / my.pageSize))
			IDataSource.set('pageIndex', Math.max(Math.ceil(my.itemCount / my.pageSize) - 1, 0));
		IDataSource.raiseEvent('propertyChanged', { property: 'page' });
		refreshPage();
	};

	var setProjection = function(name, projection) {
		my.projection = projection;
	};

	var isItemKey = function(key) {
		if (!my.keyField)
			return false;
		if ((key === null || key === undefined) && my.item === null)
			return true;
		if (my.item !== null && key === my.item[my.keyField])
			return true;
		return false;
	};

	var isItem = function(item) {
		if (!my.keyField)
			return false;
		var key;
		if (!item || typeof (item) != 'object') {
			key = item;
		} else {
			key = item[my.keyField];
		}
		return isItemKey(key);
	};

	var resolveFinqSources = function(finqNodes) {
		finqNodes = finqNodes || [];
		for (var i = 0; i < my.finqSources.length; i++) {
			var nodes = my.finqSources[i]();
			if (nodes) {
				for (var j = 0; j < nodes.length; j++) {
					finqNodes.push(nodes[j]);
				}
			}
		}
		return finqNodes;
	};

	// <<< Public Interface >>>

	$.extend(IDataSource, {

		// <<< Methods >>>

		refreshItem: function(group) {
			refreshItem(group);
		},

		isItemKey: function(key) {
			return isItemKey(key);
		},

		isItem: function(item) {
			return isItem(item);
		},

		addFinqSource: function(source) {
			my.finqSources.push(source);
		},

		// <<< Item Select/Update/Insert/Delete >>>

		refreshItem: function(group) {
			refreshItem(group);
		},

		selectPageIndex: function(index) {
			if (!my.page || index < 0 || index >= my.page.length)
				IDataSource.selectItem(null);
			else
				IDataSource.selectItem(my.page[index]);
		},

		// null = select null, undefined = reselect
		selectItem: function(key, force) {
			if (typeof (key) == 'object') {
				setItem(key);
				IDataSource.raiseEvent('selectedItem', { item: my.item });
				return IDataSource;
			}
			if (key !== null && key !== undefined && force !== true && isItemKey(key)) {
				return IDataSource;
			}
			if (key === null) {
				setItem(null);
				IDataSource.raiseEvent('selectedItem', { item: my.item });
			} else {
				if (key === undefined) {
					if (my.item == null)
						return IDataSource;
					key = my.item[my.keyField];
				}
				if (my.selectItemFromPage && my.page) {
					for (var i = 0; i < my.page.length; i++) {
						if (my.page[i][my.keyField] === key) {
							setItem(my.page[i]);
							IDataSource.raiseEvent('selectedItem', { item: my.item });
							return IDataSource;
						}
					}
				}
				var params = {};
				params[my.keyField] = key;
				$PageManager.invoke(my.entity, my.selectItemWebMethod, params, "loading", function(xhr) {
					setItem(xhr.d);
					IDataSource.raiseEvent('selectedItem', { item: my.item });
				});
			}
			return IDataSource;
		},

		updateItem: function(group, webMethod) {
			group = prefixGroup(group);
			var values = $.validateFields(group, my.item);
			if (!values)
				return IDataSource;
			// Raise the preview event
			if (IDataSource.raiseEvent('updatingItem', { group: group, item: my.item, newItem: values }) === false)
				return;
			if (!webMethod) webMethod = my.updateItemWebMethod;
			$PageManager.invoke(my.entity, webMethod, values, "saving", function(xhr) {
				var oldItem = my.item;
				setItem(xhr.d, group);
				IDataSource.raiseEvent('updatedItem', { group: group, item: my.item, oldItem: oldItem });
				// If we have a page selected we need to reselect it (TODO: see if we can update the page directly?)
				if (my.page)
					IDataSource.selectPage();
			});
			return IDataSource;
		},

		insertItem: function(group) {
			group = prefixGroup(group);
			var values = $.validateFields(group);
			if (!values)
				return IDataSource;

			// Foreign Key
			if (my.foreignKeyField) {
				if (my.foreignKeyValue !== null)
					values[my.foreignKeyField] = my.foreignKeyValue;
				// ELSE NO INSERT??
			}
			// Raise the preview event
			if (IDataSource.raiseEvent('insertingItem', { item: values }) === false)
				return;
			// Clear the values from the insert fields (TODO: Make this a configuration option?)
			$PageManager.$fields().field("setValues", {}, group, true)
			$PageManager.invoke(my.entity, my.insertItemWebMethod, values, "saving", function(xhr) {
				setItem(xhr.d);
				IDataSource.raiseEvent('insertedItem', { item: my.item });
				// If we have a page selected we need to reselect it (TODO: see if we can update the page directly?)
				if (my.page)
					IDataSource.selectPage();
			});
			return IDataSource;
		},

		deleteItem: function(key) {
			// Raise the preview event
			if (IDataSource.raiseEvent('deletingItem', { key: key }) === false)
				return;
			if (!confirm(my.deleteConfirmation))
				return IDataSource;
			var values = {};
			values[my.keyField] = key;
			$PageManager.invoke(my.entity, my.deleteItemWebMethod, values, "processing", function(xhr) {
				if (isItemKey(key))
					setItem(null);
				IDataSource.raiseEvent('deletedItem', { key: key });
				// If we have a page selected we need to reselect it (TODO: see if we can update the page directly?)
				if (my.page)
					IDataSource.selectPage();
			});
			return IDataSource;
		},

		// <<< Select Page >>>

		selectPage: function(pageIndex, callback, finqNodes) {
			// Resolve externally provided finq nodes
			finqNodes = resolveFinqSources(finqNodes);
			// Check for null foreign keys - as these imply no data, so no need for a call
			for (var i = 0; i < finqNodes.length; i++) {
				if (finqNodes[i].NodeType == $.finqNodeTypes.ForeignKey && finqNodes[i].Term2 == null) {
					setPage(null, 0);
					if (callback) callback(IDataSource, {});
					return IDataSource;
				}
			}
			// Page
			if (isFinite(pageIndex)) {
				my.pageIndex = pageIndex;
				if (my.pageIndex <= -2) {
					// -2 => Clear the page
					setPage(null, 0);
					if (callback) callback(IDataSource, {});
					return IDataSource;
				} else if (my.pageIndex == -1 || my.pageIndex >= Math.ceil(my.itemCount / my.pageSize)) {
					my.pageIndex = Math.max(Math.ceil(my.itemCount / my.pageSize) - 1, 0);
				}
			} else if (my.pageIndex == -1) {
				// No page has been loaded, so don't load one now - just ignore this.
				return IDataSource;
			}
			finqNodes.push($.finqNode(my.pageIndex * my.pageSize, my.pageSize, $.finqNodeTypes.Page));

			// Foreign Key
			if (my.foreignKeyField) {
				if (my.foreignKeyValue !== null)
					finqNodes.push($.finqNode(my.foreignKeyField, my.foreignKeyValue, $.finqNodeTypes.ForeignKey));
				else {
					// If a foreign key value is null then we have no data
					setPage(null, 0);
					if (callback) callback(IDataSource, {});
					return IDataSource;
				}
			}

			// TODO: Align client and server side naming conventions here
			$PageManager.invoke(my.entity, "SelectGrid", { finqNodes: finqNodes }, "loading", function(xhr) {
				setPage(xhr.d.DataSource, xhr.d.TotalRecords);
			});
			if (callback) callback(IDataSource, {});
			return IDataSource;
		},

		selectNextPage: function() {
			return IDataSource.selectPage(my.pageIndex + 1);
		},

		selectPrevPage: function() {
			return IDataSource.selectPage(my.pageIndex - 1);
		},

		// <<< Select Node >>>

		selectNode: function(id, callback) {
			$PageManager.invoke(my.entity, "SelectNode", { "Id": id }, "loading", function(xhr) {
				if (callback) {
					callback(IDataSource, { nodes: xhr.d });
				}
			});
		},

		// <<< Select Projection >>>

		// TODO: Named Projections with Custom Parameters (for the DB!!)
		// TODO: Make sure Fields respond to the event multiple times so we can reload projections

		selectProjection: function(name, callback, force) {

			if (callback)
				my.projectionCallbacks.push(callback);

			if (my.projection === null || ((my.projection !== false) && force)) {
				// Projection isn't loaded,  or we are forcing a reload so start a request
				my.projection = false;
				$PageManager.invoke(my.entity, "SelectProjection", {}, "loading", function(xhr) {
					setProjection(name, xhr.d);
					// Execute all callbacks
					for (var i in my.projectionCallbacks) {
						my.projectionCallbacks[i](IDataSource, { name: name, projection: my.projection });
					}
					IDataSource.raiseEvent("selectedProjection", { name: name, projection: xhr.d });
				});
			} else if (my.projection === false) {
				// Projection is in the process of loading so nothing to do
			} else {
				// Projection is already loaded so just call the callback
				if (callback)
					callback(IDataSource, { name: name, projection: my.projection });
			}
			return IDataSource;
		}
	});

	// If we have a foreign key source then bind to it
	if (my.foreignKeySource) {
		var parts = my.foreignKeySource.split('.');
		var dataSource = parts[0];
		var keyField = parts.length == 2 ? parts[1] : "Id";
		$PageManager.getDataSource(dataSource).addEventHandler('refreshedItem', function(sender, eventArgs) {
			if (eventArgs.item) {
				IDataSource.set('foreignKeyValue', eventArgs.item[keyField]);
				IDataSource.selectPage(0).selectItem(null);
			}
		});
	}

	return IDataSource;
};

