// ==Builder==
// @required
// ==/Builder==


if (SSInclude != undefined) SSLog('Including ../mootools/MTMods.js...', SSInclude);

// Start ../mootools/MTMods.js ------------------------------------------------

// ==Builder==
// @required
// ==/Builder==

// allows for queries on namespaced attributes
Selectors.RegExps = {
	id: (/#([\w-]+)/),
	tag: (/^(\w+|\*)/),
	quick: (/^(\w+|\*)$/),
	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
	combined: (/\.([\w-]+)|\[([\w:]+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
};

String.implement({
  repeat: function(times) {
    var result = "";
    for(var i = 0; i < times; i++) {
      result += this;
    }
    return result;
  }
});

Array.implement({
  copy: function(){
    var results = [];
    for(var i = 0, l = this.length; i < l; i++) results[i] = this[i];
    return results;
  }
});

var IFrame = new Native({

  name: 'IFrame',

  generics: false,

  initialize: function(){
    var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
    var props = params.properties || {};
    var iframe = $(params.iframe) || false;
    var onload = props.onload || $empty;
    delete props.onload;
    props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time());
    iframe = new Element(iframe || 'iframe', props);
    var onFrameLoad = function(){
      var host = $try(function(){
        return iframe.contentWindow.location.host;
      });
      if ((host && host == window.location.host) || !host){ // CHANGE: so that frames with no host work - David
        var win = new Window(iframe.contentWindow);
        var doc = new Document(iframe.contentWindow.document);
        if(!win.Element.prototype) win.Element.prototype = {}; // CHANGE: fix for GM and MT1.2 IFrames - David
        $extend(win.Element.prototype, Element.Prototype);
      }
      onload.call(iframe.contentWindow, iframe.contentWindow.document);
    };
    (!window.frames[props.id]) ? iframe.addListener('load', onFrameLoad) : onFrameLoad();
    return iframe;
  }

});

Selectors.Utils.genId = function(self){
  var id = self.getProperty('id');
  if(!id){
    id = 'genId'+Math.round(Math.random()*1000000+(new Date()).getMilliseconds());
    self.setProperty('id', id);
  }
  return id;
};

Selectors.Utils.search = function(self, expression, local){
  var splitters = [];
  
  
  var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
    splitters.push(m1);
    return ':)' + m2;
  }).split(':)');

  // allows .getElement('> selector') and .getElements('> selector')
  selectors = selectors.filter(function(selector) { return (selector != '');});
  
  
  if(splitters.length == selectors.length){
    return self.getWindow().$$('#'+this.genId(self)+' '+expression);
  }

  var items, match, filtered, item;

  for (var i = 0, l = selectors.length; i < l; i++)
  {

    var selector = selectors[i];

    if (i == 0 && Selectors.RegExps.quick.test(selector))
    {
      items = self.getElementsByTagName(selector);
      continue;
    }

    var splitter = splitters[i - 1];

    var tagid = Selectors.Utils.parseTagAndID(selector);
    var tag = tagid[0], id = tagid[1];

    if (i == 0){
      items = Selectors.Utils.getByTagAndID(self, tag, id);
    } else {
      var uniques = {}, found = [];
      for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
      items = found;
    }

    var parsed = Selectors.Utils.parseSelector(selector);

    if (parsed)
    {
      filtered = [];
      for (var m = 0, n = items.length; m < n; m++)
      {
        item = items[m];
        if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
      }
      items = filtered;
    }
  }
  return items;
};

MooTools.More = {
	'version': 'rc01'
};

/*
Script: Request.JSONP.js
	Defines Request.JSONP, a class for cross domain javascript via script injection.

	License:
		MIT-style license.

	Authors:
		Aaron Newton
*/

Request.JSONP = new Class({

	Implements: [Chain, Events, Options],

	options: {/*
		onRetry: $empty(intRetries),
		onRequest: $empty(scriptElement),
		onComplete: $empty(data),
		onSuccess: $empty(data),
		onCancel: $empty(),*/
		url: '',
		data: {},
		retries: 0,
		timeout: 0,
		link: 'ignore',
		callBackKey: 'callback',
		injectScript: document.head
	},

	initialize: function(options){
		this.setOptions(options);
		this.running = false;
		this.requests = 0;
		this.triesRemaining = [];
	},
	
	check: function(caller){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false;
		}
		return false;
	},

	send: function(options){
		if (!$chk(arguments[1]) && !this.check(arguments.callee, options)) return this;
		
		var type = $type(options), old = this.options, index = $chk(arguments[1]) ? arguments[1] : this.requests++;
		if (type == 'string' || type == 'element') options = {data: options};
		
		options = $extend({data: old.data, url: old.url}, options);
		
		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
		var remaining = this.triesRemaining[index];
				
		(function(){
			var script = this.getScript(options);
			MooTools.log('JSONP retrieving script with url: ' + script.src);
			this.fireEvent('request', script);
			this.running = true;
			
			(function(){
				if (remaining){
					this.triesRemaining[index] = remaining - 1;
					if (script){
						script.destroy();
						this.request(options, index);
						this.fireEvent('retry', this.triesRemaining[index]);
					}
				} else if(script && this.options.timeout){
					script.destroy();
					this.cancel();
				}					
			}).delay(this.options.timeout, this);
		}).delay(Browser.Engine.trident ? 50 : 0, this);
		return this;
	},
	
	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.fireEvent('cancel');
		return this;
	},
 	
	getScript: function(options){
		var options = this.options, index = Request.JSONP.counter, data;
		Request.JSONP.counter++;
		
		switch ($type(options.data)){
			case 'element': data = $(options.data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(options.data);
		}
		
		var script = new Element('script', {
			type: 'text/javascript',
			src: options.url + 
				 (options.url.test('\\?') ? '&' :'?') + 
				 (options.callBackKey || this.options.callBackKey) + 
				 "=Request.JSONP.request_map.req_"+ index + 
				 (data ? '&' + data : '')
		}).inject(this.options.injectScript);
		
		var callback = function(data){ this.success(data, script); }.bind(this);
		Request.JSONP.request_map['req_' + index] = callback;
				
		return script;
	},
	
	success: function(data, script){
		if (script) script.destroy();
		this.running = false;
		MooTools.log('JSONP successfully retrieved: ',  data);
		this.fireEvent('complete', data).fireEvent('success', data).callChain();
	}

});

Request.JSONP.counter = 0;
Request.JSONP.request_map = {};

$extend(MooTools, {
	logged: [],
	log: function(){
		MooTools.logged.push(arguments);
	}
});

// End ../mootools/MTMods.js --------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MTMods');

if (SSInclude != undefined) SSLog('Including ../client/SSLog.js...', SSInclude);

// Start ../client/SSLog.js ---------------------------------------------------

// ==Builder==
// @required
// @name              SSLog
// @package           System_
// @dependencies      GreaseMonkeyApi
// ==/Builder==

// Internal Error Logging, trust me, you don't need this - kisses ShiftSpace Core Robot
var SSNoLogging = 0;

var SSLogMessage        = 1,
    SSLogError          = 1 << 1,
    SSLogWarning        = 1 << 2,
    SSLogPlugin         = 1 << 3,
    SSLogServerCall     = 1 << 4,
    SSLogSpaceError     = 1 << 5,
    SSLogShift          = 1 << 6,
    SSLogSpace          = 1 << 7,
    SSLogViews          = 1 << 8,
    SSLogSandalphon     = 1 << 9,
    SSLogForce          = 1 << 10,
    SSInclude           = 1 << 11,
    SSLogViewSystem     = 1 << 12;
    SSLogSystem         = 1 << 13;

var SSLogAll = 0xfffffff;           // All bits on (if we have at most 32 types)
var __ssloglevel__ = SSNoLogging;

/*
Function: log
  Logs a message to the console, but only in debug mode or when reporting
  errors.

Parameters:
  msg - The message to be logged in the JavaScript console.
  verbose - Force the message to be logged when not in debug mode.
*/
function SSLog(msg, type) 
{
  if (typeof console == 'object' && SSLog) 
  {
    var messageType = '';

    if(type == SSLogError)
    {
      messageType = 'ERROR: ';
    }
    
    if(type == SSLogWarning)
    {
      messageType = 'WARNING: ';
    }
    
    if(__ssloglevel__ == SSLogAll || (type && (__ssloglevel__ & type)) || type == SSLogForce)
    {
      if($type(msg) != 'string')
      {
        console.log(msg);
      }
      else
      {
        console.log(messageType + msg);
      }
    }
  } 
  else if (typeof GM_log != 'undefined') 
  {
    GM_log(msg);
  } 
  else 
  {
    setTimeout(function() {
      throw(msg);
    }, 0);
  }
}

function SSSetLogLevel(level)
{
  SSLog('SSSetLogLevel ' + level);
  __ssloglevel__ = level;
}

if(typeof SSLogError != 'undefined')
{
  SSSetLogLevel(SSLogError);
}
else
{
  throw new Error("Bailing: No such logging level SSLogError, please fix the config/env/-e.json file.");
}


// End ../client/SSLog.js -----------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSLog');

if (SSInclude != undefined) SSLog('Including ../client/Base.js...', SSInclude);

// Start ../client/Base.js ----------------------------------------------------

// ==Builder==
// @required
// ==/Builder==

// any environment specific vars
var ShiftSpaceSandBoxMode = true;

// two most important vars
var server = '/social/shiftspace/';

// new additions for Sandalphon
ShiftSpaceUI = {}; // holds all UI class objects
ShiftSpaceObjects = new Hash(); // holds all instantiated UI objects
ShiftSpaceNameTable = new Hash(); // holds all instantiated UI object by CSS id

var __sys__ = {
    "exports": {
        "RangeCoder": "RangeCoder", 
        "SSElement": "Element", 
        "SSIframe": "Iframe", 
        "SSInput": "Input", 
        "ShiftSpacePin": "Pin", 
        "ShiftSpacePinWidget": "PinWidget", 
        "ShiftSpacePlugin": "Plugin", 
        "ShiftSpaceShift": "Shift", 
        "ShiftSpaceSpace": "Space", 
        "ShiftSpaceUser": "User"
    }, 
    "files": {
        "ActionMenu": {
            "customView": true, 
            "dependencies": [
                "SSView"
            ], 
            "name": "ActionMenu", 
            "optional": true, 
            "package": "ShiftSpaceUI", 
            "path": "..\/client\/customViews\/ActionMenu\/ActionMenu.js", 
            "uiclass": true
        }, 
        "Base": {
            "path": "..\/client\/Base.js", 
            "required": true
        }, 
        "Bootstrap": {
            "path": "..\/client\/Bootstrap.js", 
            "required": true
        }, 
        "BootstrapMoMA": {
            "path": "..\/momasocialbar\/BootstrapMoMA.js", 
            "required": true
        }, 
        "BootstrapSandalphon": {
            "path": "..\/sandalphon\/BootstrapSandalphon.js", 
            "required": true
        }, 
        "EventProxy": {
            "name": "EventProxy", 
            "package": "System", 
            "path": "..\/client\/EventProxy.js", 
            "required": true
        }, 
        "GreaseMonkeyApi": {
            "name": "GreaseMonkeyApi", 
            "optional": true, 
            "package": "System_", 
            "path": "..\/sandbox\/GreaseMonkeyApi.js"
        }, 
        "LocalizedStringsSupport": {
            "name": "LocalizedStringsSupport", 
            "package": "Internationalization", 
            "path": "..\/client\/LocalizedStringsSupport.js", 
            "required": true
        }, 
        "MTMods": {
            "path": "..\/mootools\/MTMods.js", 
            "required": true
        }, 
        "MoMAAllSetsCell": {
            "dependencies": [
                "SSCell"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAMySetsView\/MoMAAllSetsCell.js", 
            "uiclass": true
        }, 
        "MoMABookmarksCell": {
            "dependencies": [
                "SSCell"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMABookmarksView\/MoMABookmarksCell.js", 
            "uiclass": true
        }, 
        "MoMABookmarksView": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMABookmarksView\/MoMABookmarksView.js", 
            "uiclass": true
        }, 
        "MoMAChooseWorkCell": {
            "dependencies": [
                "SSCell"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAEditSetView\/MoMAChooseWorkCell.js", 
            "uiclass": true
        }, 
        "MoMAConsole": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAConsole\/MoMAConsole.js", 
            "uiclass": true
        }, 
        "MoMAEditSetView": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAEditSetView\/MoMAEditSetView.js", 
            "uiclass": true
        }, 
        "MoMAEditSetsWorksCell": {
            "dependencies": [
                "SSCell"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAEditSetView\/MoMAEditSetsWorksCell.js", 
            "uiclass": true
        }, 
        "MoMAMySetsView": {
            "dependencies": [
                "SSMultiView"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAMySetsView\/MoMAMySetsView.js", 
            "uiclass": true
        }, 
        "MoMAWorksCell": {
            "dependencies": [
                "SSCell"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAWorksView\/MoMAWorksCell.js", 
            "uiclass": true
        }, 
        "MoMAWorksView": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "MoMASocialBarUI", 
            "path": "..\/momasocialbar\/views\/MoMAWorksView\/MoMAWorksView.js", 
            "uiclass": true
        }, 
        "NewTrail": {
            "dependencies": [
                "Plugin"
            ], 
            "name": "TrailsPlugin", 
            "optional": true, 
            "package": "Trails", 
            "path": "..\/plugins\/Trails\/NewTrail.js"
        }, 
        "PersistenceFunctions": {
            "name": "PersistenceFunctions", 
            "optional": true, 
            "package": "Core", 
            "path": "..\/client\/core\/PersistenceFunctions.js"
        }, 
        "Pin": {
            "export": [
                "ShiftSpacePin as Pin"
            ], 
            "name": "Pin", 
            "optional": true, 
            "package": "Pinning", 
            "path": "..\/client\/Pinning\/Pin.js"
        }, 
        "PinHelpers": {
            "dependencies": [
                "Pin"
            ], 
            "name": "PinHelpers", 
            "optional": true, 
            "package": "Pinning", 
            "path": "..\/client\/Pinning\/PinHelpers.js"
        }, 
        "PinWidget": {
            "dependencies": [
                "PinHelpers", 
                "ShiftSpaceElement"
            ], 
            "export": [
                "ShiftSpacePinWidget as PinWidget"
            ], 
            "name": "PinWidget", 
            "optional": true, 
            "package": "Pinning", 
            "path": "..\/client\/Pinning\/PinWidget.js"
        }, 
        "PostInitDeclarations": {
            "name": "PostInitDeclarations", 
            "optional": true, 
            "path": "..\/client\/core\/PostInitDeclarations.js"
        }, 
        "PreInitDeclarations": {
            "optional": true, 
            "path": "..\/client\/core\/PreInitDeclarations.js"
        }, 
        "RangeCoder": {
            "export": [
                "RangeCoder"
            ], 
            "name": "RangeCoder", 
            "optional": true, 
            "package": "Pinning", 
            "path": "..\/client\/Pinning\/RangeCoder.js"
        }, 
        "RemoteFunctions": {
            "name": "RemoteFunctions", 
            "optional": true, 
            "package": "Core", 
            "path": "..\/client\/core\/RemoteFunctions.js"
        }, 
        "SSAnotherDefaultTest": {
            "path": "..\/tests\/SSAnotherDefaultTest.js", 
            "test": true
        }, 
        "SSCell": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSCell\/SSCell.js", 
            "uiclass": true
        }, 
        "SSCellTest": {
            "dependencies": [
                "SSCellTestCell"
            ], 
            "path": "..\/tests\/SSCellTest\/SSCellTest.js", 
            "suite": "UI", 
            "test": true
        }, 
        "SSCellTestCell": {
            "path": "..\/tests\/SSCellTest\/SSCellTestCell.js", 
            "test": true, 
            "uiclass": true
        }, 
        "SSCollection": {
            "optional": true, 
            "package": "ShiftSpaceCore", 
            "path": "..\/client\/SSCollection.js"
        }, 
        "SSCollectionTest": {
            "path": "..\/tests\/SSCollectionTest\/SSCollectionTest.js", 
            "suite": "Core", 
            "test": true
        }, 
        "SSConsole": {
            "optional": true, 
            "package": "ShiftSpaceUI", 
            "path": "..\/client\/views\/SSConsole\/SSConsole.js", 
            "uiclass": true
        }, 
        "SSCustomExceptions": {
            "dependencies": [
                "SSException"
            ], 
            "name": "SSCustomExceptions", 
            "optional": true, 
            "package": "System", 
            "path": "..\/client\/SSCustomExceptions.js"
        }, 
        "SSDefaultTest": {
            "name": "SSDefaultTest", 
            "path": "..\/tests\/SSDefaultTest.js", 
            "suite": "SSDefaultTestSuite", 
            "test": true
        }, 
        "SSDefaultTestSuite": {
            "dependencies": [
                "SSDefaultTest"
            ], 
            "name": "SSDefaultTestSuite", 
            "path": "..\/tests\/SSDefaultTestSuite.js", 
            "suite": "SSDefaultTestSuperSuite", 
            "test": true
        }, 
        "SSDefaultTestSuperSuite": {
            "dependencies": [
                "SSDefaultTestSuite", 
                "SSAnotherDefaultTest"
            ], 
            "name": "SSDefaultTestSuperSuite", 
            "path": "..\/tests\/SSDefaultTestSuperSuite.js", 
            "test": true
        }, 
        "SSException": {
            "name": "SSException", 
            "optional": true, 
            "package": "System", 
            "path": "..\/client\/SSException.js"
        }, 
        "SSForm": {
            "dependencies": [
                "SSView"
            ], 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSForm\/SSForm.js", 
            "required": true, 
            "uiclass": true
        }, 
        "SSFormTest": {
            "path": "..\/tests\/SSFormTest\/SSFormTest.js", 
            "suite": "UI", 
            "test": true
        }, 
        "SSListView": {
            "dependencies": [
                "SSView", 
                "SSCell"
            ], 
            "name": "SSListView", 
            "optional": true, 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSListView\/SSListView.js", 
            "uiclass": true
        }, 
        "SSListViewCell": {
            "dependencies": [
                "SSView"
            ], 
            "optional": true, 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSListViewCell\/SSListViewCell.js", 
            "uiclass": true
        }, 
        "SSListViewTest": {
            "dependencies": [
                "SSListViewTestCell"
            ], 
            "path": "..\/tests\/SSListViewTest\/SSListViewTest.js", 
            "suite": "UI", 
            "test": true
        }, 
        "SSListViewTestCell": {
            "path": "..\/tests\/SSListViewTest\/SSListViewTestCell.js", 
            "test": true, 
            "uiclass": true
        }, 
        "SSLog": {
            "dependencies": [
                "GreaseMonkeyApi"
            ], 
            "name": "SSLog", 
            "package": "System_", 
            "path": "..\/client\/SSLog.js", 
            "required": true
        }, 
        "SSMultiView": {
            "dependencies": [
                "SSView"
            ], 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSMultiView\/SSMultiView.js", 
            "required": true, 
            "uiclass": true
        }, 
        "SSMultiViewTest": {
            "path": "..\/tests\/SSMultiViewTest\/SSMultiViewTest.js", 
            "suite": "UI", 
            "test": true
        }, 
        "SSPageControl": {
            "optional": true, 
            "package": "ShiftSpaceCore", 
            "path": "..\/client\/SSPageControl.js"
        }, 
        "SSTabView": {
            "dependencies": [
                "SSView"
            ], 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/views\/SSTabView\/SSTabView.js", 
            "required": true, 
            "uiclass": true
        }, 
        "SSUITestSuite": {
            "dependencies": [
                "SSMultiViewTest", 
                "SSCellTest", 
                "SSListViewTest"
            ], 
            "path": "..\/tests\/SSUITestSuite.js", 
            "test": true
        }, 
        "SSUnitTest": {
            "dependencies": [
                "SSException"
            ], 
            "name": "SSUnitTest", 
            "optional": true, 
            "package": "Testing", 
            "path": "..\/client\/SSUnitTest.js"
        }, 
        "SSView": {
            "dependencies": [
                "SandalphonCore"
            ], 
            "package": "ShiftSpaceCoreUI", 
            "path": "..\/client\/SSView.js", 
            "required": true, 
            "uiclass": true
        }, 
        "SSViewProxy": {
            "dependencies": [
                "SandalphonSupport"
            ], 
            "name": "SSViewProxy", 
            "package": "System", 
            "path": "..\/client\/SSViewProxy.js", 
            "required": true
        }, 
        "SandalphonCore": {
            "package": "System", 
            "path": "..\/sandalphon\/SandalphonCore.js", 
            "required": true
        }, 
        "SandalphonSupport": {
            "dependencies": [
                "SandalphonCore"
            ], 
            "name": "SandalphonSupport", 
            "package": "System", 
            "path": "..\/client\/SandalphonSupport.js", 
            "required": true
        }, 
        "ShiftMenu": {
            "dependencies": [
                "ShiftSpaceElement", 
                "EventProxy"
            ], 
            "name": "ShiftMenu", 
            "package": "ShiftSpaceUI", 
            "path": "..\/client\/ShiftMenu.js", 
            "required": true
        }, 
        "ShiftSpaceElement": {
            "export": [
                "SSElement as Element", 
                "SSIframe as Iframe", 
                "SSInput as Input"
            ], 
            "name": "ShiftSpaceElement", 
            "package": "System", 
            "path": "..\/client\/ShiftSpaceElement.js", 
            "required": true
        }, 
        "SomeFile1": {
            "dependencies": [
                "SomeFile3"
            ], 
            "package": "TestTesting", 
            "path": "..\/client\/testTesting\/SomeFile1.js", 
            "required": true
        }, 
        "SomeFile2": {
            "dependencies": [
                "SomeFile3"
            ], 
            "package": "TestTesting", 
            "path": "..\/client\/testTesting\/SomeFile2.js", 
            "required": true
        }, 
        "SomeFile3": {
            "package": "TestTesting", 
            "path": "..\/client\/testTesting\/SomeFile3.js", 
            "required": true
        }, 
        "TestTesting": {
            "dependencies": [
                "SomeFile1", 
                "SomeFile2"
            ], 
            "path": "..\/tests\/TestTesting.js", 
            "suite": "TestTesting", 
            "test": true
        }, 
        "User": {
            "export": [
                "ShiftSpaceUser as User"
            ], 
            "name": "User", 
            "optional": true, 
            "package": "ShiftSpaceCore", 
            "path": "..\/client\/User.js"
        }, 
        "UserFunctions": {
            "name": "UserFunctions", 
            "optional": true, 
            "package": "Core", 
            "path": "..\/client\/core\/UserFunctions.js"
        }, 
        "UtilityFunctions": {
            "name": "UtilityFunctions", 
            "optional": true, 
            "package": "Core", 
            "path": "..\/client\/core\/UtilityFunctions.js"
        }
    }, 
    "packages": {
        "Core": [
            "PluginFunctions", 
            "SpaceFunctions", 
            "UserFunctions", 
            "UtilityFunctions", 
            "PersistenceFunctions", 
            "ShiftFunctions", 
            "RemoteFunctions"
        ], 
        "ErrorHandling": [
            "ErrorWindow"
        ], 
        "EventHandling": [
            "CoreEvents"
        ], 
        "Internationalization": [
            "LocalizedStringsSupport"
        ], 
        "MoMASocialBarUI": [
            "MoMAConsole", 
            "MoMAWorksCell", 
            "MoMAWorksView", 
            "MoMABookmarksCell", 
            "MoMABookmarksView", 
            "MoMAMySetsView", 
            "MoMAAllSetsCell", 
            "MoMAEditSetsWorksCell", 
            "MoMAEditSetView", 
            "MoMAChooseWorkCell"
        ], 
        "ShiftSpaceCore": [
            "User", 
            "SSPageControl", 
            "Plugin", 
            "Shift", 
            "Space", 
            "SSTableViewDatasource", 
            "SSCollection"
        ], 
        "ShiftSpaceCoreUI": [
            "SSView", 
            "SSForm", 
            "SSCell", 
            "SSListViewCell", 
            "SSMultiView", 
            "SSTabView", 
            "SSTableView", 
            "SSTableRow", 
            "SSCustomTableRow", 
            "SSEditableTextCell", 
            "SSListView"
        ], 
        "ShiftSpaceUI": [
            "ActionMenu", 
            "ShiftMenu", 
            "SSConsole"
        ], 
        "System": [
            "SandalphonCore", 
            "EventProxy", 
            "SSException", 
            "ShiftSpaceElement", 
            "SSCustomExceptions", 
            "SandalphonSupport", 
            "SSViewProxy"
        ], 
        "System_": [
            "GreaseMonkeyApi", 
            "SSLog"
        ], 
        "TestTesting": [
            "SomeFile3", 
            "SomeFile1", 
            "SomeFile2"
        ], 
        "Testing": [
            "SSUnitTest"
        ], 
        "Trails": [
            "RecentlyViewedHelpers", 
            "NewTrail"
        ], 
        "UtilitiesExtras": [
            "IframeHelpers", 
            "FullScreen"
        ]
    }
};
var __sysavail__ = {
  files: [],
  packages: []
};

var __membermemo__ = {};
function $memberof(_subclass, superclass)
{
 if(_subclass == superclass) return true;
  
  var subclass = ($type(_subclass) == 'object' && _subclass.name) || _subclass;
  var tag = subclass+':'+superclass;
  
  // check memo
  if(__membermemo__[tag] != null) 
  {
    return __membermemo__[tag];
  }
  
  // check deps
  var deps = __sys__.files[subclass].dependencies;
  if(deps == null || deps.length == 0) return false;
  var memberof = false;
  
  if(deps.contains(superclass))
  {
    // memoize
    __membermemo__[tag] = true;
    return true;
  }
  
  // each dep
  for(var i = 0, l = deps.length; i < l; i++)
  {
    if($memberof(deps[i], superclass)) return true;
  }
  
  // memoize
  __membermemo__[tag] = false;
  return false;
}

// End ../client/Base.js ------------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('Base');

// === START PACKAGE [System] ===

if(__sysavail__) __sysavail__.packages.push("System");

if (SSInclude != undefined) SSLog('Including ../sandalphon/SandalphonCore.js...', SSInclude);

// Start ../sandalphon/SandalphonCore.js --------------------------------------

// ==Builder==
// @required
// @package           System
// ==/Builder==

var SandalphonClass = new Class({

  initialize: function(attribute)
  {
    // for analyzing fragments of markup
    this.setFragment(new Element('div'));
    this.outletBindings = [];
    this.contextHash = $H();
  },
  
  
  reset: function()
  {
    this.outletBindings = [];
    this.contextHash = $H();
    SSClearControllersTable();
    SSClearObjects();
  },
  
  /*
    Function: _genId
      Generate an object id.  Used for debugging.  The instance is indentified by this in the global
      ShiftSpaceObjects hash.
  */
  _genContextId: function()
  {
    return ('ctxtid_'+(Math.round(Math.random()*1000000+(new Date()).getMilliseconds())));
  },
  
  
  getContextId: function(ctxt)
  {
    if(!ctxt.ssctxtid)
    {
      ctxt.ssctxtid = this._genContextId();
    }
    return ctxt.ssctxtid;
  },
  

  contextForId: function(id)
  {
    return this.contextHash.get(id).context;
  },
  
  
  internContext: function(ctxt)
  {
    var ctxtId = this.getContextId(ctxt);
    if(!this.contextHash.get(ctxtId))
    {
      // default isReady to true, because some contexts aren't activated
      this.contextHash.set(ctxtId, {isReady:true, context:ctxt});
    }
    return ctxtId;
  },
  
  
  contextIsReady: function(ctxt)
  {
    // intern the context just in case
    this.internContext(ctxt);
    return this.contextHash.get(this.getContextId(ctxt)).isReady;
  },
  
  
  setContextIsReady: function(ctxtid, val)
  {
    this.contextForId(ctxtid).isReady = val;
  },
  
  
  convertToFragment: function(markup, ctxt)
  {
    var context = ctxt || window;
    
    // generate the fragment in the context
    var fragment = context.$(context.document.createElement('div'));
    fragment.set('html', markup);
    
    // TODO: generalize to return markup that doesn't have a root node
    var markupFrag = $(fragment.getFirst().dispose());

    //console.log('convertToFragment');
    //console.log(markupFrag.getProperty('id'));
    
    // destroy the temporary fragment
    fragment.destroy();
    
    return markupFrag;
  },
  
  /*
    Function: fragment
      Returns the private fragment node.
    
    Returns:
      The fragment node.
  */
  fragment: function()
  {
    return this.__fragment__;
  },
  
  /*
    Function:
      Sets the private fragment node.
  */
  setFragment: function(frag)
  {
    this.__fragment__ = frag;
  },
  
  
  compileAndLoad: function(path, callback)
  {
    var request = new Request({
      url: "../sandalphon/compile_and_load.php",
      async: false,
      method: "get",
      data: { filepath: "../" + path + ".html" },
      onComplete: function(responseText, responseXml)
      {
        var escapedUI = JSON.decode(responseText);
        callback({interface:unescape(escapedUI.interface), styles:unescape(escapedUI.styles)});
      },
      onFailure: function(responseText, responseXml)
      {
        
      }
    });
    request.send();
  },
  

  /*
    Function: loadFile
      Loads an interface file from the speficied path.
    
    Parameters:
      path - a file path as string.  This path should be absolute from the root ShiftSpace directory.
  */
  load: function(path, callback)
  {
    var interface;
    var styles;
    
    SSLog("Sandalphon LOAD " + path, SSLogSandalphon);
    
    var server = (SSInfo && SSInfo().server) || '..';
    //console.log('load!');
    // load the interface file
    if(typeof SandalphonToolMode != 'undefined')
    {
      var interfaceCall = new Request({
        url: server+path+'.html',
        method: 'get',
        onComplete: function(responseText, responseXML)
        {
          SSLog("Sandalphon interface call complete");
          interface = responseText;
        }.bind(this),
        onFailure: function()
        {
          console.error('Oops could not load that interface file');
        }
      });
      
      var stylesCall = new Request({
        url:  server+path+'.css',
        method: 'get',
        onComplete: function(responseText, responseXML)
        {
          SSLog("Sandalphon styles call complete");
          styles = responseText;
        }.bind(this),
        onFailure: function()
        {
          // we don't care if the interface file is missing
          stylesCall.fireEvent('complete');
        }
      });
      
      // Group HTMl and CSS calls
      var loadGroup = new Group(interfaceCall, stylesCall);
      loadGroup.addEvent('complete', function() {
        SSLog('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Sandalphon interface load complete');
        if(callback) callback({interface:interface, styles:styles});
      });
      
      // fetch
      interfaceCall.send();
      stylesCall.send();
    }
    else
    {
      // just use loadFile if we're running in ShiftSpace
      SSLoadFile(path+'.html', function(rx) {
        interface = rx.responseText;
        SSLoadFile(path+'.css', function(rx) {
          styles = rx.responseText;
          if(callback) callback({interface:interface, styles:styles});
        });
      });
    }
  },
  
  
  addStyle: function(css, ctxt) 
  {
    var context = ctxt || window;
    var contextDoc = context.document;
    
    if (context.$$('head').length != 0) 
    {
      var head = context.$$('head')[0];
    } 
    else 
    {
      var head = context.$(contextDoc.createElement('head'));
      head.injectBefore($(contextDoc.body));
    }
    
    // Add some base styles
    /*
    css += "                          \
      .SSDisplayNone                  \
      {                               \
        display: none;                \
      }                               \
      .SSUserSelectNone               \
      {                               \
        -moz-user-select: none;       \
        user-select: none;            \
        -webkit-user-select: none;    \
      }                               \
    ";
    */
    
    if(!Browser.Engine.trident)
    {
      var style = context.$(contextDoc.createElement('style'));
      style.setProperty('type', 'text/css');
      style.appendText(css);
      style.injectInside(head);
    }
    else
    {
      var style = contextDoc.createStyleSheet();
      style.cssText = css;
    }
  },
  
  
  activate: function(ctxt)
  {
    var context = ctxt || window;
    
    // internalize this context
    var ctxtid = this.internContext(context);
    this.setContextIsReady(ctxtid, false);
    
    SSLog('>>>>>>>>>>>>>>>>>> activate', SSLogSandalphon);
    
    // First generate the outlet bindings
    this.generateOutletBindings(context);
    // First instantiate all controllers
    this.instantiateControllers(context);
    // Initialize all outlets
    this.bindOutlets(context);
    this.awakeObjects(context);
    
    // the context is ready now
    this.setContextIsReady(ctxtid, true);
  },
  
  
  associate: function(controller, interface)
  {
    controller.element = Sandalphon.convertToFragment(interface);
    SSSetControllerForNode(controller, controller.element);
    return controller.element;
  },
  
  
  /*
    Function: instantiateControllers
      Instantiate any backing JS view controllers for the interface.
  */
  instantiateControllers: function(ctxt)
  {
    SSLog('instantiateControllers', SSLogSandalphon);
    var context = ctxt || window;
    
    var views = this.contextQuery(context, '*[uiclass]');
    
    SSLog(views, SSLogSandalphon);  

    // instantiate all objects
    views.each(function(aView) {
      var theClass = aView.getProperty('uiclass');
      SSLog('=========================================');
      SSLog('instantiating ' + theClass, SSLogSandalphon);
      new ShiftSpaceUI[theClass](aView, {
        context: context
      });
      SSLog('instantation complete');
    });
    
    // notify all listeners
    SSLog('Notifying all listeners');
    views.each(SSNotifyInstantiationListeners);
  },
  
  
  contextQuery: function(context, sel)
  {
    return (context.$$ && context.$$(sel)) ||
           (context.getElements && context.getElements(sel)) ||
           [];
  },
  
  
  generateOutletBindings: function(ctxt)
  {
    // grab the right context, grab all outlets
    var context = ctxt || window;
    var outlets = this.contextQuery(context, '*[outlet]');
    
    outlets.each(function(anOutlet) {
      var outletTarget, sourceName;
      
      // grab the outlet parent id
      var outlet = anOutlet.getProperty('outlet').trim();
      
      // if not a JSON value it's just the id
      if(outlet[0] != '{')
      {
        outletTarget = outlet;
        sourceName = anOutlet.getProperty('id');
      }
      else
      {
        // otherwise JSON decode it in safe mode
        var outletBinding = JSON.decode(outlet);
        outletTarget = outletBinding.target;
        sourceName = outletBinding.name;
      }
      
      this.outletBindings.push({
        sourceName: sourceName,
        source: anOutlet,
        targetName: outletTarget,
        context: context
      });
      
    }.bind(this));
  },
  
  
  bindOutlets: function()
  {
    // bind each outlet
    this.outletBindings.each(function(binding) {
      
      // get the real window context
      var context = ($type(binding.context) == 'window' && binding.context) ||
                    ($type(binding.context) == 'element' && binding.context.getWindow()),
          sourceName = binding.sourceName,
          source = binding.source,
          targetName = binding.targetName;
      
      // first check frame then check parent window    
      var target = context.$(targetName) || (context != window && $(targetName));
        
      if(!target)
      {
        // check for parent with matching css selector
        target = source.getParent(targetName);
      } 
      
      if(!target)
      {
        // throw an exception
        console.error('Error: Sandalphon bindOutlets, binding target does not exist! ' + targetName);
        console.error(source);
        // bail
        return;
      }
      
      // check for a controller on the source
      if(SSControllerForNode(source))
      {
        source = SSControllerForNode(source);
      }
      
      SSControllerForNode(target).addOutletWithName(sourceName, source);
    }.bind(this));
    
    SSLog(this.outletBindings, SSLogSandalphon);
  },
  
  
  awakeObjects: function(context)
  {
    // set up superview relationships
    ShiftSpaceObjects.each(function(object, objectId) {
      if(object.__awake__) object.__awake__(context);
    });

    // set any delegate references
    ShiftSpaceObjects.each(function(object, objectId) {
      if(object.beforeAwake) object.beforeAwake(context);
    });

    // awake all the objects
    ShiftSpaceObjects.each(function(object, objectId) {
      if(object.awake && !object.isAwake())
      {
        object.awake(context);
        object.setIsAwake(true);
        object.fireEvent('onAwake');
      }
    });
    
    // post awake, all outlets set
    ShiftSpaceObjects.each(function(object, objectId) {
      if(object.afterAwake)
      {
        object.afterAwake(context);
      }
    });
  },
  
  
  /*
    Function: analyze
      Determine if all the required classes for the interface are available.
    
    Parameters:
      html - the interface markup as a string.
  */
  analyze: function(html)
  {
    this.fragment().set('html', html);
    
    var allNodes = this.fragment().getElements('*[uiclass]');
    
    var classes = allNodes.map(function(x){return x.getProperty('uiclass')});
    
    // First verify that we have a real path for each class
    var missingClasses = false;
    classes.each(function(x) {
      if(!missingClasses) missingClasses = (ShiftSpaceClassPaths[x] == null && ShiftSpaceUIClassPaths[x] == null && ShiftSpaceUserClassPaths[x] == null);
    }.bind(this));
    
    if(missingClasses) console.error('Error missing uiclasses.');
    
    if(missingClasses)
    {
      return false;
    }
    else
    {
      return true;
    }
  }
  
});
var Sandalphon = new SandalphonClass();


var SandalphonToolClass = new Class({
   Language: 'en',

   initialize: function(storage)
   { 
     SSLog('Sandalphon, sister of Metatron, starting up.', SSLogSandalphon);
     // setup the persistent storage
     this.setStorage(storage);
     this.initInterface();
   },

   /*
     Function: initInterface
       Loads the last used input paths as a convenience.
   */
   initInterface: function()
   {
     SSLog('Initializing interface', SSLogSandalphon);

     this.storage().get('lastInterfaceFile', function(ok, value) {
       if(ok && value) $('loadFileInput').setProperty('value', value);
     });

     this.attachEvents();    
   },

   
   /*
     Function: storage
       Accessor method.

     Returns:
       The persistent storage object.
   */
   storage: function()
   {
     return this.__storage__;
   },

   /*
     Function: setStorage
       Set the persistent storage object.

     Parameters:
       storage - A persistent storage object, provided by persist.js
   */
   setStorage: function(storage)
   {
     this.__storage__ = storage;
   },

   
   loadUI: function(ui)
   {
     //console.log(ui);
     // empty it out first
     $('SSSandalphonContainer').empty();
     // Add the style
     Sandalphon.addStyle(ui.styles);
     // load the new file
     $('SSSandalphonContainer').set('html', ui.interface);
     // activate the interface
     Sandalphon.activate();
   },

   /*
     Function: attachEvents
       Attach the gui events for the interface.
   */
   attachEvents: function()
   {
     // attach file loading events
     $('loadFileInput').addEvent('keyup', function(_evt) {
       var evt = new Event(_evt);
       if(evt.key == 'enter')
       {
         Sandalphon.load($('loadFileInput').getProperty('value'), this.loadUI.bind(this));
       }
     }.bind(this));

     // attach the compile events
     $('compileFile').addEvent('click', this.compileFile.bind(this));

     // attach events to localization switcher
     $('localizedStrings').addEvent('change', function(_evt) {
       var evt = new Event(_evt);
       SSLoadLocalizedStrings($('localizedStrings').getProperty('value'));
     }.bind(this));
   },
   
   /*
      Function: compileFile
        Tell the server to compile the file
    */
   compileFile: function()
   {
     // clear out all existing system data

     // grab the filepath
     var filepath = $('loadFileInput').getProperty('value');
     // save for later
     this.storage().set('lastInterfaceFile', filepath);

     new Request({
       url: "compile.php",
       data: {"filepath":'../'+filepath+'.html'},
       onComplete: function(responseText, responseXml)
       {
         var filename = filepath.split('/').getLast();
         Sandalphon.load('client/compiledViews/'+filename, this.loadUI.bind(this));
       }.bind(this),
       onFailure: function(response)
       {
         console.error(response);
       }
     }).send();
   }
});


// End ../sandalphon/SandalphonCore.js ----------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SandalphonCore');

if (SSInclude != undefined) SSLog('Including ../client/EventProxy.js...', SSInclude);

// Start ../client/EventProxy.js ----------------------------------------------

// ==Builder==
// @required
// @name              EventProxy
// @package           System
// ==/Builder==

// event proxy object since, ShiftSpace is not a MooTools class
var __eventProxyClass__ = new Class({});
__eventProxyClass__.implement(new Events);
var __eventProxy__ = new __eventProxyClass__();

/*
  Function: SSAddEvent
    Adds a Mootools style custom event to the ShiftSpace object.

  Parameters:
    eventType - a event type as string.
    callback - a function.

  See also:
    SSFireEvent
*/
var __sleepingObjects__ = $H();
function SSAddEvent(eventType, callback, anObject)
{
  //console.log('adding event ' + eventType);
  if(anObject && anObject.isAwake && !anObject.isAwake())
  {
    var objId = anObject.getId();
    if(!__sleepingObjects__.get(objId))
    {
      __sleepingObjects__.set(anObject.getId(), $H({
        object: anObject,
        events: $H()
      }));
    }
    var eventsHash = __sleepingObjects__.get(objId).get('events');
    if(!eventsHash.get(eventType))
    {
      eventsHash.set(eventType, []);
    }
    eventsHash.get(eventType).push(callback);
  }
  else
  {
    __eventProxy__.addEvent(eventType, callback);
  }
};

/*
  Function: SSFireEvent
    A function to fire events.

  Parameters:
    eventType - event type as string.
    data - any extra event data that should be passed to the event listener.
*/
function SSFireEvent(eventType, data) 
{
  //console.log('SSFireEvent ' + eventType);
  __eventProxy__.fireEvent(eventType, data);
  
  var awakeNow = __sleepingObjects__.filter(function(objectHash, objectName) {
    return objectHash.get('object').isAwake();
  });
  
  // call back these immediate
  awakeNow.each(function(objectHash, objectName) {
    //console.log('now awake ' + objectName);
    SSAddEventsAndFire(eventType, objectHash.get('events').get(eventType));
  });
  
  var stillSleeping = __sleepingObjects__.filter(function(objectHash, objectName) {
    //console.log('checking ' + objectName + ' ' + objectHash.get('object').isAwake());
    return !objectHash.get('object').isAwake();
  });
  
  stillSleeping.each(function(objectHash, objectName) {
    objectHash.get('object').addEvent('onAwake', function() {
      //console.log('waking up!');
      SSAddEventsAndFire(eventType, objectHash.get('events').get(eventType));
    });
  });
  
  // update which objects are still sleeping
  __sleepingObjects__ = stillSleeping;
  
  //console.log('still sleeping');
  //console.log(__sleepingObjects__.getLength());
};

// takes and event type and a list of event callbacks
// adds each callback as well as executing
function SSAddEventsAndFire(eventType, events)
{
  if(events && events.length > 0)
  {
    events.each(function(callback) {
      SSAddEvent(eventType, callback);
      callback();
    });
  }
}

// End ../client/EventProxy.js ------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('EventProxy');

if (SSInclude != undefined) SSLog('Including ../client/SSException.js...', SSInclude);

// Start ../client/SSException.js ---------------------------------------------

// ==Builder==
// @optional
// @name              SSException
// @package           System
// ==/Builder==

var SSExceptionPrinter = new Class({
  toString: function()
  {
    return ["["+this.name+"] message: " + this.message(), " fileName:" + this.fileName(), " lineNumber: " + this.lineNumber(), (this.originalError() && this.originalError().message) || 'no original error'].join(", ");
  }
});

var SSException = new Class({
  
  name: 'SSException',

  Implements: SSExceptionPrinter,
    
  initialize: function(_error)
  {
    this.theError = _error;
  },
    
  setMessage: function(msg)
  {
    this.__message__ = msg; 
  },
  
  message: function()
  {
    return this.__message__ || (this.theError && this.theError.message) || 'n/a';
  },
  
  fileName: function()
  {
    return (this.theError && this.theError.fileName) || 'n/a';
  },

  lineNumber: function()
  {
    return (this.theError && this.theError.lineNumber) || 'n/a';
  },
  
  originalError: function()
  {
    return this.theError;
  }
  
});

// End ../client/SSException.js -----------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSException');

if (SSInclude != undefined) SSLog('Including ../client/ShiftSpaceElement.js...', SSInclude);

// Start ../client/ShiftSpaceElement.js ---------------------------------------

// ==Builder==
// @required
// @export            SSElement as Element, SSIframe as Iframe, SSInput as Input
// @name	            ShiftSpaceElement
// @package           System
// ==/Builder==

// Element extensions because child selectors don't work properly in MooTools 1.2 for some reason - David
Element.implement({
  _ssgenId: function()
  {
    var id = this.getProperty('id');
    if(!id)
    {
      id = Math.round(Math.random()*1000000+(new Date()).getMilliseconds());
      this.setProperty('id', 'generatedId_'+id);
    }
    return id;
  },
  _getElement: function(sel)
  {
    this._ssgenId();
    return (new Document(this.ownerDocument)).getWindow().$$('#' + this.getProperty('id') + ' ' + sel)[0];
  },
  _getElements: function(sel)
  {
    this._ssgenId();
    return (new Document(this.ownerDocument)).getWindow().$$('#' + this.getProperty('id') + ' ' + sel);
  }
});

/*
  Class: ShiftSpace.Element
    A wrapper around the MooTools Element class that marks each DOM node with the ShiftSpaceElement CSS
    class.  This is required for identifying which elements on the page belong to ShiftSpace.  In the case
    of iFrames this is also used to make sure that iFrame covers get generated so that drag and resize
    operations don't break.
*/
var SSElement = new Class({
  /*
    Function: initialize (private)
      Initialize the element and if necessary add appropiate event handlers.

    Parameters:
      _el - a raw DOM node or a string representing a HTML tag type.
      props - the same list of options that would be passed to the MooTools Element class.

    Returns:
      An ShiftSpace initialized and MooTools wrapped DOM node.
  */
  initialize: function(_el, props)
  {
    var el = (_el == 'iframe') ? new IFrame(props) : new Element(_el, props);

    // ShiftSpaceElement style needs to be first, otherwise it overrides passed in CSS classes - David
    el.setProperty( 'class', 'ShiftSpaceElement ' + el.getProperty('class') );

    // remap makeResizable and makeDraggable - might want to look into this more
    var resizeFunc = el.makeResizable;
    var dragFunc = el.makeDraggable;

    // override the default behavior
    if(SSAddIframeCovers)
    {
      el.makeDraggable = function(options)
      {
        var dragObj;
        if(!dragFunc)
        {
          dragObj = new Drag.Move(el, options);
        }
        else
        {
          dragObj = (dragFunc.bind(el, options))();
        }

        dragObj.addEvent('onStart', function() {
          SSAddIframeCovers();
        });
        dragObj.addEvent('onDrag', SSUpdateIframeCovers);
        dragObj.addEvent('onComplete', SSRemoveIframeCovers);

        return dragObj;
      };

      // override the default behavior
      el.makeResizable = function(options)
      {
        var resizeObj;
        if(!resizeFunc)
        {
          resizeObj = new Drag.Base(el, $merge({modifiers: {x: 'width', y: 'height'}}, options));
        }
        else
        {
          resizeObj = (resizeFunc.bind(el, options))();
        }

        resizeObj.addEvent('onStart', SSAddIframeCovers);
        resizeObj.addEvent('onDrag', SSUpdateIframeCovers);
        resizeObj.addEvent('onComplete', SSRemoveIframeCovers);

        return resizeObj;
      };
    }

    return el;
  }
});

/*
  Class : ShiftSpace.Iframe
    This class allows the creation of iframes with CSS preloaded.  This will eventually
    be deprecated by the the forthcoming MooTools Iframe element which actually loads
    MooTools into the Iframe.  Inherits from <ShiftSpace.Element>.  You shouldn't instantiate
    this class directly, just use <ShiftSpace.Element>.
*/
var SSIframe = new Class({

  Extends: SSElement,

  /*
    Function: initialize (private)
      Initializes the iframe.

    Parameters:
      props - the same properties that would be passed to a MooTools element.

    Returns:
      A ShiftSpace initialized and MooTools wrapped Iframe.
  */
  initialize: function(props)
  {
    // check for the css property
    this.css = props.css;

    // check for cover property to see if we need to add a cover to catch events
    var loadCallBack = props.onload;
    delete props.onload;

    // eliminate the styles, add on load event
    var finalprops = $merge(props, {
      events:
      {
        load : function(_cb) {
          // load the css
          if(this.css)
          {
            SSLoadStyle(this.css, null, this.frame);
          }
          _cb();
        }.bind(this, loadCallBack)
      }
    });

    // store a ref for tricking
    this.frame = this.parent('iframe', finalprops);

    var addCover = true;
    if($type(props.addCover) != 'undefined' && props.addCover == false) addCover = false;

    if(addCover && SSAddCover)
    {
      // let ShiftSpace know about it
      SSAddCover({cover:SSCreateCover(), frame:this.frame});
    }
    else
    {
      SSLog('=========================== No cover to add!');
    }

    // return
    return this.frame;
  }
});

var SSInput = new Class({
  Extends: SSElement
  // Create an iframe
  // Apply the styles
  // Create the requested input field
  // set the input field / textarea to be position absolute, left top right bottom all 0
  // set up event handlers so they get pass up to the developer
});

/*
  Function: SSIsSSElement
    Check wheter a node is a ShiftSpace Element or has a parent node that is.

  Parameters:
    node - a DOM node.

  Returns:
    true or false.
*/
function SSIsSSElement(node)
{
  if(node.hasClass('ShiftSpaceElement'))
  {
    return true;
  }

  var hasSSParent = false;
  var curNode = node;

  while(curNode.getParent() && $(curNode.getParent()).hasClass && !hasSSParent)
  {
    if($(curNode.getParent()).hasClass('ShiftSpaceElement'))
    {
      hasSSParent = true;
      continue;
    }
    curNode = curNode.getParent();
  }

  return hasSSParent;
}
this.isSSElement = SSIsSSElement;

/*
  Function: SSIsNotSSElement
    Conveniece function that returns the opposite of SSIsSSElement.  Useful for node filtering.

  Parameters:
    node - a DOM node.

  Returns:
    true or false.
*/
function SSIsNotSSElement(node)
{
  return !SSIsSSElement(node);
}

// End ../client/ShiftSpaceElement.js -----------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('ShiftSpaceElement');

if (SSInclude != undefined) SSLog('Including ../client/SSCustomExceptions.js...', SSInclude);

// Start ../client/SSCustomExceptions.js --------------------------------------

// ==Builder==
// @optional
// @name              SSCustomExceptions
// @package           System
// @dependencies      SSException
// ==/Builder==

var SSSpaceDoesNotExistError = new Class({
  Extends: SSException,
  
  name: 'SSSpaceDoesNotExistError',
  
  initialize: function(_error, spaceName)
  {
    this.parent(_error);
    this.spaceName = spaceName;
  },
  
  message: function()
  {
    return "Space " + this.spaceName + " does not exist.";
  }

});

var ShiftDoesNotExistError = new Class({
  
});

// End ../client/SSCustomExceptions.js ----------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSCustomExceptions');

if (SSInclude != undefined) SSLog('Including ../client/SandalphonSupport.js...', SSInclude);

// Start ../client/SandalphonSupport.js ---------------------------------------

// ==Builder==
// @required
// @name              SandalphonSupport
// @package           System
// @dependencies      SandalphonCore
// ==/Builder==

var SSInstantiationListeners = {};
function SSAddInstantiationListener(element, listener)
{
  var id = element._ssgenId();
  if(!SSInstantiationListeners[id])
  {
    SSInstantiationListeners[id] = [];
  }
  SSInstantiationListeners[id].push(listener);
}

function SSNotifyInstantiationListeners(element)
{
  var listeners = SSInstantiationListeners[element.getProperty('id')];
  if(listeners)
  {
    listeners.each(function(listener) {
      if(listener.onInstantiate)
      {
        listener.onInstantiate();
      }
    });
  }
}

var __controllers__ = $H();

function SSClearControllersTable()
{
  __controllers__ = $H();
}

function SSClearObjects()
{
  ShiftSpaceObjects.empty();
  ShiftSpaceNameTable.empty();
}

// NOTE: we generate ids and store controller refs ourselves this is because of weird garbage collection
// around iframes and wrappers around dom nodes when SS runs under GM - David
function SSSetControllerForNode(controller, _node)
{
  var node = $(_node);

  // generate our own id
  node._ssgenId();
  // keep back reference
  __controllers__.set(node.getProperty('id'), controller);
}

// return the controller for a node
function SSControllerForNode(_node)
{
  var node = $(_node);
  return __controllers__.get(node.getProperty('id')) ||
         (node.getProperty('uiclass') && new SSViewProxy(node)) ||
         null;
}

function SSControllerOrNode(object)
{
  return SSControllerForNode(object) || object;
}

function SSIsController(object)
{
  if($type(object) == 'element')
  {
    return false;
  }
  else if(object._genId)
  {
    return true;
  }
  return false;
}

function SSGetInlineOptions(el)
{
  return JSON.decode(el.getProperty('options'));
}

var __ssappdelegate__;
function SSSetAppDelegate(delegate)
{
  __ssappdelegate__ = delegate;
}

function SSAppDelegate()
{
  return __ssappdelegate__;
}

// End ../client/SandalphonSupport.js -----------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SandalphonSupport');

if (SSInclude != undefined) SSLog('Including ../client/SSViewProxy.js...', SSInclude);

// Start ../client/SSViewProxy.js ---------------------------------------------

// ==Builder==
// @required
// @name	            SSViewProxy
// @package           System
// @dependencies      SandalphonSupport
// ==/Builder==

var SSViewProxy = new Class({

  name: "SSViewProxy",

  Implements: [Options, Events],
  
  defaults: function()
  {
    return {};
  },

  initialize: function(el, options)
  {
    // store the element
    this.element = $(el);

    // generate an id for the element in case it doesn't already have one
    el._ssgenId();
    // set messages
    this.setMessages([]);
    // add a listener for this element
    SSAddInstantiationListener(el, this);
  },


  onInstantiate: function()
  {
    this.deliverMessages();
  },


  adoptClassMethods: function()
  {
    // NOTE: probably overkill, but here to implement just in case - David
  },


  setMessages: function(newMessages)
  {
    this.__messages__ = newMessages;
  },


  messages: function()
  {
    return this.__messages__;
  },


  deliverMessages: function()
  {
    var controller = SSControllerForNode(this.element);
    SSLog('deliverMessages ' + this.element, SSLogViews);
    SSLog(controller, SSLogViews);
    this.messages().each(function(message) {
      controller[message.name].apply(controller, message.arguments);
    });
  },


  setDelegate: function()
  {
    this.messages().push({name:'setDelegate', arguments:$A(arguments)});
  },


  show: function()
  {
    // add a show message
    this.messages().push({name:'show', arguments:$A(arguments)});
  },


  hide: function()
  {
    // add a hide message
    this.messages().push({name:'hide', arguments:$A(arguments)});
  },


  refresh: function()
  {
    // add a refresh message
    this.messages().push({name:'refresh', arguments:$A(arguments)});
  },


  addEvent: function(type, handler)
  {
    this.message().push({name:'addEvent', arguments:$A(arguments)});
  },


  destroy: function()
  {

  }

});

// End ../client/SSViewProxy.js -----------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSViewProxy');

// === END PACKAGE [System] ===


// === START PACKAGE [ErrorHandling] ===

if(__sysavail__) __sysavail__.packages.push("ErrorHandling");

// === END PACKAGE [ErrorHandling] ===


// === START PACKAGE [Internationalization] ===

if(__sysavail__) __sysavail__.packages.push("Internationalization");

if (SSInclude != undefined) SSLog('Including ../client/LocalizedStringsSupport.js...', SSInclude);

// Start ../client/LocalizedStringsSupport.js ---------------------------------

// ==Builder==
// @required
// @name              LocalizedStringsSupport
// @package           Internationalization
// ==/Builder==

var __sslang__ = null;
function SSLoadLocalizedStrings(lang, ctxt)
{
  var context = ctxt || window;
  //SSLog('load localized strings ' + lang);
  SSLoadFile("client/LocalizedStrings/"+lang+".json", function(rx) {
    SSLog(')))))))))))))))))))))))))))))))))))))))))))');
    SSLog(lang + " - " + __sslang__);
    if(lang != __sslang__)
    {
      SSLog('Evaluating language file');
      ShiftSpace.localizedStrings = JSON.decode(rx.responseText);
      //SSLog(ShiftSpace.localizedStrings);

      // update objects
      ShiftSpaceObjects.each(function(object, objectId) {
        if(object.localizationChanged) object.localizationChanged();
      });

      // in case we get a raw context from FF3
      if(!context.$$)
      {
        context = new Window(context);
      }

      // update markup
      //SSLog('fix localized');
      context.$$(".SSLocalized").each(function(node) {

        var originalText = node.getProperty('title');

        if(node.get('tag') == 'input' && node.getProperty('type') == 'button')
        {
          node.setProperty('value', SSLocalizedString(originalText));
        }
        else
        {
          node.set('text', SSLocalizedString(originalText));
        }

      }.bind(this));
    }

    __sslang__ = lang;
  });
}

// End ../client/LocalizedStringsSupport.js -----------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('LocalizedStringsSupport');

// === END PACKAGE [Internationalization] ===


// === START PACKAGE [EventHandling] ===

if(__sysavail__) __sysavail__.packages.push("EventHandling");

// === END PACKAGE [EventHandling] ===


// === START PACKAGE [UtilitiesExtras] ===

if(__sysavail__) __sysavail__.packages.push("UtilitiesExtras");

// === END PACKAGE [UtilitiesExtras] ===


// === START PACKAGE [Core] ===

if(__sysavail__) __sysavail__.packages.push("Core");

if (SSInclude != undefined) SSLog('Including ../client/core/UserFunctions.js...', SSInclude);

// Start ../client/core/UserFunctions.js --------------------------------------

// ==Builder==
// @optional
// @name              UserFunctions
// @package           Core
// ==/Builder==

// Private variable and function for controlling user authentication
var username = false;

function setUsername(_username) {
  username = _username;
}

/*
  Function: SSUserForShift
    Returns the username for a shift.

  Parameters:
    shiftId - a shift id.

  Returns:
    The shift author's username as a string.
*/
function SSUserForShift(shiftId)
{
  return SSGetShift(shiftId).username;
}

/*
  Function: SSUserOwnsShift
    Used to check whether the currently logged in user authored a shift.

  Parameters:
    shiftId - a shift id.

  Returns:
    true or false.
*/
function SSUserOwnsShift(shiftId)
{
  return (SSUserForShift(shiftId) == ShiftSpace.User.getUsername());
}

/*
  Function: SSUserCanEditShift
    Used to check whether a user has permission to edit a shift.

  Parameters:
    shiftId - a shift id.

  Returns:
    true or false.
*/
function SSUserCanEditShift(shiftId)
{
  return (ShiftSpace.User.isLoggedIn() &&
          SSUserOwnsShift(shiftId));
}

// End ../client/core/UserFunctions.js ----------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('UserFunctions');

if (SSInclude != undefined) SSLog('Including ../client/core/UtilityFunctions.js...', SSInclude);

// Start ../client/core/UtilityFunctions.js -----------------------------------

// ==Builder==
// @optional
// @name              UtilityFunctions
// @package           Core
// ==/Builder==


/*

Function: info
Provides basic information about ShiftSpace's current state.

Parameters:
    spaceName - (optional) Get information about a specific installed space.

Returns:
    When no parameter is specified, returns an object with the following
    variables set:

    - server (string), the base URL of the ShiftSpace server
    - spaces (string), a list of currently installed spaces
    - version (string), the current version of ShiftSpace

    If spaceName is specified, returns the following information about the
    space:

    - title (string), a human-readable version of the space name
    - icon (string), the URL of the Space's icon
    - version (string), the current version of the installed Space

*/
function SSInfo(spaceName) 
{
  if (typeof spaceName != 'undefined') 
  {
    var defaults = {
      title: spaceName,
      icon: server + 'images/unknown-space.png',
      version: '1.0'
    };
    if (!installed[spaceName]) 
    {
      defaults.unknown = true;
      return defaults;
    }
    // TODO - this must be fixed, we need to cache space attributes - David
    defaults.icon = server + 'spaces/' + spaceName + '/' + spaceName + '.png';
    //var spaceInfo = $merge(defaults, spaces[spaceName].attributes);
    var spaceInfo = $merge(defaults, {});
    delete spaceInfo.name; // No need to send this back
    spaceInfo.url = installed[spaceName];
    return spaceInfo;
  }
  if(typeof installed != 'undefined')
  {
    var spaceIndex = [];
    for (var aSpaceName in installed) 
    {
      spaceIndex.push(aSpaceName);
    }
  }
  return {
    server: server,
    spacesDir: (typeof spacesDir != 'undefined' && spacesDir) || null,
    spaces: (spaceIndex && spaceIndex.join(', ')) || null,
    version: (typeof version != 'undefined' && version) || null
  };
};

// ===============================
// = Function Prototype Helpers  =
// ===============================

// This won't work for GM_getValue of course - David
Function.prototype.safeCall = function() {
  var self = this, args = [], len = arguments.length;
  for(var i = 0; i < len; i++) args.push(arguments[i]);
  setTimeout(function() {
    return self.apply(null, args);
  }, 0);
};

// Work around for GM_getValue - David
Function.prototype.safeCallWithResult = function() {
  var self = this, args = [], len = arguments.length;
  for(var i = 0; i < len-1; i++) args.push(arguments[i]);
  // the last argument is the callback
  var callback = arguments[len-1];
  setTimeout(function() {
    callback(self.apply(null, args));
  }, 0);
};

/*
  Function: SSHasProperty
    Convenience function to check whether an object has a property.

  Parameters:
    obj - an Object.
    prop - the property name as a string.

  Returns:
    a boolean.
*/
function SSHasProperty(obj, prop)
{
  return (typeof obj[prop] != 'undefined');
}

/*
  Function: SSImplementsProtocol
    A method to check if an object implements the required properties.

  Parameters:
    protocol - an array of required properties
    object - the javascript object in need of verification.

  Returns:
    A javascript object that contains two properties, 'result' which is a boolean and 'missing', an array of missing properties.
*/
function SSImplementsProtocol(protocol, object)
{
  var result = true;
  var missing = [];
  for(var i = 0; i < protocol.length; i++)
  {
    var prop = protocol[i];
    if(!object[prop])
    {
       result = false;
       missing.push(prop);
    }
  }
  return {'result': result, 'missing': missing};
}

var __dragDiv__;
function SSCreateDragDiv()
{
  __dragDiv__ = new ShiftSpace.Element('div', {
    id: 'SSDragDiv'
  });
}

function SSAddDragDiv()
{
  $(document.body).grab(__dragDiv__);
}

function SSRemoveDragDiv()
{
  __dragDiv__ = __dragDiv__.dispose();
}

function SSLocalizedStringSupport()
{
  return (typeof __sslang__ != 'undefined');
}

// Localized String Support
function SSLocalizedString(string)
{
  if(SSLocalizedStringSupport() && ShiftSpace.localizedStrings[string]) return ShiftSpace.localizedStrings[string];
  return string;
}

function SSSetDefaultEmailComments(value)
{
  if(value)
  {
    __defaultEmailComments__ = value;
    SSSetPref('defaultEmailComments', __defaultEmailComments__);
  }
}

function SSGetDefaultEmailComments(checkPref)
{
  // NOTE: 2 because we can't store 0s in the DB when in the sandbox, 1 = false, 2 = true in this case - David
  return (checkPref && SSGetPref('defaultEmailComments', 2) || __defaultEmailComments__);
}

function SSHasResource(resourceName)
{
  return __sysavail__.files.contains(resourceName) || __sysavail__.packages.contains(resourceName);
}

function SSResourceExists(resourceName)
{
  return __sys__.files[resourceName] != null || __sys__.packages[resourceName] != null;
}

/*
  Function: SSCheckForAutolaunch
    Check for Spaces which need to be auto-launched.
*/
function SSCheckForAutolaunch()
{
  for(space in installed)
  {
    if(SSGetPrefForSpace(space, 'autolaunch'))
    {
      var ids = SSAllShiftIdsForSpace(space);
      var spaceObject = SSSpaceForName(space);

      // in the case of the web we need to load the space first
      if(!spaceObject)
      {
        // load the space first
        SSLoadSpace(space, function() {
          ids.each(SSShowShift);
        });
        return;
      }
      else
      {
        // otherwise just show the puppies, this works in the sandbox
        ids.each(SSShowShift);
      }
    }
  }
}

function SSResetCore()
{
  // reset all internal state
  __spaces__ = {};
}

// End ../client/core/UtilityFunctions.js -------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('UtilityFunctions');

if (SSInclude != undefined) SSLog('Including ../client/core/PersistenceFunctions.js...', SSInclude);

// Start ../client/core/PersistenceFunctions.js -------------------------------

// ==Builder==
// @optional
// @name              PersistenceFunctions
// @package           Core
// ==/Builder==

/*
Function: SSSetValue
  A wrapper function for GM_setValue that handles non-string data better.

Parameters:
  key - A unique string identifier
  value - The value to store. This will be serialized by uneval() before
          it gets passed to GM_setValue.

Returns:
    The value passed in.
*/
function SSSetValue(key, value, rawValue) 
{
  SSLog('SSSetValue ' + key, SSLogForce);
  if (rawValue) 
  {
    GM_setValue(key, value);
  } 
  else 
  {
    GM_setValue(key, JSON.encode(value));
  }
  return value;
}

/*
Function: SSGetValue (private, except in debug mode)
  A wrapper function for GM_getValue that handles non-string data better.

Parameters:
  key - A unique string identifier
  defaultValue - This value will be returned if nothing is found.
  rawValue - Doesn't use Json encoding on stored values

Returns:
  Either the stored value, or defaultValue if none is found.
*/
function SSGetValue(key, defaultValue, rawValue)
{
  if (!rawValue) 
  {
    defaultValue = JSON.encode(defaultValue);
  }
  var result = GM_getValue(key, defaultValue);
  var temp = JSON.decode(result);

  if(temp == null || temp == 'null') result = null;
  
  if (result == null) 
  {
    SSLog('is null SSGetValue("' + key + '") = ' + JSON.decode(defaultValue), SSLogForce);
    return JSON.decode(defaultValue);
  } 
  else if (rawValue) 
  {
    SSLog('raw value SSGetValue("' + key + '") = ' + result, SSLogForce);
    return result;
  } 
  else 
  {
    SSLog('real value SSGetValue("' + key + '") = ...' + JSON.decode(result), SSLogForce);
    return JSON.decode(result);
  }
}


/*
  Function: SSSetPref
    Set a user preference. Implicitly calls SSSetValue which will JSON encode the value.

  Parameters:
    pref - the preference name as string.
    value - the value.

  See Also:
    SSSetValue
*/
function SSSetPref(pref, value)
{
  if(ShiftSpace.User.isLoggedIn())
  {
    var key = [ShiftSpace.User.getUsername(), pref].join('.');
    SSSetValue(key, value);
  }
}

/*
  Function: SSGetPref
    Return a user preference.  Implicity calls SSGetValue which will JSON decode the value.

  Parameters:
    pref - the preference key as a string.
    defaultValue - the defaultValue if this preference does not exist.

  Returns:
    A JSON object.
*/
function SSGetPref(pref, defaultValue)
{
  if(ShiftSpace.User.isLoggedIn())
  {
    var key = [ShiftSpace.User.getUsername(), pref].join('.');
    return SSGetValue(key, defaultValue);
  }
  return defaultValue;
}

// End ../client/core/PersistenceFunctions.js ---------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('PersistenceFunctions');

if (SSInclude != undefined) SSLog('Including ../client/core/RemoteFunctions.js...', SSInclude);

// Start ../client/core/RemoteFunctions.js ------------------------------------

// ==Builder==
// @optional
// @name              RemoteFunctions
// @package           Core
// ==/Builder==

var __sspendingRequest = 0;
var __ssshowLoader = false;
var __ssloaderListeners = [];

function SSAddLoaderListener(obj)
{
  __ssloaderListeners.push(obj);
}

function SSIncPendingRequests()
{
  __sspendingRequest = __sspendingRequest + 1;
  if(!__ssshowLoader)
  {
    __ssloaderListeners.each(function(listener) {
      if(listener.showLoader) listener.showLoader();
    });
    __ssshowLoader = true;
  }
}

function SSDecPendingRequests()
{
  if(__sspendingRequest > 0) __sspendingRequest = __sspendingRequest - 1;
  if(__sspendingRequest == 0)
  {
    __ssloaderListeners.each(function(listener) {
      if(listener.hideLoader) listener.hideLoader();
    });
    __ssshowLoader = false;
  }
}

/*
Function: SSServerCall
  Sends a request to the server.

Parameters:
  method - Which method to call on the server (string)
  parameters - Values passed with the call (object)
  callback - (optional) A function to execute upon completion
*/
function SSServerCall(method, parameters, _callback) 
{
  SSIncPendingRequests();

  var callback = _callback;
  var url = server + 'server/?method=' + method;
  
  SSLog('serverCall: ' + url, SSLogServerCall);
  
  var data = '';

  for (var key in parameters) 
  {
    if (data != '') 
    {
      data += '&';
    }
    data += key + '=' + encodeURIComponent(parameters[key]);
  }

  if(typeof installedPlugins != 'undefined')
  {
    var plugins = new Hash(installedPlugins);
    url += '&plugins=' + plugins.getKeys().join(',');
  }

  var now = new Date();
  url += '&cache=' + now.getTime();

  var req = {
    method: 'POST',
    url: url,
    data: data,
    onload: function(_rx) 
    {
      SSDecPendingRequests();
      
      SSLog('done!');
      var rx = _rx;
      SSLog('servercall returned', SSLogServerCall);

      if ($type(callback) == 'function') 
      {
        try
        {
          SSLog('trying ' + url);
          SSLog(rx.responseText);
          SSLog(eval('(' + rx.responseText + ')'));
          SSLog('tried ' + url);
          var theJson = JSON.decode(rx.responseText);
          SSLog('success!');
        }
        catch(exc)
        {
          SSLog('Server call exception: ' + SSDescribeException(exc), SSLogServerCall);
        }
        callback(theJson);
      }
      else
      {
        SSLog('callback is not a function', SSLogServerCall);
      }
    },
    onerror: function(err)  
    {
      SSDecPendingRequests();
      SSLog(err);
    }
  };

  // Firefox doesn't work without this
  // and the existence of this breaks Safari
  if(!window.webkit)
  {
    req.headers = {
      'Content-type': 'application/x-www-form-urlencoded'
    };
  }

  // we need to have error handling right here
  GM_xmlhttpRequest(req);
}

function SSCollectionsCall(options)
{
  SSIncPendingRequests();
  var url = server + 'server/index.php';
  
  var req = {
    method: 'POST',
    url: url,
    data: $H({method: "collections", desc:JSON.encode(options.desc)}).toQueryString(),
    onload: function(_rx) 
    {
      SSDecPendingRequests();
      if(options.onComplete)
      {
        options.onComplete(_rx.responseText);
      }
    },
    onerror: function(err)  
    {
      SSDecPendingRequests();
      if(options.onFailure)
      {
        options.onFailure(_rx.responseText);
      }
    }
  };

  // Firefox doesn't work without this
  // and the existence of this breaks Safari
  if(!window.webkit)
  {
    req.headers = {
      'Content-type': 'application/x-www-form-urlencoded'
    };
  }
  
  // we need to have error handling right here
  GM_xmlhttpRequest(req);
}

/*
Function: SSLoadStyle
  Loads a CSS file, processes it to make URLs absolute, then appends it as a
  STYLE element in the page HEAD.

Parameters:
  url - The URL of the CSS file to load
  callback - A custom function to handle css text if you don't want to use GM_addStyle
  spaceCallback - A callback function for spaces that want to use GM_addStyle but need to be notified of CSS load.
*/
function SSLoadStyle(url, callback, frame) 
{
  // TODO: check to see if the domain is different, if so don't mess with the url - David
  // TODO: get rid of frame, change to context so we can use this function for iframe's as well
  var dir = url.split('/');
  dir.pop();
  dir = dir.join('/');
  if (dir.substr(0, 7) != 'http://') {
    dir = server + dir;
  }

  //SSLog('loadStyle: ' + url);
  SSLoadFile(url, function(rx) {
    //SSLog(')))))))))))))))))))))))))))))))))))))))))))))))))) ' + url);
    var css = rx.responseText;
    // this needs to be smarter, only works on directory specific urls
    css = css.replace(/url\(([^)]+)\)/g, 'url(' + dir + '/$1)');

    // if it's a frame load it into the frame
    if(frame)
    {
      var doc = frame.contentDocument;

      if( doc.getElementsByTagName('head').length != 0 )
      {
        var head = doc.getElementsByTagName('head')[0];
      }
      else
      {
        // In Safari iframes don't get the head element by default - David
        // Mootools-ize body
        $(doc.body);
        var head = new Element( 'head' );
        head.injectBefore( doc.body );
      }

      var style = doc.createElement('style');
      style.setAttribute('type', 'text/css');
      style.appendChild(doc.createTextNode(css)); // You can not use setHTML on style elements in Safari - David
      head.appendChild(style);
    }
    else
    {
      // FIXME: we don't want to rely on this, we can't target iframes - David
      GM_addStyle(css);
    }

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

  });
}

/*
Function: SSLoadFile
  Loads a URL and executes a callback with the response

Parameters:
  url - The URL of the target file
  callback - A function to process the file once it's loaded
*/
function SSLoadFile(url, callback)
{
  // If the URL doesn't start with "http://", assume it's on our server
  if (url.substr(0, 7) != 'http://' &&
      url.substr(0, 8) != 'https://') {
    url = server + url;
  }

  //SSLog('loadFile:' + url);

  // Caching is implemented as a rather blunt instrument ...
  if ((typeof cacheFiles != 'undefined') && !cacheFiles) 
  {
    // ... either append the current timestamp to the URL ...
    var now = new Date();
    url += (url.indexOf('?') == -1) ? '?' : '&';
    url += now.getTime();
  } 
  else if((typeof cacheFiles != 'undefined') && cacheFiles)
  {
    SSLog('SSLoadFile, load from cache', SSLogForce);
    // ... or use SSGetValue to retrieve the file's contents
    var cached = SSGetValue('cache.' + url, false, true);

    if (cached) 
    {
      //SSLog('Loading ' + url + ' from cache');
      if (typeof callback == 'function') 
      {
        callback({ responseText: cached });
      }
      return true;
    }
  }

  // Load the URL then execute the callback
  //SSLog('Loading ' + url + ' from network');
  GM_xmlhttpRequest({
    'method': 'GET',
    'url': url,
    'onload': function(response) 
    {
      // Store file contents for later retrieval
      if (typeof cacheFiles != 'undefined' && cacheFiles) 
      {
        cache.push(url);
        SSSetValue('cache', cache);
        SSSetValue('cache.' + url, response.responseText, true);
      }
      if (typeof callback == 'function') 
      {
        callback(response);
      }
    },
    'onerror': function(response) 
    {
      SSLog("failed loadFile call, for file " + url, SSLogError);
      if(typeof errCallback != 'undefined' && typeof errCallback == 'function') errCallback(); // FIXME: broken - David
    }
  });

  return true;
}

/*
  Function: SSXmlHttpRequest
    Private version of GM_xmlHttpRequest. Implemented for public use via Space/Shift.xmlhttpRequest.

  Parameters:
    config - same JSON object as used by GM_xmlhttpRequest.
*/
function SSXmlHttpRequest(config) 
{
  GM_xmlhttpRequest(config);
}


// End ../client/core/RemoteFunctions.js --------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('RemoteFunctions');

// === END PACKAGE [Core] ===


// === START PACKAGE [Pinning] ===
// PROJECT OVERRIDE -- PACKAGE NOT INCLUDED


// === START PACKAGE [ShiftSpaceCore] ===

if(__sysavail__) __sysavail__.packages.push("ShiftSpaceCore");

if (SSInclude != undefined) SSLog('Including ../client/User.js...', SSInclude);

// Start ../client/User.js ----------------------------------------------------

// ==Builder==
// @optional
// @export            ShiftSpaceUser as User
// @name              User
// @package           ShiftSpaceCore
// ==/Builder==

/*
  Class: User
    A an object wrapping the current ShiftSpace User.  Use this class to check the user's display
    name as well as checking if the user is logged in or out.
*/
var ShiftSpaceUserClass = new Class({
  
  Implements: Events,
  
  
  initialize: function()
  {
    this.clearData();
  },
  
  
  syncData: function(data)
  {
    this.setUsername(data.username);
    this.setId(data.id);
    this.setEmail(data.email);
    this.setPhone(data.phone);
    this.setPhoneValidated(data.phone_validated);
    this.setPreview(data.preview);
  },
  
  
  clearData: function()
  {
    this.__username = null;
    this.__userId = null;
    this.__email = null;
    this.__phone = null;
    this.__phoneValidated = null;
    this.__preview = null;
  },
  
  
  setId: function(id)
  {
    this.__userId = id;
  },
  
  
  getId: function()
  {
    return this.__userId;
  },
  
  
  setPreview: function(val)
  {
    this.__preview = val;
  },
  
  
  preview: function()
  {
    return this.__preview;
  },
  
  
  setPerspective: function(perspective)
  {
    this.__perspective = perspective;
  },
  
  
  perspective: function()
  {
    return this.__perspective;
  },
  
  
  setUsername: function(username)
  {
    if(username != null && username != false)
    {
      this.__username = username;
    }
  },

  /*
    Function: getUsername
      Returns the logged in user's name.
      
    Returns:
      User name as string. Returns false if there is no logged in user.
  */
  getUsername: function() 
  {
    return this.__username;
  },
  
  
  setEmail: function(email)
  {
    if(email != '' && email != 'NULL' && email != null)
    {
      this.__email = email;
    }
    else
    {
      this.__email = '';
    }
  },
  
  
  email: function()
  {
    return this.__email;
  },
  
  
  setPhone: function(phone)
  {
    if(phone != '' && phone != 'NULL' && phone != null)
    {
      this.__phone = phone;
    }
    else 
    {
      this.__phone = '';
    }
  },
  
  
  phone: function()
  {
    return this.__phone;
  },
  
  
  setPhoneValidated: function(value)
  {
    this.__phoneValidated = value;
  },
  
  
  phoneValidated: function()
  {
    return this.__phoneValidated;
  },
  

  /*
    Function: isLoggedIn
      Checks whether there is a logged in user.
      
    Returns:
      A boolean.
  */
  isLoggedIn: function(showErrorAlert) 
  {
    return (this.getId() != null);
  },
  
  
  query: function(_callback)
  {
    var callback = _callback;
    SSServerCall('user.query', null, function(json) {
      if(json.data) this.syncData(json.data);
      if(callback) callback(json);
      this.fireEvent('onUserQuery', json);
    }.bind(this));
  },
  
  /*
    Function: login (private)
      Login a user. Will probably be moved into ShiftSpace.js.

    Parameters:
      credentials - object with username and password properties.
      _callback - a function to be called when login action is complete.
  */
  login: function(credentials, _callback) 
  {
    var callback = _callback;
    
    SSServerCall('user.login', credentials, function(json) {
      if(json.data) this.syncData(json.data);
      if(callback) callback(json);
      if(!json.error)
      {
        this.fireEvent('onUserLogin', json);
      }
      else
      {
        this.fireEvent('onUserLoginError', json);
      }
    }.bind(this));
  },
  
  /*
    Function: logout (private)
      Logout a user. Will probably be moved into ShiftSpace.js.
  */
  logout: function()
  {
    SSServerCall('user.logout', null, function(json) {
      // clear out all values
      this.clearData();
      if(!json.error)
      {
        this.fireEvent('onUserLogout');
      }
      else
      {
        this.fireEvent('onUserLogoutError', json);
      }
    }.bind(this));
  },
  
  /*
    Function: join (private)
      Join a new user.  Will probably be moved into ShiftSpace.js.
  */
  join: function(userInfo, _callback) 
  {
    var callback = _callback;
    SSServerCall('user.join', userInfo, function(json) {
      if(json.data) this.syncData(json.data);
      if(callback) callback(json);
      if(!json.error)
      {
        this.fireEvent('onUserJoin', json);
      }
      else
      {
        this.fireEvent('onUserJoinError', json);
      }
    }.bind(this));
  },
  
  /*
    Function: update
      Update a user's info.
      
    Parameters:
      info - info to be updated.
      callback - callback function to be run when update server call is complete.
  */
  update: function(info, callback) 
  {
    SSServerCall('user.update', info, function(json) {
      if(json.data) this.syncData(json.data);
      if(callback) callback(json);
      if(!json.error)
      {
        this.fireEvent('onUserUpdate', json);
      }
      else
      {
        this.fireEvent('onUserUpdateError', json);
      }
    }.bind(this));
  },
  
  
  validatePhone: function(_callback) 
  {
    var callback = _callback;
    SSServerCall('user.validate_phone', null, function(json) {
      if(callback) callback(json);
      this.fireEvent('onUserValidatePhone', json);
    }.bind(this));
  },
  
  
  validatePhoneComplete: function(passcode, _callback)
  {
    var callback = _callback;
    SSServerCall('user.validate_phone_complete', {key:passcode}, function(json) {
      if(callback) callback(json);
      this.fireEvent('onUserValidatePhoneComplete', json);
    }.bind(this));
  },
  
  /*
    Function: resetPassword (private)
      Reset a user's password
      
    Parameters:
      info - ?
      callback - callback function to be run when resetPassword is complete.
  */
  resetPassword: function(info, callback) 
  {
    SSServerCall('user.resetPassword', info, callback);
  },

  
  setEmailCommentsDefault: function(newValue, callback)
  {
    SSLog('setEmailCommentsDefault ' + newValue);
    // setting the value, can't use zero because of PHP, GRRR - David
    SSSetDefaultEmailComments(newValue+1);
    
    SSServerCall('user.update', {
      email_comments: newValue
    }, function(json) {
    });
  },
  
  
  bookmarksByPhone: function(phone, _callback)
  {
    var callback = _callback;
    SSServerCall('user.bookmarks_by_phone', {phone:phone}, function(json) {
      if(callback) callback(json);
      this.fireEvent('onBookmarksByPhoneComplete', json);
    }.bind(this));
  },
  
  
  getEmailCommentsDefault: function()
  {
    // setting the value, can't user zero because of PHP, GRRR - David
    return (SSGetDefaultEmailComments(true)-1);
  }
});

var ShiftSpaceUser = new ShiftSpaceUserClass();


// End ../client/User.js ------------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('User');

if (SSInclude != undefined) SSLog('Including ../client/SSPageControl.js...', SSInclude);

// Start ../client/SSPageControl.js -------------------------------------------

// ==Builder==
// @optional
// @package           ShiftSpaceCore
// ==/Builder==

var SSPageControl = new Class({

  Implements: [Events, Options],

  name: "SSPageControl",
  
  defaults: {
    listView: null,
    perPage: 25
  }, 

  initialize: function(el, listView, options)
  {
    this.setOptions(this.defaults, options);
    this.element = el;

    this.setInterfaceInitialized(false);
    this.setCurrentPage(0);
    this.setPerPage(this.options.perPage);
    this.attachEvents();
    
    if(listView)
    {
      this.setListView(listView);
      this.listView().setPageControl(this);
      
      // set a filter
      this.listView().setFilter(this.filterItem.bind(this));
      
      if(this.listView().dataIsReady())
      {
        this.initializeInterface();
      }
      
      this.listView().addEvent('onReloadData', this.initializeInterface.bind(this));
    }
  },
  
  
  filterItem: function(x, index)
  {
    if(index == undefined) return false;
    return !(index >= this.lowerBound()) || !(index < this.upperBound());
  },
  
  
  setCurrentPage: function(page)
  {
    var els = this.element.getElements('.page');
    els.removeClass('SSActive');
    els[page].addClass('SSActive');
    this.__currentPage = page;
    if(this.listView()) this.listView().refresh(true);
    this.updatePreviousAndNext();
  },
  
  
  currentPage: function()
  {
    return this.__currentPage;
  },
  
  
  updatePreviousAndNext: function()
  {
    if(this.currentPage() == 0)
    {
      this.element.getElement('.previous').addClass('SSDisplayNone');
    }
    else
    {
      this.element.getElement('.previous').removeClass('SSDisplayNone');
    }
    
    if(this.currentPage() == (this.numPages()-1))
    {
      this.element.getElement('.next').addClass('SSDisplayNone');
    }
    else
    {
      this.element.getElement('.next').removeClass('SSDisplayNone');
    }
  },
  
  
  setPerPage: function(perpage)
  {
    this.__perpage = perpage;
  },
  
  
  perPage: function()
  {
    return this.__perpage;
  },
  
  
  lowerBound: function()
  {
    return this.currentPage() * this.perPage();
  },
  
  
  upperBound: function()
  {
    return this.lowerBound() + this.perPage();
  },
  
  
  setListView: function(newListView)
  {
    this.__listView = newListView;
  },
  
  
  listView: function()
  {
    return this.__listView;
  },
  
  
  setInterfaceInitialized: function(val)
  {
    this.__interfaceInitialized = val;
  },
  
  
  interfaceInitialized: function()
  {
    return this.__interfaceInitialized;
  },
  
  
  addPages: function(startIndex, n)
  {
    for(var i = 0, j=startIndex; i <= n; i++, j++)
    {
      var link = new Element('a');
      link.set('text', j);
      var newPage = new Element('span', {
        'class': 'page'
      });
      var divider = new Element('span');
      divider.set('text', '|');
      
      newPage.grab(link);
      newPage.inject(this.element.getElements('span').getLast(), 'before');
      divider.inject(newPage, 'after');
    }
  },
  
  
  numPages: function()
  {
    if(this.listView()) return (this.listView().count() / this.perPage()).ceil();
    return 1;
  },
  
  
  attachEvents: function()
  {
    this.element.getElement('.previous').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      if(this.currentPage() > 0) this.setCurrentPage(this.currentPage()-1);
    }.bind(this));
    
    this.element.getElement('.next').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      if((this.currentPage()+1) < this.numPages()) this.setCurrentPage(this.currentPage()+1);
    }.bind(this));
  },
  
  
  initializeInterface: function()
  {
    // initialize the page control
    var count = this.listView().count();
    var numPages = (count / this.perPage()).ceil();
    var remainder = count % this.perPage();
    
    if(numPages > this.element.getElements('.page').length)
    {
      var curCount = this.element.getElements('.page').length;
      this.addPages(curCount+1, numPages-curCount);
    }
    
    this.element.getElements('.page').each(function(x) {
      var idx = this.element.getElements('.page').indexOf(x);
      
      // hide page that beyond the numbe available
      if(idx >= numPages)
      {
        x.addClass('SSDisplayNone');
        if(x.getNext()) x.getNext().addClass('SSDisplayNone');
      }
      else
      {
        x.removeClass('SSDisplayNone');
        x.getNext().removeClass('SSDisplayNone');
        // set up the click event
        x.removeEvents('click');
        x.addEvent('click', function(_evt) {
          var evt = new Event(_evt);
          this.setCurrentPage(idx);
        }.bind(this));
      }
    }.bind(this));
    
    this.updatePreviousAndNext();
    
    // check if no visible page is selected (this would happen from a deletion), if not select the last page in the list
    var currentPage = this.element.getElement('.SSActive');
    if(currentPage.hasClass('SSDisplayNone'))
    {
      this.setCurrentPage(numPages-1);
    }
  },
  
  
  show: function()
  {
    this.element.removeClass('SSDisplayNone');
  },
  
  
  hide: function()
  {
    this.element.addClass('SSDisplayNone');
  }

});

// End ../client/SSPageControl.js ---------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSPageControl');

if (SSInclude != undefined) SSLog('Including ../client/SSCollection.js...', SSInclude);

// Start ../client/SSCollection.js --------------------------------------------

// ==Builder==
// @optional
// @package           ShiftSpaceCore
// ==/Builder==

String.implement({
  assoc: function(value)
  {
    var result = {};
    result[this] = value;
    return result;
  }
});


Hash.implement({
  copy: function(value)
  {
    var copy = $H();
    this.each(function(value, key) {
      copy.set(key, value);
    });
    return copy;
  }
});


Hash.implement({
  choose: function(_properties)
  {
    var properties = $splat(_properties);
    return this.filter(function(value, key) {
      return properties.contains(key);
    });
  }
});


// ==============
// = Exceptions =
// ==============

var SSCollectionError = SSException;

SSCollectionError.NoName = new Class({
  name:"SSCollectionError.NoName",
  Extends: SSCollectionError,
  Implements: SSExceptionPrinter
});

// =========================
// = Collection Management =
// =========================

var SSCollections = $H()

function SSCollectionForName(name)
{
  return SSCollections.get(name);
}

function SSSetCollectionForName(collection, name)
{
  SSCollections.set(name, collection);
}

function SSClearCollections()
{
  SSCollections.empty();
}

function SSCollectionsClearAllPlugins()
{
  SSCollections.each(function(coll, name) {
    coll.clearPlugins();
  });
}

// ====================
// = Class Definition =
// ====================

var SSCollection = new Class({

  Implements: [Events, Options],

  name: "SSCollection",
  
  defaults: function()
  {
    return {
      table: null,
      constraints: null,
      properties: [],
      orderBy: null,
      startIndex: null,
      range: null,
      delegate: null
    }
  },
  
  initialize: function(name, options)
  {
    this.setOptions(this.defaults(), options);
    
    // set the delegate
    if(this.options.delegate) this.setDelegate(this.options.delegate);
    
    // TODO: shouldn't allow plugins from element, need a way to prevent - David
    this.setPlugins($H());
    
    this.setIsUnread(true);

    // check if using array
    if(this.options.array)
    {
      this.setArray(this.options.array)
    }
    else if(this.options.table)
    {
      // real DB backend
      this.setLoadRefresh(true);
      //setTable is marked for DELETION.
      this.setTable(this.options.table);
      this.setConstraints(this.options.constraints);
      this.setProperties(this.options.properties);
      this.setOrderBy(this.options.orderBy);
      this.setRange(this.options.range);
    }
    else
    {
      this.setArray([]);
    }
    
    if(name == null)
    {
      throw new SSCollectionError.NoName(new Error(), "collection instantiated without name.");
    }
    
    // a new collection
    this.setName(name);
    SSSetCollectionForName(this, name);
  },
  
  /*
    Function: setIsUnread 
      Sets the isUnread property of a collection. This property specifies if any of the information has been read from the collection array. 
      
    Parameters:
      value - A boolean value.
  */
  setIsUnread: function(value)
  {
    if(this.__isunread && !value) this.fireEvent('onCollectionFirstRead');
    this.__isunread = value;
  },
  /*
    Function: isUnread 
       Returns the isUnread property of a collection. This property specifies if any of the information has been read from the collection array. 
      
    Returns:
      A boolean value. 
  */
  isUnread: function()
  {
    return this.__isunread;
  },
  /*
    Function: setIsReading 
      Sets the isreading property of a collection. This property specifies if the content of a collection is being read. 
      
    Parameters:
      value - A boolean value.
  */
  setIsReading: function(value)
  {
    this.__isreading = value;
  },
  /*
    Function: isReading 
       Returns the isreading property of a collection. This property specifies if the content of a collection is being read
      
    Returns:
      A boolean value. 
  */
  isReading: function()
  {
    return this.__isreading;
  },
  /*
    Function: setPlugins 
       Sets the plugin(s) to apply to a collection. Plugins are used to assign multiple actions on collections.
       
    Parameters:
      newPlugins - plugin names as string, or an array of string values. 
  */
  setPlugins: function(newPlugins)
  {
    this.__plugins = newPlugins;
  },
  /*
    Function: plugins 
       Returns the plugins all of the plugins currently applied to a collection. 
      
    Returns:
       An array.
  */  
  plugins: function()
  {
    return this.__plugins;
  },
  /*
    Function: pluginsForAction
      Returns all of the plugins currently applied with the specified action.
      
    Parameters: 
      action - An event.
      
    Returns:
      A plugin, or an array of plugins. 
  */
  pluginsForAction: function(action)
  {
    if(!this.plugins().get(action)) this.plugins().set(action, []);
    return $A(this.plugins().get(action));
  },
  /*
    Function: addPlugin
      Accepts an event and a plugin, and inserts it into the current plugins array. 
      
    Parameters: 
      action -  the type of action (create, write, delete, update)
      plugin -  Plugin name as string
      
  */
  addPlugin: function(action, plugin)
  {
    var pluginsForAction = this.pluginsForAction(action);
    pluginsForAction.push(plugin);
    this.plugins().set(action, pluginsForAction);
  },
  /*
    Function: removePlugin
      Accepts an event and a plugin, and clears it from the current plugins array. 
      
    Parameters: 
      action - the type of action (create, write, delete, update)
      plugin -  Plugin name as string
  */
  removePlugin: function(action, plugin)
  {
    // erase a plugin
    var pluginsForAction = this.pluginsForAction(action);
    this.plugins().set(action, pluginsForAction.erase(plugin));
  },
  /*
    Function: clearPlugins
      Clears all of the currently set plugins for the collection.
  */
  clearPlugins: function()
  {
    this.setPlugins($H());
  },
  /*
    Function: setDelegate
      Sets a delegate for the collection.
      
    Parameter:
      delegate - A delegate object. 
  */
  setDelegate: function(delegate)
  {
    this.__delegate = delegate;
  },
  /*
    Function: delegate
      Returns the delegate for the collection.
      
    Return:
      A delegate object. 
  */
  delegate: function()
  {
    return this.__delegate;
  },
  /*
    Function: setLoadRefresh
      Sets the loadOnRefresh property, which determines if the collection
      should be loaded in when refreshed. 
      
    Parameters:
      val - A boolean value.
  */
  setLoadRefresh: function(val)
  {
    this.__loadOnRefresh = val;
  },
  /*
    Function: setLoadRefresh
      Returns whether the collection should be loaded in when refreshed. 
      
    Returns:
      A boolean value.
  */
  shouldLoadOnRefresh: function()
  {
    return this.__loadOnRefresh;
  },
  /*
    Function: setName
      Sets the name of the collection 
      
    Parameters:
      name - A string.
  */
  setName: function(name)
  {
    this.__name = name;
  },
  /*
    Function: name
      Returns the name of the collection 
      
    Returns:
      A string.
  */
  name: function()
  {
    return this.__name;
  },
  /*
    Function: cleanObject
      Accepts an object, and removes any null values contained within it. 
      
    Parameters:
      anObject - An object.
  */
  cleanObject: function(anObject)
  {
    return $H(anObject).filter(function(value, key) {
      return value != null && value !== '';
    }).getClean();
  },
  /*
    Function: cleanPayload
      Accepts an payload, and removes any null values contained within it. 
      
    Parameters:
      anObject - An object.
  */
  cleanPayload: function(payload)
  {
    if($type(payload) == 'array')
    {
      return payload.map(this.cleanObject);
    }
    return this.cleanObject(payload);
  },
  /*
      MARKED FOR DELETION: never used -Justin 
  */
  escapeValues: function(obj)
  {
    return $H(obj).map(function(value, key) {
      return escape(value);
    }).getClean();
  },
  /*
      MARKED FOR DELETION: never used -Justin 
  */
  unescapeValues: function(obj)
  {
    return $H(obj).map(function(value, key) {
      return unescape(value);
    }).getClean();
  },
  /*
      MARKED FOR DELETION: never used -Justin 
  */
  unescapeResult: function(ary)
  {
    return ary.map(this.unescapeValues);
  },
  /*
      Function: transact
        Accepts an action, an array of options, and a compiled collection. If a bulk is not passed or is null, the collection object is cleaned and the currently set delagates are applied....//||\\ 
      
      Parameters:
        action - the type of action (create, write, delete, update)
        options - An array of options to apply to the transaction.
        bulk -   A bulk is a compiled version without any server calls/actions.
        
      Returns:
        A payload object, an array of collection methods.      
  */
  
  transact: function(action, options, bulk)
  {
    var payload = {
      action: action,
      table: options.table,
      values: options.values,
      properties: options.properties,
      constraints: options.constraints,
      orderby: options.orderBy,
      startIndex: options.startIndex,
      range: options.range
    };
    
    if(!bulk)
    {
      // allow the delegate to add info
      var delegate = this.delegate();
      if(delegate && delegate.onTransact) payload = delegate.onTransact(this, payload);

      payload = this.cleanPayload(payload);

      // clean values as well
      if(payload.values) payload.values = this.cleanObject(payload.values);
      
      SSCollectionsCall({
        desc: payload,
        onComplete: function(response) {
          var result = JSON.decode(response);
          var data = result.data;
          data = this.applyPlugins(action, data);
          // transform the data
          options.onComplete(data);
        }.bind(this),
        onFailure: options.onFailure
      });
    }
    else
    {
      return payload;
    }
  },
  /*
    Function: bulkTransact
      Takes a series of methods. Incomplete implementation, does not support
      plugins.
    
    Parameters:
      payload - an array of collection methods.
  */
  bulkTransact: function(payload, options)
  {
    SSCollectionsCall({
      desc: payload,
      onComplete: function(response) {
        var result = JSON.decode(response);
        var data = result.data;
        // transform the data
        options.onComplete(data);
      }.bind(this),
      onFailure: options.onFailure
    });
  },
  /*
     Function: applyPlugins
      Takes an action and a collection, and applies the currently set plugins
      to the collection. The modified collection is returned. 
      
     Parameters:
       action - the type of action (create, write, delete, update)
       data - an array. 
       
      Returns: 
        A collection array. 
   */
  applyPlugins: function(action, data)
  {
    var rdata = data;
    var pluginsForAction = this.pluginsForAction(action);
    
    if(pluginsForAction.length == 0) return rdata;

    var plugin = pluginsForAction.shift();
    while(plugin)
    {
      rdata = plugin(rdata);
      plugin = pluginsForAction.shift();
    }
    
    return rdata;
  },
  /*
    NOTE: MARKED FOR DELETION - justin  
  */
  setTable: function(table)
  {
    this.__table = table;
  },
  /*
    NOTE: MARKED FOR DELETION - justin  
  */
  table: function()
  {
    return this.__table;
  },
  /*
    Function: setProperties
      Sets the properties property of a collection. The coloumns that
      are to be affected in the sqlLite database.
      
    Parameters:
      props - 
  */ 
  setProperties: function(props)
  {
    this.__properties = props;
  },
  /*
    Function: properties
      Returns properties property of a collection. 
      
    Returns:
      An array.
  */
  properties: function()
  {
    return this.__properties;
  },
  /*
    Function: setOrderBy
      Sets the orderBy property of a collection. 
      
    Parameters:
      orderBy - A string.
  */
  setOrderBy: function(orderBy)
  {
    this.__orderBy = orderBy;
  },
  /*
    Function: orderBy
      Returns the orderBy property of a collection. 
      
    Returns:
      A string.
  */
  orderBy: function()
  {
    return this.__orderBy;
  },
  /*
    Function: setRange
      Sets the range property of a collection. 
      
    Parameters:
      range - An integer.
  */
  setRange: function(range)
  {
    this.__range = range;
  },
  /*
    Function: range
      Sets the range property of a collection. 
      
    Returns:
       An integer.
  */
  range: function()
  {
    return this.__range;
  },
  /*
    Function: setConstraints
      Sets the constraints property of a collection. 
      
    Parameters:
      constraints - An array.
  */
  setConstraints: function(constraints)
  {
    this.__constraints = constraints;
  },
  /*
    Function: constraints
      Returns the constraints property of a collection. 
      
    Return:
      An array.
  */
  constraints: function()
  {
    return this.__constraints;
  },
  /*
    Function: setArray
      Sets the array property of a collection. 
      
    Parameters:
      array - An array.
  */
  setArray: function(array)
  {
    this.__array = array;
  },
  /*
    Function: getArray
      Returns the array property of a collection. 
      
    Parameters:
      array - An array.
  */
  getArray: function()
  {
    return this.__array;
  },
  /*
    Function: getColumn
      Returns a coloumn in the collections array.
    
    Parameters:
      col - An integer
    
    Returns:
      An object. 
  */
  getColumn: function(col)
  {
    return this.getArray().map(function(x) {
      return x[col];
    });
  },
  /*
    Function: getColumn
      Returns a row in the collections array.
      
    Parameters:
      idx - An integer.
      
    Returns:
      An object.
  */
  get: function(idx)
  {
    return this.__array[idx];
  },
  /*
    Function: push
      Takes an object and inserts it into the collections array.
      
    Parameters:
      object - An object.
      
    Note:
      See add method. Possibly redundant? - justin

  */
  push: function(object)
  {
    this.__array.push(object);
  },
  /*
    NOTE: marked for deletion - justin
  */
  setMetadata: function(metadata)
  {
    this.__metadata = metadata;
  },
  /*
    NOTE: marked for deletion - justin
  */
  metadata: function()
  {
    return this.__metadata;
  },
  /*
    Function: length
      Returns the length of an array, or 0 an array is not set.
      
    Returns:
      An integer
      
  */
  length: function()
  {
    if(!this.__array) return 0;
    return this.__array.length;
  },
  /*
    Function: add 
      Takes an object and adds it the the collections array.  
      Fires the onAdd and onChange events.
      
    Parameters:
      An integer
  */
  add: function(obj)
  {
    this.__array.push(obj);
    
    this.fireEvent('onAdd');
    this.fireEvent('onChange');
  },
  /*
    Function: remove
      Takes an index and removes the row from the array. 
      
    Parameters:
      inx - An integer
  */
  remove: function(idx)
  {
    if(!this.table())
    { 
      this.__array.splice(idx, 1);
    }
    else
    {
      this.__array.splice(idx, 1);
      this.fireEvent('onChange');
    }
  },
  /*
    Function: insert
      Takes an object and an index, and inserts the object into 
      the collection array at the passed index. Fires an onInsert 
      and onChange event.
      
    Parameters: 
      obj - An object.
      idx - An integer.
  */
  insert: function(obj, idx)
  {
    this.__array.splice(idx, 0, obj);
    
    this.fireEvent('onInsert', {object:obj, index:idx});
    this.fireEvent('onChange');
  },
  /*
    Function: move
      Takes a fromIndex and a toIndex, and fires an onMove event 
      
    Parameters: 
      fromIndex - An integer.
      toIndex - An integer.
  */
  move: function(fromIndex, toIndex)
  {
    this.fireEvent('onMove', {from:fromIndex, to:toIndex});
  },
  /*
    Function: set      
      Takes an object and an index, and sets the object into the
      collection array at the passed index. 
      
    Parameters: 
      obj - An object.
      index -  An integer.
  */
  set: function(obj, index)
  {
    this.__array[index] = obj;
  },
  /*
    Function: loadIndex (abstract)
        
        
    Parameters: 
      index - An integer.
      count -  An integer.
  */
  
  loadIndex: function(index, count)
  {

  },
  /*
    Function: read
      Read from the collection. Accepts a callback. Also fires
      an onLoad event that can be listened to. The onLoad event
      can be suppressed.  This is useful for handling animations
      which should wait until the result is synchronized with the
      server.
      
    Parameters:
      callback - a function.
      suppressEvent - a boolean value.
  */
  
  read: function(callback, suppressEvent)
  {
    this.setIsReading(true);
    this.initializeReadFns();
    return this.transact('read', {
      table: this.table(),
      constraints: this.constraints(),
      properties: this.properties(),
      orderBy: this.orderBy(),
      onComplete: function(data) {
        this.setArray(data);
        this.setIsUnread(false);
        this.setIsReading(false);
        this.clearOnReadFns();
        this.onRead(suppressEvent);
        if(callback && $type(callback) == 'function') callback(data);
      }.bind(this)
    });
  },
  /*
    Function: initializeReadFns (private)
      Creates a new readFns array, which stores the functions
      
  */
  
  initializeReadFns: function()
  {
    this.__readFns = [];
  },
  /*
    Function: addOnReadFn 
      Takes a function and adds it to the readFns array. 
      
    Parameters: 
      fn - A function.
      
  */
  
  addOnReadFn: function(fn)
  {
    this.__readFns.push(fn);
  },
  /*
    Function: clearOnReadFns
      Clears the readFns array.
      
  */
  
  clearOnReadFns: function()
  {
    this.__readFns.each(function(fn){fn();});
    this.__readFns = [];
  },
  /*
    Function: readIndex
      
    Parameters:
      index - An integer.
      constraint - An integer. 
      callback - A function.
  */
  
  readIndex: function(index, constraint, callback)
  {
    var theConstraint = {};
    theConstraint[constraint] = this.get(index)[constraint];
    this.transact('read', {
      table: this.table(),
      constraints: $merge(this.constraints(), theConstraint),
      properties: this.properties(),
      onComplete: function(data) {
        if(callback && data.length > 0) callback(data[0]);
      }.bind(this)
    });
  },
  /*
    Function: create 
      

    Parameters:
      data - An array.
      options - An array.
      
    Returns: 
      A payload object, an array of collection methods.
  */

  create: function(data, options)
  {
    return this.transact('create', {
      table: this.table(),
      values: data,
      onComplete: function(theId) {
        var newData = $merge(data, {id:theId});
        this.onCreate(newData, (options && options.userData));
        if(options && options.onCreate && $type(options.onCreate) == 'function') options.onCreate(newData);
      }.bind(this)
    });
  },
  
  /*
    Function: query
      Takes a predicate function and a list of properties to be returned for each matching item.
      
    Parameters:
      queryFn - a predicate function.
      properties - an array of string of the properties to be returned.
      
    Returns:
      An array of matching items containing only the request properties.
  */
  query: function(queryFn, properties)
  {
    // IE6 fix
    if(!this.getArray()) return;
    
    return this.getArray().filter(queryFn).map(function(obj) {
      return $H(obj).choose(properties).getClean();
    });
  },
  
  /*
    Function: indexWhere
      Returns the first index of the item matching the predicate fn.
      
    Parameters:
      fn - A predicate function.
      
    Returns:
      The index of the first matching item, or -1 if no match.
  */
  indexWhere: function(fn)
  {
    var ary = this.getArray();
    for(var i = 0, len = ary.length; i < len; i++)
    {
      if(fn(ary[i])) return i;
    }
    return -1;
  },
  
  /*
    Function: find
      Returns the first item matching the passed in predicated:
      
    Parameters:
      fn - a predicate function.
      
    Returns:
      The first item matching the predicate or null.
  */
  find: function(fn)
  {
    var index = this.indexWhere(fn);
    if(index == -1)
    {
      return null;
    }
    else
    {
      return this.get(index);
    }
  },
  
  
  'delete': function(index)
  {
    return this.transact('delete', {
      table: this.table(),
      constraints: $merge(this.constraints(), {
        id: this.get(index).id
      }),
      onComplete: function(data) {
        this.onDelete(data, index);
      }.bind(this),
      onFailure: function(data) {
        this.onFailure('delete', data, index);
      }.bind(this)
    });
  },
  
  
  update: function(data, index, bulk)
  {
    var indexConstraint = null;
    if(index != null) indexConstraint = {id: this.get(index).id};

    return this.transact('update', {
      table: this.table(),
      values: data,
      constraints: $merge(this.constraints(), indexConstraint),
      onComplete: function(rx) {
        this.onUpdate(data, index);
      }.bind(this),
      onFailure: function(data) {
        this.onFailure('delete', data, index);
      }.bind(this)
    }, bulk);
  },
  
  
  updateById: function(data, id, bulk)
  {
    return this.transact('update', {
      table: this.table(),
      values: data,
      constraints: $merge(this.constraints(), {id: id}),
      onComplete: function(rx) {
        this.onUpdateById(data, id);
      }.bind(this),
      onFailure: function(data) {
        this.onFailure('delete', data, index);
      }.bind(this)
    }, bulk);
  },
  /*
    Function: onFailure 
      ??
      
    Parameter:
      action -
      data - 
      index - 
  */
  onFailure: function(action, data, index)
  {
    
  },
  /*
    Function: onRead 
      
    Parameters:
      suppressEvent - NOTE: Not being used in the function. Is it Neccessary? - Justin 
  */  
  onRead: function(suppressEvent)
  {
    this.fireEvent('onLoad');
  },
  /*
    Function: onCreate 
      
    Paramters:
      data - An array.
      userData - An array.
      
    See Also:
      create
  */
  
  onCreate: function(data, userData)
  {
    this.fireEvent('onCreate', {data:data, userData:userData});
  },
  /*
    Function: onDelete
      Takes an index row in the collection and removes it from the array. Fires the onDelete event when called. 
      
    Parameters:
      data - An array. NOTE: unused. - Justin
      index - An integer.
      
    See Also:
      delete
  */
  onDelete: function(data, index)
  {
    // synchronize internal
    this.remove(index);
    this.fireEvent('onDelete', index);
  },
  /*
    Function: onUpdate
      Merges an array of data into the passed index in the collection array. Fires the onUpdate event when called. 
      
    Parameters:
      data - An array. NOTE: unused. - Justin
      index - An integer.
  */
  onUpdate: function(data, index)
  {
    this.__array[index] = $merge(this.__array[index], data);
    this.fireEvent('onUpdate', index);
  },
  /*
    Function: byId 
      Returns a row in the collection array specified by the passed id.
      
    Parameters:
      id - An integer.
    
    Returns:
       A row in the collection array.
  */
  
  byId: function(id)
  {
    return this.find(function(x){return x.id == id;});
  },
  /*
    Function: onUpdateById
      Merges an array of data into the passed id in the collection array. Fires the onUpdateById event when called.
      
    Parameters: 
      data - An array of data. 
      id - An integer.
  */
  
  onUpdateById: function(data, id)
  {
    var index = this.byId(id);
    this.__array[index] = $merge(this.__array[index], data);
    this.fireEvent('onUpdateById', index);
  },
  /*
    Function: each
      
      
    Parameters:
      fn - A function. 
  */
  
  each: function(fn)
  {
    this.__array.each(fn);
  },
  
  /*
    Function: map
      Takes a function and performs it on each row in the collection array. Returns an array containing the results of each function call. 
      ??
      
    Parameters:
      fn - A function.
  */
  map: function(fn)
  {
    var result = []
    var len = this.__array.length;
    for(var i = 0; i < len; i++)
    {
      result.push(fn(this.__array[i], i));
    }
    return result;
  },
  /*
    Function: updateConstraints
      Takes an array of constraints and a value, and update the constraints array.
      ??
      
    Parameters:
      constraint - An array.
      value - An integer.
  */
  updateConstraints: function(constraint, value)
  {
    this.setConstraints($merge(this.constraints(), constraint.assoc(value)));
  },
  /*
    Function: empty 
      Clears the collections array. 
  */
  empty: function()
  {
    this.setArray([]);
  },
  /*
    Function: reset
      Clears the collections array and sets the unread attribute of the collection to true. 
  */
  reset: function()
  {
    this.empty();
    this.setIsUnread(true);
  }

});

// End ../client/SSCollection.js ----------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSCollection');

// === END PACKAGE [ShiftSpaceCore] ===


// === START PACKAGE [ShiftSpaceCoreUI] ===

if(__sysavail__) __sysavail__.packages.push("ShiftSpaceCoreUI");

if (SSInclude != undefined) SSLog('Including ../client/SSView.js...', SSInclude);

// Start ../client/SSView.js --------------------------------------------------

// ==Builder==
// @uiclass
// @required
// @package           ShiftSpaceCoreUI
// @dependencies      SandalphonCore
// ==/Builder==


var SSView = new Class({

  name: 'SSView',

  Implements: [Events, Options],
  
  defaults: function()
  {
    SSLog('Returning defaults', SSLogViewSystem);
    var temp = {
      context: null,
      generateElement: true,
      suppress: false
    };
    return temp;
  },

  /*
    Function: _genId
      Generate an object id.  Used for debugging.  The instance is indentified by this in the global
      ShiftSpaceObjects hash.
  */
  _genId: function()
  {
    return (this.name+(Math.round(Math.random()*1000000+(new Date()).getMilliseconds())));
  },

  /*
    Function: initialize
      Takes an element and controls it.

    Parameters:
      el - a HTML Element.
  */
  initialize: function(el, options)
  {
    SSLog("Initialize SSView", SSLogViewSystem);
    if(el)
    {
      SSLog('Instantiating SSView with ' + el.getProperty('id'), SSLogMessage);
    }
    
    // we are new and we are dirty
    this.setNeedsDisplay(true);
    
    // get the options first
    this.setOptions(this.defaults(), (el && $merge(options, SSGetInlineOptions(el))) || {});
    // remove them
    if(el) el.removeProperty('options');

    // generate an id
    this.__id__ = this._genId();
    this.setIsAwake(false);
    
    // add to global hash
    if(ShiftSpaceObjects) ShiftSpaceObjects.set(this.__id__, this);
    
    SSLog('Interned view into ShiftSpaceObjects hash', SSLogViewSystem);

    // check if we are prebuilt
    //this.__prebuilt__ = (el && true) || false; // NOT IMPLEMENTED - David
    this.__ssviewcontrollers__ = [];
    this.__delegate__ = null;
    this.__outlets__ = new Hash();
    
    SSLog('SSView internal vars set', SSLogViewSystem);

    this.element = (el && $(el)) || (this.options.generateElement && new Element('div')) || null;
    
    if(this.element)
    {
      // NOTE: the following breaks tables, so we should avoid it for now - David
      //this.element.setProperty('class', 'ShiftSpaceElement '+this.element.getProperty('class'));

      // store a back reference to this class
      SSSetControllerForNode(this, this.element);

      // add to global name look up dictionary
      if(ShiftSpaceNameTable && this.element.getProperty('id').search('generatedId') == -1)
      {
        ShiftSpaceNameTable.set(this.element.getProperty('id'), this);
      }
    }

    // We need to build this class via code - NOT IMPLEMENTED - David
    /*
    if(!this.__prebuilt__)
    {
      this.build();
    }
    */

    this.__subviews__ = [];

    // Call setup or setupTest allowing classes to have two modes
    // For example, SSConsole lives in a IFrame under ShiftSpace
    // but not under the interface tool.
    if(typeof SandalphonToolMode != 'undefined' && this.setupTest)
    {
      this.setupTest();
    }
    else
    {
      this.setup();
    }
    
  },
  
  
  getContext: function()
  {
    return this.options.context;
  },
  
  
  setup: function() {},
  
  
  beforeAwake: function(context)
  {
    if(this.options.delegate)
    {
      this.setDelegate(SSControllerForNode($(this.options.delegate)));
    }
  },
  
  
  __awake__: function(context)
  {
    var superview = this.getSuperView(context);
    if(superview) 
    {
      superview.addEvent('onRefresh', function() {
        if(!this.isVisible()) return;
        if(!this.needsDisplay()) return;
        this.refreshAndFire();
      }.bind(this));
    }
  },
  
  
  /*
    Function: awake
      Called after the outlets have been attached.
  */
  awake: function(context)
  {
    SSLog(this.getId() + " awake, outlets " + JSON.encode(this.outlets().getKeys()));
  },
  
  
  setIsAwake: function(val)
  {
    this.__isAwake__ = val;
  },
  
  
  isAwake: function()
  {
    return this.__isAwake__;
  },


  /*
    Function: getId
      Returns the id for this instance.

    Returns:
      The instance id as a string.
  */
  getId: function()
  {
    return this.__id__;
  },
  
  
  elId: function()
  {
    return this.element.getProperty('id');
  },


  setOutlets: function(newOutlets)
  {
    this.__outlets__ = newOutlets;
  },


  outlets: function()
  {
    return this.__outlets__;
  },


  addOutlet: function(element)
  {
    var outletKey = element.getProperty('outlet');
    // check if there is a controller
    var controller = this.controllerForNode(element);
    this.outlets().set(element.getProperty('id'), (controller || element));
  },


  addOutletWithName: function(name, outlet)
  {
    SSLog('Setting name ' + name + ' for ' + outlet);
    this.outlets().set(name, outlet);
  },
  
  
  mapOutletsToThis: function()
  {
    this.outlets().each(function(object, name){ 
      this[name] = object;
    }.bind(this));
  },


  /*
    Function: setDelegate
      Set the delegate of this instance.

    Parameters:
      delegate - an Object.
  */
  setDelegate: function(delegate)
  {
    this.__delegate__ = delegate;
  },

  /*
    Function: delegate
      Returns the delegate for this instance.
  */
  delegate: function()
  {
    return this.__delegate__;
  },


  eventDispatch: function(evt)
  {
    
  },


  checkForMatch: function(_cands, node)
  {
    if(_cands.length == 0) return null;

    var cands = (_cands instanceof Array && _cands) || [_cands];

    var len = cands.length;
    for(var i = 0; i < len; i++)
    {
      if(cands[i] == node) return true;
    }

    return false;
  },


  /*
    Function: hitTest
      Matches a target to see if it occured in an element pointed to by the selector test.

    Parameters:
      target - the HTML node where the event originated.
      selectorOfTest - the CSS selector to match against.
  */
  hitTest: function(target, selectorOfTest)
  {
    var parts = selectorOfTest.split(',');
    if(parts.length > 1)
    {
      return parts.map(function(selector) {
        return this.hitTest(target, selector);
      }.bind(this)).flatten().clean()[0];
    }

    var node = target;
    var matches = this.element.getElements(selectorOfTest);

    while(node && node != this.element)
    {
      if(this.checkForMatch(matches, node))
      {
        this.setCachedHit(node);
        return node;
      }
      node = node.getParent();
    }

    return null;
  },

  /*
    Function: setCachedHit
      Used in conjunction with hitTest.  This is because hitTest may be slow, so you shouldn't have to call it twice.
      If there was a successful hit you should get it from cachedHit instead of calling hitTest again.

    See Also:
      hitTest, cachedHit
  */
  setCachedHit: function(node)
  {
    this.__cachedHit__ = node;
  },

  /*
    Function: cachedHit
      Returns the hit match that was acquired in hitTest.

    Returns:
      An HTML Element.
  */
  cachedHit: function()
  {
    return this.__cachedHit__;
  },


  indexOfNode: function(array, node)
  {
    var len = array.length;
    for(var i = 0; i < len; i++)
    {
      if(array[i] == node) return i;
    }
    return -1;
  },


  /*
    Function: controllerForNode
      Returns the view controller JS instance for an HTML Element.
  */
  controllerForNode: function(node)
  {
    //SSLog(('controllerForNode ' + node);
    // return the storage property
    return SSControllerForNode(node);
  },

  // will probably be refactored
  addControllerForNode: function(node, controllerClass)
  {
    // instantiate and store
    this.__ssviewcontrollers__.push(new controllerClass(node));
  },

  // will probably be refactored
  removeControllerForNode: function(node)
  {
    // get the controller
    var controller = SSControllerForNode(node);
    if(controller)
    {
      // clear out the storage
      SSSetControllerForNode(null, node);

      if(this.__ssviewcontrollers__.contains(controller))
      {
        // remove from internal array
        this.__ssviewcontrollers__.remove(controller);
      }
    }
  },

  /*
    Function: show
      Used to show the interface associated with this instance.
  */
  show: function()
  {
    this.element.addClass('SSActive');
    this.element.removeClass('SSDisplayNone');
    if(this.isVisible() && this.needsDisplay()) this.refreshAndFire();
  },
  

  /*
    Function: hide
      Used to hide the interface assocaited with this instance.
  */
  hide: function()
  {
    this.element.removeClass('SSActive');
    this.element.addClass('SSDisplayNone');
  },
  

  isVisible: function()
  {
    var curElement = this.element;
    while(curElement)
    {
      if(curElement.getStyle('display') == 'none') return false;
      curElement = curElement.getParent();
    }
    return true;
  },

  /*
    Function: destroy
      Used to destroy this instance as well as the interface associated with it.
  */
  destroy: function()
  {
    this.removeControllerForNode(this.element);
    this.element.destroy();
    delete this;
  },
  
  
  getSuperView: function(context)
  {
    var parent = this.element.getParent('*[uiclass]');
    if(parent) return SSControllerForNode(parent);
    return null;
  },
  

  /*
    Function: refresh (abstract)
      To be implemented by subclasses.
  */
  refresh: function()
  {
    
  },
  
  
  refreshAndFire: function()
  {
    this.refresh();
    this.fireEvent('onRefresh');
  },

  /*
    Function: build (abstract)
      To be implemented by subclasses.
  */
  build: function()
  {

  },
  
  
  setNeedsDisplay: function(val)
  {
    this.__needsDisplay = val;
  },
  
  
  needsDisplay: function()
  {
    return this.__needsDisplay;
  },


  localizationChanged: function(newLocalization)
  {
    SSLog('localizationChanged');
  }

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSView = SSView;


// End ../client/SSView.js ----------------------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSView');

if (SSInclude != undefined) SSLog('Including ../client/views/SSForm/SSForm.js...', SSInclude);

// Start ../client/views/SSForm/SSForm.js -------------------------------------

// ==Builder==
// @uiclass
// @required
// @package           ShiftSpaceCoreUI
// @dependencies      SSView
// ==/Builder==

var SSForm = new Class({

  Extends: SSView,

  name: "SSForm",

  initialize: function(el, options)
  {
    this.parent(el, options);
    // initialize the subforms
    this.initSubForms();
  },
  
  
  hide: function()
  {
    this.parent();
    if(this.options.firstSubForm)
    {
      this.showForm(this.options.firstSubForm);
    }
  },
  
  
  awake: function(context)
  {
    // set the delegate if there is one
    if(this.options.delegate)
    {
      this.setDelegate(SSControllerForNode($(this.options.delegate)));
    }
    
    // connect the anchors to their respective forms
    if(this.options.anchors)
    {
      $H(this.options.anchors).each(function(formName, anchorName) {
        this.element.getElementById(anchorName).addEvent('click', this.showForm.bind(this, [formName]));
      }.bind(this));
    }
  },

  
  initSubForms: function()
  {
    // initialize each sub form
    this.element.getElements('.SSSubForm').each(this.initSubForm.bind(this));
  },
  
  
  initSubForm: function(subForm)
  {
    // prevent default submit behavior
    subForm.addEvent('submit', function(_evt) {
      var evt = new Event(_evt);
      evt.stop();
      if(this.validateSubForm(subForm))
      {
        if(this.delegate() && this.delegate().onFormSubmit) this.delegate().onFormSubmit(subForm.getProperty('id'));
      }
    }.bind(this));
  },
  

  validateSubForm: function(subForm)
  {
    return true;
  },
  
  
  showForm: function(formName)
  {
    var subform = this.element.getElementById(formName);
    if(!subform.hasClass('SSActive'))
    {
      this.element.getElement('.SSActive').removeClass('SSActive');
      subform.addClass('SSActive');
    }
  }

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSForm = SSForm;


// End ../client/views/SSForm/SSForm.js ---------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSForm');

if (SSInclude != undefined) SSLog('Including ../client/views/SSCell/SSCell.js...', SSInclude);

// Start ../client/views/SSCell/SSCell.js -------------------------------------

// ==Builder==
// @uiclass
// @optional
// @package           ShiftSpaceCoreUI
// @dependencies      SSView
// ==/Builder==

// ==============
// = Exceptions =
// ==============

var SSCellError = SSException;

SSCellError.NoSuchProperty = new Class({
  name:"SSCellError.NoSuchProperty",
  Extends: SSCellError,
  Implements: SSExceptionPrinter
});

SSCellError.NoLock = new Class({
  name:"SSCellError.NoLock",
  Extends: SSCellError,
  Implements: SSExceptionPrinter
});

SSCellError.MissingAccessor = new Class({
  name:"SSCellError.MissingAccessor",
  Extends: SSCellError,
  Implements: SSExceptionPrinter
});

SSCellError.NoSuchTarget = new Class({
  name:"SSCellError.NoSuchTarget",
  Extends: SSCellError,
  Implements: SSExceptionPrinter
});

// ====================
// = Class Definition =
// ====================

var SSCell = new Class({

  name: 'SSCell',
  Extends: SSView,

  initialize: function(el, options)
  {
    this.parent(el, options);
    
    this.initActions();
    this.attachEvents();
    this.prepareClone();
    
    if(this.options.properties)
    {
      this.setPropertyList(this.options.properties);
    }
  },
  
  
  attachEvents: function()
  {
    this.element.addEvent('click', this.eventDispatch.bindWithEvent(this, 'click'));
  },
  
  
  setProxy: function(newProxy)
  {
    this.__proxy = newProxy;
  },
  
  
  proxy: function()
  {
    return this.__proxy;
  },


  forwardToProxy: function(methodName)
  {
    var proxy = this.proxy();
    if(proxy && proxy[methodName])
    {
      proxy[methodName](this);
    }
  },
  
  
  initActions: function()
  {
    if(this.options.actions)
    {
      this.setActions(this.options.actions);
    }
  },
  
  
  eventDispatch: function(_event, eventType)
  {
    var event = new Event(_event);
    
    var action = this.actionForNode(event.target);
    
    if(action)
    {
      this.runAction(action);
    }
    
    if(this.delegate() && this.delegate().onCellClick)
    {
      var cellNode = (event.target.get('tag') == 'li' && event.target) || event.target.getParent('li');
      this.delegate().onCellClick(cellNode);
    }
  },
  
  
  runAction: function(action, event)
  {
    var target = this.getBinding(action.target);
    var method = (target && target[action.method] && target[action.method].bind(target)) || 
                 (action.target == 'SSProxiedTarget' && this.forwardToProxy.bind(this, [action.method])) ||
                 null;
                  
    if(!method)
    {
      throw (new SSCellError.NoSuchTarget(new Error(), "target " + target + " does not exist."));
    }
    
    method(this, event);
  },
  
  
  getBinding: function(target)
  {
    // TODO: allow getBinding to access simple properties - David
    if(target == 'self') return this;
    
    var parts = target.split('.');
    var base = ShiftSpaceNameTable[parts.shift()];
    var result = base;
    if(parts.length < 1) return result;
    while(parts.length > 0)
    {
      var property = parts.shift();
      result = result['get'+property.capitalize()]();
    }
    return result;
  },
  
  
  actionForNode: function(node)
  {
    if(!this.lockedElement()) throw new SSCellError.NoLock(new Error(), "actionForNode called with no locked element.");
    var ary = this.getActions().filter(function(x) {
      return this.lockedElement().getElements(x.selector).contains(node);
    }.bind(this));
    if(ary.length > 0) return ary[0];
    return null;
  },
  
  
  setActions: function(actions)
  {
    this.__actions = actions;
  },
  
  
  getActions: function()
  {
    return this.__actions;
  },
  
  
  setPropertyList: function(propertyList)
  {
    this.__properties = propertyList;
  },
  
  
  getPropertyList: function()
  {
    return this.__properties;
  },
  
  
  verifyPropertyAccess: function()
  {
    this.getPropertyList().each(function(property) {
      if(!this['get'+property.capitalize()] || !this['set'+property.capitalize()])
      {
        throw new SSCellError.MissingAccessor(new Error(), "missing accessor for " + property);
      }
    }.bind(this));
  },
  
  
  setData: function(data)
  {
    var propertyList = this.getPropertyList();
    $H(data).each(function(value, property) {
      if(propertyList.contains(property)) this.setProperty(property, value);
    }.bind(this));
  },
  
  
  getData: function()
  {
    var args = $A(arguments);
    if((args.length == 1) && (args[0] instanceof Array))
    {
      args = $A(args[0]);
    }
    return args.map(this.getProperty.bind(this)).associate(args);
  },
  
  
  cleanData: function(data)
  {
    return $H(data).filter(function(value, key) {
      return value != null;
    }).getClean();
  },
  
  
  getAllData: function()
  {
    var data = {};
    this.getPropertyList().each(function(property) {
      data[property] = this.getProperty(property);
    }.bind(this));
    return this.cleanData(data);
  },
  
  
  setProperty: function(property, value)
  {
    if(!this.isLocked()) throw new SSCellError.NoLock(new Error(), "attempt to set property " + property + " without element lock.");
    if(!this.getPropertyList().contains(property)) throw new SSCellError.NoSuchProperty(new Error(), "no such property " + property);
    var setter = 'set'+property.capitalize();
    if(this[setter])
    {
      this[setter](value);
    }
  },
  
  
  getProperty: function(property)
  {
    if(!this.isLocked()) throw new SSCellError.NoLock(new Error(), "attempt to get property " + property + " without element lock.");
    if(!this.getPropertyList().contains(property)) throw new SSCellError.NoSuchProperty(new Error(), "no such property " + property);
    var getter = 'get'+property.capitalize();
    if(this[getter])
    {
      return this[getter]();
    }
    return null;
  },
  
  
  prepareClone: function()
  {
    var clone = this.element.clone(true);
    
    clone.removeClass('SSCell');
    clone.removeProperty('options');
    clone.removeProperty('uiclass');
    clone.removeProperty('outlet');
    
    this.__modelClone = clone;
  },
  
  /*
    Function: clone
      Creates a clone of the DOM model and returns it.
      
    Returns:
      A DOM node.
  */
  clone: function()
  {
    var clone = this.__modelClone.clone(true);
    
    if(clone.getElement('*[uiclass]'))
    {
      Sandalphon.activate(clone);
    }
    
    clone.addClass('SSCellClone');
    
    return clone;
  },
  
  /*
    Function: cloneWithData
      Creates a clone, locks it, modifies it's content
      and returns it.
      
    Returns:
      A DOM node.
  */
  cloneWithData: function(data)
  {
    var clone = this.clone();
    this.lock(clone);
    this.setData(data);
    this.unlock(clone);
    return clone;
  },
  
  
  lockedElement: function()
  {
    return this.__lockedElement;
  },

  /*
    Function: lock
      Lock the cell on a particular node.  Any setting of
      data on this controller will affect only that node.
      
    Parameters:
      element - a DOM node.
  */
  lock: function(element)
  {
    this.__lockedElement = element;
  },

  /*
    Function: lock
      Unlock this cell.
  */
  unlock: function()
  {
    this.__lockedElement = null;
  },

  /*
    Function: isLocked
      Returns the lock status of this cell.
      
    Returns:
      A boolean.
  */
  isLocked: function()
  {
    return this.__lockedElement != null;
  },


  getParentRow: function()
  {
    // TODO: related to SSTableView - not sure if this should be here.
    // probably should not because this.element is not in the DOM
    if(this.element) return this.element.getParent('.SSRow');
    return null;
  },
  
  
  edit: function()
  {
    var el = this.lockedElement();

    // show the edit view
    el.addClass('SSIsBeingEdited');
    if(el.getElement('.SSEditView')) el.getElement('.SSEditView').addClass('SSActive');
  },
  
  
  leaveEdit: function()
  {
    var el = this.lockedElement();
    
    // let the delegate know the edits were committed
    el.removeClass('SSIsBeingEdited');
    
    // FIXME: hmm seems hacky - David
    el.setStyles({
      width: ''
    });
    
    if(el.getElement('.SSEditView')) el.getElement('.SSEditView').removeClass('SSActive');
  }

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSCell = SSCell;


// End ../client/views/SSCell/SSCell.js ---------------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSCell');

if (SSInclude != undefined) SSLog('Including ../client/views/SSListViewCell/SSListViewCell.js...', SSInclude);

// Start ../client/views/SSListViewCell/SSListViewCell.js ---------------------

// ==Builder==
// @uiclass
// @optional
// @package           ShiftSpaceCoreUI
// @dependencies      SSView
// ==/Builder==

var SSListViewCell = new Class({

  Extends: SSCell,

  name: "SSListViewCell",

  initialize: function(el, options)
  {
    this.parent(el, options);
  },
  
  
  awake: function(context)
  {
    this.parentController = SSControllerForNode(this.element.getParent('.SSListView'));
    this.attachEvents();
  },
  
  
  attachEvents: function(attribute)
  {
    this.element.addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      if(this.parentController.canSelect(this))
      {
        this.parentController.selectByNode(this);
      }
    }.bind(this));
  }
  

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSListViewCell = SSListViewCell;


// End ../client/views/SSListViewCell/SSListViewCell.js -----------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSListViewCell');

if (SSInclude != undefined) SSLog('Including ../client/views/SSMultiView/SSMultiView.js...', SSInclude);

// Start ../client/views/SSMultiView/SSMultiView.js ---------------------------

// ==Builder==
// @required
// @uiclass
// @package           ShiftSpaceCoreUI
// @dependencies      SSView
// ==/Builder==

// ==============
// = Exceptions =
// ==============

var SSMultiViewError = SSException;

SSMultiViewError.NoSuchSubView = new Class({
  name: "SSMultiViewError.NoSuchSubView",
  Extends: SSMultiViewError,
  Implements: SSExceptionPrinter
});

SSMultiViewError.OutOfBounds = new Class({
  name: "SSMultiViewError.OutOfBounds",
  Extends: SSMultiViewError,
  Implements: SSExceptionPrinter
});

// =========
// = Class =
// =========

var SSMultiView = new Class({

  Extends: SSView,

  name: "SSMultiView",
  
  defaults: function()
  {
    return $merge(this.parent(), {
      subViewSelector: '.SSSubView'
    });
  },
  

  initialize: function(el, options)
  {
    this.parent(el, options);
    
    this.initPivots();
    
    // add subview class for CSS styling reasons
    if(this.options.subViewSelector != '.SSSubView')
    {
      this.getRawSubViews().each(function(x) { 
        if(!x.hasClass('SSSubView')) x.addClass('SSSubView'); 
      });
    }
  },
  
  
  hide: function()
  {
    this.parent();
    if(this.options.defaultView)
    {
      this.showViewByName(this.options.defaultView);
    }
  },
  
  
  initPivots: function(el, options)
  {
    if(this.options.pivots)
    {
      for(selector in this.options.pivots)
      {
        this.initPivot(selector, this.options.pivots[selector]);
      }
    }
  },
  
  
  initPivot: function(selector, view)
  {
    this.element.getElements(selector).addEvent('click', function(_evt) {
      
      var evt = new Event(_evt);
      if(!this.delegate() ||
         !this.delegate().canPivot ||
          this.delegate().canPivot(this))
         
      if($type(view) == 'number') this.showView(view);
      if($type(view) == 'string') this.showViewByName(view);
      
    }.bind(this));
  },
  
  
  getRawSubViews: function()
  {
    return this.element.getElements('> ' + this.options.subViewSelector);
  },
  
  
  getSubViews: function()
  {
    return this.getRawSubViews().map(function(x) { return SSControllerOrNode(x); });
  },
  
  
  getRawCurrentView: function()
  {
    return this.element.getElement('> ' + this.options.subViewSelector + '.SSActive');
  },
  

  getCurrentView: function()
  {
    return SSControllerOrNode(this.getRawCurrentView());
  },
  
  
  getIndexOfCurrentView: function()
  {
    return this.getRawSubViews().indexOf(this.getRawCurrentView());
  },
  
  
  getViewByIndex: function(idx)
  {
    return this.element.getElements('> ' + this.options.subViewSelector)[idx];
  },
  
  
  indexOfView: function(_view)
  {
    var view = (SSIsController(_view) && _view.element) || _view;
    return this.getRawSubViews().indexOf(view);
  },
  

  showView: function(idx)
  {
    // TODO: throw an error, if index too great! - David
    if(idx >= this.getRawSubViews().length)
    {
      throw new SSMultiViewError.OutOfBounds(new Error(), "index of view out of bounds.");
    }
    
    // hide the old view
    var el = this.getRawCurrentView();
    var controller = SSControllerForNode(el);
    var oldIndex = this.getIndexOfCurrentView();
    if(controller)
    {
      controller.hide();
    }
    else
    {
      el.removeClass('SSActive');
    }
    this.fireEvent('onViewHide', {multiView:this, index:oldIndex});
    
    // show the new view
    el = this.getViewByIndex(idx);
    controller = SSControllerForNode(el);
    if(controller)
    {
      controller.show();
    }
    else
    {
      el.addClass('SSActive');
    }
    this.fireEvent('onViewShow', {multiView:this, index:idx});
  },
  
  
  showViewByName: function(name)
  {
    SSLog('showViewByName ' + name, SSLogForce);
    if(!this.element.getElementById(name))
    {
      throw new SSMultiViewError.NoSuchSubView(new Error(), this.element.getProperty('id') + "'s controller has no subview with name " + name + ".");
    }
    this.showView(this.getRawSubViews().indexOf(this.element.getElement('> #'+name)));
  }

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSMultiView = SSMultiView;


// End ../client/views/SSMultiView/SSMultiView.js -----------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSMultiView');

if (SSInclude != undefined) SSLog('Including ../client/views/SSTabView/SSTabView.js...', SSInclude);

// Start ../client/views/SSTabView/SSTabView.js -------------------------------

// ==Builder==
// @uiclass
// @required
// @package           ShiftSpaceCoreUI
// @dependencies      SSView
// ==/Builder==

  /*
    ====================
    = Class Definition =
    ====================
    
    Class: SSTabView
      SSTabView controls the view and function of tabs within the console. 
  */
var SSTabView = new Class({
  
  name: 'SSTabView',
  
  Extends: SSView,
  
  initialize: function(el, options)
  {
    this.setOptions(this.defaults(), options)
    
    this.parent(el, options);
    
    this.__selectedTab__ = -1;
    
    // check for default tab
    var defaultActiveTab = this.element.getElement('> .SSControlView > .SSButton.SSActive');
    
    if(defaultActiveTab)
    {
      var idx = this.indexOfTab(defaultActiveTab);
      // force selection of default tab
      this.selectTab(idx);
      this.__selectedTab__ = idx;
    }
    
    // if none select the first
    if(this.__selectedTab__ == -1)
    {
      this.selectTab(0);
    }

    this.element.addEvent('click', this.eventDispatch.bind(this));
  },
  
  
  /*
    Function: eventDispatch (private)
      Dispatches the selectTab event when tab when hit. selectTab is only called if the SSControlView class name is not null. 
      
    Paremeters:
      evt - A DOM node event 

    
  */
  eventDispatch: function(evt)
  {
    //SSLog('eventDispatch');

    var theEvent = new Event(evt);
    var theTarget = $(evt.target);
    
    switch(true)
    {
      case (this.hitTest(theTarget, '> .SSControlView') != null):
        var hit = this.hitTest(theTarget, '> .SSControlView .SSButton');
        if(hit) this.selectTab(this.indexOfTab(hit));
      break;
      
      default:
      break;
    }
  },
  
  /*
    Function: indexOfTabByName
      Takes the tab's class name and returns its index
    
    Pararmeter:
      name - class name of tab
    
    Return:
      Returns the index of a tab node if a contentView or tabPane name exists
      If a tab name doesn't exist, return -1
    
    See Also: 
      indexOfTab
  
  */
  
  indexOfTabByName: function(name)
  {
    var tab = this.element.getElement('> .SSControlView #'+name);
    
    // return tab index if we have it
    if(tab)
    {
      return this.indexOfTab(tab);
    }
    
    tab = this.element.getElement('> .SSContentView #'+name);
    
    // return content view index if we have it
    if(tab)
    {
      return this.indexOfContentView(tab);
    }
    
    // we couldn't find it
    return -1;
  },
  
  /*
    Funtion: indexOfTab
      Takes the class name of a tab and returns the tab's node index 
    
    Parameters:
      tabButton -  SSButton class name of tab button
    
    Returns:
      Index node of tab 
      
    See Also:
      indexOfTabByName 
      
      
  */
  indexOfTab: function(tabButton)
  {
    return this.indexOfNode(this.element.getElements('> .SSControlView > .SSButton'), tabButton);
  },
  
  /*
    Funtion: tabButtonForIndex
      Takes the index of tab and returns its DOM node
    
    Parameters:
      idx - index of tab button 
    
    Returns:
      DOM node of tab
      
    See Also: 
      tabButtonForName
      
  */
  tabButtonForIndex: function(idx)
  {
    return this.element.getElements('> .SSControlView > .SSButton')[idx];
  },
  
  /*
    Funtion: tabButtonForName
      Takes the class name of tab and returns its DOM node 
    
    Parameters:
      name - id name of tab button 
    
    Returns:
      DOM node of tab
      
    See Also: 
      tabButtonForIndex
      
  */
  tabButtonForName: function(name)
  {
    return this.element.getElement('> .SSControlView #'+name);
  },
  
  /*
    Funtion: indexOfContentView
      Takes the class name of contentView Div and returns its index
    
    Parameters:
      contentView - class name of contentView Div
    
    Returns:
      Index of contentView Div
      
    See Also: 
      indexOfTab
      
  */
  indexOfContentView: function(contentView)
  {
    return this.indexOfNode(this.element.getElements('> .SSContentView > .SSTabPane'), contentView);
  },
  
  /*
    Funtion: contentViewForIndex
      Takes the index of a SSContentView Div and returns its DOM node
    
    Parameters:
      idx - index of Tab
    
    Returns:
      DOM node of SSContentView
      
    See Also: 
      indexOfContentView
      
  */
  contentViewForIndex: function(idx)
  {
    return this.element.getElements('> .SSContentView > .SSTabPane')[idx];
  },
  
  /*
    Funtion: selectTabByName
      Selects a tab by its name
      
    Parameters:
      name - class name of Tab 
      
    See Also: 
      selectTab
  */
  selectTabByName: function(name)
  {
    this.selectTab(this.indexOfTabByName(name));
  },
  
  /*
    Function: selectedContentView
      Checks the currently selected tab's for a controller. Returns the selected tab's controller if it exsists, or else returns the contentView. 
      
    Returns: 
      Selected tab's controller or contentView
   */
  selectedContentView: function()
  {
    // grab the DOM node
    var contentView = this.contentViewForIndex(this.__selectedTab__);
    // check for a controller
    var controller = this.controllerForNode(contentView);
    return (controller || contentView);
  },
  
  
  /*
    Function: selectedTab
      Returns the currenlty selected tab
      
    Returns: 
      Currently selected tab
    
    See Also:
      SelectTab
      
  */
  selectedTab: function()
  {
    return this.__selectedTab__;
  },
  
  /*
    Function: selectTab
      Takes the index of a tab, makes it active, and displays content of the new selected tab if it exists. Removes the active class from the previously selected tab. 
    
    Parameters:
      idx - index of Tab
  */
  selectTab: function(idx)
  {
    SSLog(this.element.getProperty('id') + ' selectTab ' + idx);
    
    if(this.__selectedTab__ != idx)
    {
      // hide the last tab button and tab pane only if there was a last selected tab
      if(this.__selectedTab__ != -1)
      {
        this.tabButtonForIndex(this.__selectedTab__).removeClass('SSActive');

        // hide the last tab pane
        var lastTabPane = this.contentViewForIndex(this.__selectedTab__);
        //SSLog('controller for last tab ' + lastTabPane + ' ' + $uid(lastTabPane));
        var lastTabPaneController = this.controllerForNode(lastTabPane);
        SSLog('got controller');
        SSLog(lastTabPaneController);

        if(lastTabPaneController)
        {
          lastTabPaneController.hide();
        }
        else
        {
          lastTabPane.removeClass('SSActive');
        }
        
        this.fireEvent('tabDeselected', {tabView:this, tabIndex:this.__selectedTab__});
      }

      // check to see if there is a view controller for the content view
      var controller = this.contentViewControllerForIndex(idx);
      SSLog('>>>>>>>>>>>>>>>>>>>>>>>> getting tab content view controller');
      SSLog(controller);
      if(controller)
      {
        //SSLog('showing controller');
        controller.show();
      }
      else
      {
        this.contentViewForIndex(idx).addClass('SSActive');
      }
      
      SSLog('Activating tab button');
      SSLog(this.tabButtonForIndex(idx));
      // hide the tab button
      this.tabButtonForIndex(idx).addClass('SSActive');
      
      this.__selectedTab__ = idx;
      
      //SSLog('fire tabSelected');
      this.fireEvent('tabSelected', {tabView:this, tabIndex:this.__selectedTab__});
      //SSLog('exit tabSelected');
    }
    else
    {
      SSLog('Tab already selected');
      this.fireEvent('tabClicked', {tabView:this, tabIndex:idx});
    }
  },
  
  /*
    Function: addTab
      Creates a new tab and applies the passed argument to its id name. 
      
    Parameters:
      name - Name of the new tab
      
      
  */
  addTab: function(name)
  {
    var tabButton = new Element('div', {
      'id': name,
      'class': "SSButton"
    });
    tabButton.set('text', name);
    var tabContent = new Element('div', {
      'class': 'SSTabPane'
    });
    
    tabButton.injectInside(this.element.getElement('> .SSControlView'));
    tabContent.injectInside(this.element.getElement('> .SSContentView'));
  },
  
  /*
    Function: contentViewControllerForIndex
      Takes the index of a contentView and returns the controller's DOM node
      
    Parameters:
      idx - index of contentView 
      
    Returns:
      DOM node of controller 

  */
  contentViewControllerForIndex: function(idx)
  {
    return this.controllerForNode(this.contentViewForIndex(idx));
  },
  
  /*
    Function: activeTab
      Returns the index of the currently active tab
      
    Returns:
      Index of tab
  */
  activeTab: function()
  {
    return this.indexOfTab(this.element.getElement('> .SSControlView > .SSButton.SSActive'));
  },
  
  /*
    Function: hideTabByName
      Takes a tab's id name and hides the tab.
    
    Parameters:
      name - Tab id name
      
    See Also:
      hideTab
  */
  hideTabByName: function(name)
  {
    this.hideTab(this.indexOfTabByName(name));
  },
  
  /*
    Function: hideTab
      Takes a tab's node index and hides the tab.
    
    Parameters:
      index - Tab node index
      
    See Also:
      hideTabByName
  */
  hideTab: function(index)
  {
    this.tabButtonForIndex(index).addClass('SSDisplayNone');
    this.contentViewForIndex(index).addClass('SSDisplayNone');
  },
  
  /*
    Function: revealTabByName
      Takes a tab's id name and reveals the tab.
    
    Parameters:
       name - Tab id name
      
    See Also:
      revealTab
  */
  revealTabByName: function(name)
  {
    this.revealTab(this.indexOfTabByName(name));
  },
  
  /*
    Function: revealTab
      Takes a tab's node index and reveals the tab.
    
    Parameters:
       name - Tab node index
      
    See Also:
      revealTabByName
  */
  revealTab: function(index)
  {
    this.tabButtonForIndex(index).removeClass('SSDisplayNone');
    this.contentViewForIndex(index).removeClass('SSDisplayNone');
  },

  /*
    Function: removeTabByName
      Takes a tab's id name and removes the tab.
    
    Parameters:
       name - Tab id name
      
    See Also:
      removeTab
  */
  removeTabByName: function(name)
  {
    this.removeTab(this.indexOfTabByName(name));
  },

  /*
    Function: removeTab
      Takes a tab's node index and removes the tab and its controller. If the currently selected tab is being removed, the first tab is selected.
    
    Parameters:
       idx - Tab node index 
      
    See Also:
      removeTabByName
  */
  removeTab: function(idx)
  {
    // if removing selected tab, highlight a different tab
    if(this.activeTab() == idx)
    {
      this.selectTab(0);
    }
    
    // remove tab button
    this.tabButtonForIndex(idx).dispose();

    // Remove the controller
    var contentView = this.contentViewForIndex(idx);
    var controller = this.controllerForNode(contentView);
    
    if(controller)
    {
      // destroy the controller
      controller.destroy();
    }
    else
    {
      // remove the DOM element
      contentView.dispose();
    }
  },
  
  /*
    Function: refresh
      Resizes the the SSContentView and SSControlView if they contain the autosize property
  */
  refresh: function()
  {
    var theControlView = this.element.getElement('> .SSControlView');
    var theContentView = this.element.getElement('> .SSContentView');
    
    // resize content view if it's supposed to autoresize
    if(theContentView.getProperty('autoresize'))
    {
      var size = this.element.getSize();
      var controlSize = theControlView.getSize();
    }
    
    // refresh the selected content view as well
    var contentView = this.selectedContentView();
  }
  
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSTabView = SSTabView;


// End ../client/views/SSTabView/SSTabView.js ---------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSTabView');

if (SSInclude != undefined) SSLog('Including ../client/views/SSListView/SSListView.js...', SSInclude);

// Start ../client/views/SSListView/SSListView.js -----------------------------

// ==Builder==
// @uiclass
// @optional
// @name              SSListView
// @package           ShiftSpaceCoreUI
// @dependencies      SSView, SSCell
// ==/Builder==

// ==============
// = Exceptions =
// ==============

var SSListViewError = SSException;

SSListViewError.OutOfBounds = new Class({
  name:"SSListViewError.OutOfBounds",
  Extends: SSListViewError,
  Implements: SSExceptionPrinter
});

// ====================
// = Class Definition =
// ====================
  /*
    Class: SSListView
      SSListView controls the display of Collection content within the console. 
      
  */

var SSListView = new Class({
  name: "SSListView",
  
  Extends: SSView,
  
  defaults: function()
  {
    return $merge(this.parent(), {
      cell: null,
      sortable: false,
      lazy: false,
      multipleSelection: false,
      horizontal: false,
      cellSize: null,
      filter: null,
      addAt: 'bottom',
      leaveEditOnUpdate: false
    });
  },
  
  initialize: function(el, options)
  {
    this.parent(el, options);
    
    this.__cellBeingEdited = -1;
    
    this.setSuppressRefresh(false);
    
    if(this.options.filter) this.setFilter(this.options.filter);
    
    if(this.options.collection)
    {
      this.useCollection(this.options.collection);
    }
    else
    {
      this.setData([]);
    }
    
    this.initSortables();
    this.attachEvents();
  },
  
  
  setPageControl: function(pageControl)
  {
    this.__pageControl = pageControl;
  },
  
  
  pageControl: function()
  {
    return this.__pageControl;
  },
  
  
  dataIsReady: function()
  {
    if(this.hasCollection())
    {
      return (this.data() && !this.data().isUnread()) || false;
    }
    else
    {
      return true;
    }
  },

  /*
      Function: setFilter
        Sets the filter as a function.
      
      Parameters: 
        fn - A function.
      
      See Also: 
        getFilter
        filter
  */  
  setFilter: function(fn)
  {
    this.__filter = fn;
  },
  
  /*
    Function: getFilter
      Returns the current filter.
      
    Returns: 
      A function.
      
    See Also: 
      setFilter
      filter
  */
  getFilter: function()
  {
    return this.__filter;
  },
  
  /*
    Function: filter
      Returns true if the filter is set. 
      
    Parameters: 
      data - a row in a javascript array. //NOTE:The name data is a bit ambigious. rowData maybe? -Jusitn
        
    Returns:
      A boolean value
    
    See Also:
      setFilter
      getFilter
  */
  filter: function(data, index)
  {
    var filterFn = this.getFilter();
    
    if(filterFn)
    {
      return filterFn(data, index);
    }
    return false;
  },
  
  /*
    Function: setHasCollection
      Sets the hasCollection property.
      
    Parameters:
      val - a boolean value
      
    See Also: 
      hasCollection
  */
  setHasCollection: function(val)
  {
    this.__hasCollection = val;
  },
  
  /*
    Function: hasCollection
      Returns a the boolean value of hasCollection 
    
    Returns:
      A boolean value.
      
    See Also: 
      setHasCollection
  */
  hasCollection: function()
  {
    return this.__hasCollection;
  },
  
  /*
      Function: initSortables (private)
        Called during intialize(). Creates a new sortable object.   
  
  */
  initSortables: function()
  {
    if(this.options.sortable)
    {
      // destroy any previous sortables
      if(this.__sortables)
      {
        this.__sortables.detach();
        delete this.__sortables;
      }
      
      this.__sortables = new Sortables(this.element, {
        constrain: true,
        clone: true,
        snap: 4,
        revert: true,
        onStart: function(cellNode) {
          this.__sortables.clone.addClass('Clone');
          this.sortStart(cellNode);
        }.bind(this),
        onSort: this.sortSort.bind(this),
        onComplete: this.sortComplete.bind(this)
      });
    }
  },
  
  /*
      Function: sortStart
        Sets the sortStart property to the index of a cell node. Determines the starting point for a sort.  
                  
      Parameters:
       cellNode - a cell's DOM node
  */
  sortStart: function(cellNode)
  {
    this.__sortStart = this.cellNodes().indexOf(cellNode);
  },
  
  /*
    Function: sortSort (abstract)
      Sorts changed hooks.  
      
    Note: 
      This name needs to be change. sortChange?  -Justin
  */
  sortSort: function(cellNode)
  {
    this.__sortCurrent = this.cellNodes().indexOf(cellNode);
  },
  
  /*
    Function: sortComplete
      Calls the move function and sorts an array from sortStart to the passed cell node.
      
    Parameters:
     cellNode - a cell's DOM node. Determines where the sort ends. 
     
    See Also:
      move
  */
  sortComplete: function(cellNode)
  {
    this.__sortEnd = this.cellNodes().indexOf(cellNode);
    
    this.fireEvent('onSortComplete');
    
    if(this.__sortStart != undefined &&
       this.__sortEnd != undefined &&
       this.__sortStart != this.__sortEnd)
    {
      this.fireEvent('onOrderChange', {
        listView: this, 
        start: this.__sortStart, 
        end: this.__sortEnd
      });
    }
    
    // clear the state vars
    this.__sortStart = undefined;
    this.__sortCurrent = undefined;
    this.__sortEnd = undefined;
  },
  
  /*
    Function: attatchEvents (private)
      Called by the initialize function.  Adds an event that calls eventDispatch on a click event. 
      
  */
  attachEvents: function()
  {
    this.element.addEvent('click', this.eventDispatch.bind(this));
  },
  
  /*
    Function: eventDispatch (private)
      Called on click event. 
  
    Parameters:
      _event - the event issueing the function. Always a "click" event. 
      eventType - //NOTE: I'm not sure what this argument means. -Justin
  */
  eventDispatch: function(_event, eventType)
  {
    var event = new Event(_event);
    var target = event.target;
    
    switch(true)
    {
      case(this.hitTest(target, 'li, > li *') != null):
        var hit = this.cachedHit();
        this.cell().lock((hit.get('tag') == 'li' && hit) || hit.getParent('li'));
        this.cell().eventDispatch(event, eventType);
        this.cell().unlock();
      break;
      
      default:
      break;
    }
    
    event.stop();
  },

  /*
      Function: awake
        If a cell has content, set the cell's content to the assigned context.
      
      Parameters:
        context - The context a object was created for. Either a window, element, or iframe.
  */
  awake: function(context)
  {
    var cellNode = this.element.getElement('> .SSCell');
    if(cellNode)
    {
      this.setCell(SSControllerForNode(cellNode));
    }
  },
  
  /*
      Function: setCell
        Sets the cell object, and sets a delegate instance of the cell. 

      Parameters:
        cell - A cell object.
        
      See also:
        cell
  */
  setCell: function(cell)
  {
    this.__cell = cell;
    cell.setDelegate(this);
    cell.element.dispose();
  },
    
  /*
      Function: cell
        Returns the cell object.

      Returns:
        A cell ojbecct
        
      See also:
        setCell
  */  
  cell: function()
  {
    return this.__cell;
  },

  /*
    Function: setData
      Sets the data property of the class. 
    
    Parameters:
      newData - A javascript array row.
    
    See Also:
      getData
      data
  */
  setData: function(newData)
  {
    this.__data = newData;
    //MARKED FOR DELETION - add.view method is never being used -Justin
    if(newData.addView)
    {
      newData.addView(this);
    }
    //END OF DELETION
  
    this.setNeedsDisplay(true);
  },
    
  /*
      Function: data
        Returns the data property. 
      
      Returns:
        A javascript array row. 

      See Also:
        setData
        getData
  */
  data: function()
  {
    if(this.__pendingCollection) this.checkPendingCollection();
    return this.__data;
  },
  
  /*
     Note:
      MARKED FOR DELETION: Redundant function, see data() above -Justin
  */
  getData: function()
  {
    return this.data();
  },
  
  /*
    Function: checkPendingCollection 
      If a pending collection exists, delete the current one and reassign it. 
      
  */
  checkPendingCollection: function()
  {
    var coll = SSCollectionForName(this.__pendingCollection);
    if(coll)
    {
      delete this.__pendingCollection;
      this.__addEventsToCollection__(coll);
      this.setData(coll);
    }
  },
  
  /*
      Function: rawData
        Returns the data property of the class.  
      
      Returns:
        A row in a javascript array. 
        
      Note:
        Internal is a possible future implementation. 
   */
  rawData: function()
  {
    var data = this.data();
    if(data.internal) return data.internal();
    return data;
  },
  
  /*
      Function: count
         //NOTE: See TODO in function. -Justin  
         
      Returns:
        The length of a row in a Javascript array. 
  */
  count: function()
  {
    // TODO: not sure about the bounds checking in SSListView, this should probably be put into SSCollections - David
    if($type(this.data().length) == 'function') return this.data().length();
    return this.data().length;
  },
  
  /*
      Function: find
        Returns a 0 if a row in a raw data array is found in a passed function, otherwise returns -1.
        
      Parameters:
        fn - A function
        
      Returns:
        An integer 
        
      See Also:
        findAll
  */
  find: function(fn)
  {
    var data = this.rawData();
    for(var i = 0, l = data.length; i < l; i++) if(fn(data[i])) return i;
    return -1;
  },
  
  /*
      Function: findAll
        Returns an array containing all of the found raw data rows in a passed function. 
      
      Parameters:
        fn - A function
        
      Returns:
        An array
    
      See Also:
        find   
  */
  findAll: function(fn)
  {
    var data = this.rawData();
    var result = [];
    for(var i = 0, l = data.length; i < l; i++) if(fn(data[i])) result.push[1];
    return result;
  },
  
  /*
      Function: query
        Accepts an index of a collection item and argument to search for in a function. Returns the argument value(s) in a string or array, othewise returns null. 
      
      Parameters:
        index - the index of a SSCell object
        arg   - An argument of a function. 
        
      Returns:
        An string, array or null.
        
  */
  query: function(index, arg)
  {
    if($type(arg) == 'string') return this.get(index)[arg];
    if($type(arg) == 'array')
    {
      var data = this.get(index);
      var result = {};
      arg.each(function(prop) {
        result[prop] = data[prop];
      });
      return result;
    }
    return null;
  },
  
  /*
    Function: cellNodes
      Returns all the listed cell nodes of an element.
      
    Returns:
      A group of list elements
  */
  cellNodes: function()
  {
    return this.element.getElements('> li');
  },
  
  /*
      Function: add
        Adds an object, that is specified with the newItem argument, to a collection. The _animate argument determines if an animation occurs during function call.
        
      Parameters:
        newItem  - a javascript object
        options - an object, can contain animate boolean as well as atIndex value.
  */
  add: function(newItem, options)
  {
    var animate = ((!options || options.animate == null) && true) || options.animate;

    var delegate = this.delegate();
    var canAdd = (delegate && delegate.canAdd && delegate.canAdd(this)) || true;
    
    if(canAdd)
    {
      // grab extra data, not completely sure why we need this here - David
      var addData = (delegate.dataFor && delegate.dataFor('add', this));
      
      if(this.hasCollection())
      {
        this.getData()['create']($merge(newItem, addData), {
          userData:
          {
            atIndex: (options && options.atIndex)
          }
        });
      }
      else
      {
        if(this.options.addAt == 'bottom') this.data().push(newItem);
        if(this.options.addAt == 'top') this.data().unshift(newItem);
        this.refresh();
      }
    }
  },
  
  /*
      Function: onAdd (private)
        Callback event when a new Item is added to a collection. 
    
      Parameters:
        data - A row in a javascript array.
    
  */
  onAdd: function(data, userData)
  {
    // leave editing a cell if it's being edited
    if(this.cellBeingEdited() != -1)
    {
      this.cancelEdit(this.cellBeingEdited(), false);
    }
    
    var filtered = false;
    if(userData && userData.atIndex != null) filtered = this.filter(data, userData.atIndex);

    var delegate = this.delegate();
    var anim = (!filtered &&
                delegate &&
                delegate.animationFor && 
                delegate.animationFor({action:'add', listView:this, userData:data})) || false;
    
    if(anim)
    {
      var animData = anim();
      animData.animation(function() {
        // refreshing content
        if(animData.cleanup)
        {
          animData.cleanup();
        }
        this.refresh(true);
      }.bind(this));
    }
    else
    {
      this.refresh(true);
    }
    
    this.fireEvent('onAdd', data);
  },
  
  /*
    Function: addObject
      Adds an object to a collection. The sender argument specifies the object to add. Intended to be used for event handling.
      
    Parameters:
      sender -  An HTML element. (SSCell)
      
    See Also:
      add
  */
  addObject: function(sender, options)
  {
    this.add(sender.dataForNewItem(), options);
  },
  
  /*
    Function: edit
      Accepts the index of a cell in a collection and allows it to be edited (if permitted). The _animate argument determines if an animation occurs during function call.
      
    Parameters:
      index - the index of a SSCell object. 
      _animate - A boolean value.
  
  */
  edit: function(index, _animate)
  {
    var animate = (_animate == null && true) || _animate;
    this.boundsCheck(index);
    
    var delegate = this.delegate();
    var canEdit = (delegate && delegate.canEdit && delegate.canEdit(index)) || true;
    
    if(canEdit)
    {
      if(!this.options.multipleSelection && this.cellBeingEdited() != -1)
      {
        animate = false;
        this.cancelEdit(this.cellBeingEdited(), false);
      }
      
      var anim = (animate && 
                  delegate && 
                  delegate.animationFor && 
                  delegate.animationFor({action:'edit', listView:this, index:index})) || false;
      
      var editModeForCell = function() {
        this.setCellBeingEdited(index);
        this.cell().lock(this.cellNodeForIndex(index));
        this.cell().edit();
        this.cell().unlock();
      }.bind(this);
      
      if(anim)
      {
        var animData = anim();
        animData.animation().chain(function() {
          if(animData.cleanup) animData.cleanup();
          editModeForCell();
        }.bind(this));
      }
      else
      {
        editModeForCell();
      }
    }
  },
  
  /*
      Function: insert
        Inserts data into a cell at a specified index and refreshes collection. 
      
      Parameters:
        cellData - An object.
        index - the index of a SSCell object
  */  
  insert: function(cellData, index)
  {
    this.boundsCheck(index);
    this.__insert__(cellData, index);
    this.refresh();
  },
  
  /*
      Function: __insert__  (private)
        Inserts data into a cell at a specified index.
      
      Parameters:
        cellData - An object.
        index - the index of a SSCell object. 
  */
  __insert__: function(cellData, index)
  {
    if(this.data().insert)
    {
      this.data().insert(cellData, index);
    }
    else
    {
      this.data().splice(index, 0, cellData);
    }    
  },
  
  /*
    Function: get 
      Accepts an index of cell in a collection  and performs a boundsCheck to make sure the index is valid. Retreives the propeties of each data element, stores them in an array, and returns the array. 
      
    Parameters:
      index - the index of a SSCell object. 
      
    Returns: 
      An array. 
  */
  get: function(index)
  {
    this.boundsCheck(index);
    
    var copy = {};
    var data = this.__get__(index);
    for(prop in data)
    {
      copy[prop] = data[prop];
    }
    return copy;
  },
  
  /*
    Function: get (private)
     Accepts the index of cell in a colletion and calls the returns the cells data in an array.
     
    Parameters: 
      index - the index of a SSCell object.
      
    Returns:
      An array
      */
  __get__: function(index)
  {
    if(this.data().get)
    {
      return this.data().get(index);
    }
    else
    {
      return this.data()[index];
    }
  },
  
  /*
      Function: update  
        Updates a collection's content with the passed cellData at the specified index. Accepts the current data, the index of the collection to update, and whether 
    
      Parameters:
        cellData - An object.
        index - the index of a SSCell object
        _noArrayUpdate - A boolean.
  */
  update: function(cellData, index, _noArrayUpdate)
  {
    this.boundsCheck(index);
    
    var noArrayUpdate = _noArrayUpdate || false;
    var delegate = this.delegate();
    var canUpdate = (delegate && delegate.canUpdate && delegate.canUpdate(index)) || true;
    
    if(canUpdate)
    {
      if(this.hasCollection())
      {
        if(!noArrayUpdate) this.getData().update(cellData, index);
      }
      else
      {
        if(!noArrayUpdate) this.__update__(cellData, index);
        this.onUpdate(index);
      }
    }

    if(this.options.leaveEditOnUpdate)
    {
      var canLeaveEdit = (this.canLeaveEdit && this.canLeaveEdit(index)) || true;
      if(canLeaveEdit) this.cell().leaveEdit();
    }
    
  },
  
  /*
    Function: updateObject 
      Accepts a SSCell object in a collection and updates it.
      
    Parameters:
      sender -  An HTML element. (SSCell)
  */
  updateObject: function(sender)
  {
    var index = this.indexOf(sender);
    this.update(this.cell().getAllData(), index);
  },
  
  /*
    Function: updateCellView 
      Accepts a cell's index in a collection array, and updates the cell's view with new cell data. 
      
    Parameters: 
      cellData - An Object.
      index - the index of a SSCell object.
  */
  updateCellView: function(cellData, index)
  {
    this.cell().lock(this.cellNodeForIndex(index));
    this.cell().setData(cellData);
    this.cell().unlock();
  },
  
  /*
    Function: __update__ (private)
      Accepts cell data and a cell's index. Merges the new cell data with the existing data of a specified cell in a collection.
        
    Parameters: 
      cellData - An Object. 
      index - the index of a SSCell object. 
      
  */
  __update__: function(cellData, index)
  {
    var oldData =this.data()[index];
    this.__set__(oldData.merge(cellData), index);
  },
  
  /*
    Function: onUpdate 
      Accepts the index of cell in a collection, checks if an animaton should be applied, and refreshes it.
    
    Parameter: 
      index - the index of a SSCell object. 
      
    //NOTE: animation support to be implemented -Justin
  */
  onUpdate: function(index)
  {

    var delegate = this.delegate();
    var anim = (delegate && 
                delegate.animationFor && 
                delegate.animationFor({action:'update', listView:this, index:index})) || false;
    
    if(anim)
    {
      anim().chain(this.refresh.bind(this));
    }
    else
    {
      this.refresh();
    }
  },
  
  /*
    Function: set
      Accepts cell data and a cell index, and applies the data to the specified cell after performing a bounds check.
      
    Parameters: 
      cellData - An object. 
      index - the index of a SSCell object.
  */
  set: function(cellData, index)
  {
    this.boundsCheck(index);
    this.__set__(cellData, index);
  },
  
  /*
    Function: __set__ (private)
      Accepts cell data and a cell index, and applies the data to the specified cell.
    
    Parameters: 
      cellData - An object. 
      index - the index of a SSCell object.
  */
  __set__: function(cellData, index)
  {
    this.data()[index] = cellData;
  },
  
  /*
      //MARKED FOR DELETION -Justin
    
  */
  move: function(fromIndex, toIndex)
  {
    this.boundsCheck(fromIndex);
    this.boundsCheck(toIndex);
    this.__move__(fromIndex, toIndex);
    this.refresh();
  },
  
  /*
      //MARKED FOR DELETION -Justin
      
  */
  __move__: function(fromIndex, toIndex)
  {
    if(this.data().move)
    {
      this.data().move(fromIndex, toIndex);
    }
    else
    {
      var data = this.get(fromIndex);
      this.__remove__(fromIndex);
      this.__insert__(data, toIndex);
    }
  },
  

  /*
    Function: remove
      Accepts an cell index, and removes the cell from the collection
      
    Parameter:
      index - the index of a SSCell object.
      
    See Also:
      removeObject
      
    //NOTE: ability to remove a cell with and without using collections needs to be redesigned.
  */
  // TODO: animation support
  remove: function(index)
  {
    this.boundsCheck(index);

    var delegate = this.delegate();
    
    var canRemove = true;
    if(delegate && delegate.canRemove)
    {
      canRemove = delegate.canRemove({listView:this, index:index});
    } 

    if(canRemove)
    {
      if(this.hasCollection())
      {
        this.getData()['delete'](index);
        return;
      }
      else
      {
        this.__remove__(index);
      }
    }
  },
  
  /*
    Function: __remove__ (private)
      Accepts a cell's index and removes it from the array.
      
    Parameters:
      index - the index of a SSCell object. 
  */
  __remove__: function(index)
  {
    this.data().splice(index, 1);
  },
  
  /*
    Function: removeObject
      Accepts a cell element and removes it from a collection.
    
    Parameters:
      sender -  An HTML element. (SSCell)
      
    See Also:
      remove
  */
  removeObject: function(sender)
  {
    var index = this.indexOf(sender);
    this.remove(index);
  },
  
  /*
    Function: onRemove (private)
      Checks to see if an animation should be applied after removing a cell, and then calls refresh.
    
    Parameters:
      index - the index of a SSCell object.
      
    //NOTE: animation support to be implemented -Justin
  */
  onRemove: function(index)
  {
    var delegate = this.delegate();
    var anim = (delegate && 
                delegate.animationFor && 
                delegate.animationFor({action:'remove', listView:this, index:index})) || false;
                
    // check if we need to reset cellBeingEdited
    if(index == this.cellBeingEdited())
    {
      this.setCellBeingEdited(-1);
    }
    
    if(anim)
    {
      var animData = anim();
      animData.animation().chain(function() {
        if(animData.cleanup) animData.cleanup();
        this.refresh();
      }.bind(this));
    }
    else
    {
      this.refresh();
    }
    
    this.fireEvent('onRemove', index);
  },
  
  /*
    Function: editObject
      Accepts a cell element and allows that cell to be edited.
    
    Parameters:
      sender -  An HTML element. (SSCell)
  */
  editObject: function(sender)
  {
    var index = this.indexOf(sender);
    this.edit(index);
  },
  
  /*
    Function: hideItem
      Hides the specified cell within a collection, and checks to see if animation should occur during hiding. Calls the refresh function to perform filtering of hidden items. Accepts a cell index and a boolean that determines whether animation occurs.  
    
    Parameters:
      index - the index of a SSCell object.
      _animate - A boolean.
  */
  hideItem: function(index, _animate)
  {
    var animate = (_animate == null && true) || _animate;
    this.boundsCheck(index);
    
    var delegate = this.delegate();
    var canHide = (delegate && delegate.canHide && delegate.canHide(index)) || true;
    
    if(canHide)
    { 
      var anim = (animate && delegate && delegate.animationFor && delegate.animationFor({action:'hide', listView:this, index:index})) || false;
      
      if(anim)
      {
        var animData = anim();
        animData.animation().chain(function() {
          if(!this.suppressRefresh()) this.refresh();
          if(animData.cleanup) animData.cleanup();
        }.bind(this));
      }
      else
      {
        this.refresh();
      }
    }
  },
  
  /*
    Function: hideObject
      Accepts a cell object and hides it by passsing it to hideItem
    
    Parameters:
      sender -  An HTML element. (SSCell)
    
    See Also:
      hideItem
    
    //NOTE:  Shouldn't this function have an _animate parameter?  -Justin
  */
  hideObject: function(sender)
  {
    var index = this.indexOf(sender);
    this.hideItem(index);
  },

  /*
    Function: checkForUnsavedChanges 
      //Note: Needs work  
  */
  checkForUnsavedChanges: function(properties)
  {
    // grab the old values
    return false;
  },

  /*
    Function: cancelEdit
      Cancels Accepts a cell index and a boolean that determines if animation occurs.
      
    Parameters:
      index - the index of a SSCell object.
      _animate - A boolean.
  */
  cancelEdit: function(index, _animate)
  {
    var animate = (_animate == null && true) || _animate;
    var cellBeingEdited = this.cellBeingEdited();
    
    var delegate = this.delegate();
    var canLeaveEdit = (delegate && delegate.canLeaveEdit && delegate.canLeaveEdit(index)) || true;

    // check for unsaved changes
    if(cellBeingEdited != -1 && canLeaveEdit)
    {
      var anim = (animate && delegate && delegate.animationFor && delegate.animationFor({action:'leaveEdit', listView:this, index:index})) || false;
      
      var leaveEditModeForCell = function() {
        this.cell().lock(this.cellNodeForIndex(cellBeingEdited));
        this.cell().leaveEdit();
        this.cell().unlock();
        this.setCellBeingEdited(-1);
      }.bind(this);
      
      if(anim)
      {
        var animData = anim();
        animData.animation().chain(function() {
          leaveEditModeForCell();
          if(animData.cleanup) animData.cleanup();
        }.bind(this));
      }
      else
      {
        leaveEditModeForCell();
      }
    }
  },
  
  /*
    Function: cancelEditObject
      Cancels edits to a passed SSCell object.
      
    Parameters:
      sender -  An HTML element. (SSCell)
      
    See Also:
      cancelEdit
      
    //NOTE:  Shouldn't this function have an _animate parameter to send to cancelEdit?  -Justin
  */
  cancelEditObject: function(sender)
  {
    var index = this.indexOf(sender);
    this.cancelEdit(index);
  },
  
  /*
    Function: canSelect
      Checks the delagate of a cell specified by the index argument.  If a delegate exists, it returns whether the cell is set as selectable. Returns true by default.
      
    Parameters:
      index - the index of a SSCell object.
      
    Returns:
      An Boolean
  */
  canSelect: function(index)
  {
    if(this.delegate() && this.delegate().canSelect)
    {
      return this.delegate().canSelect(index);
    }
    return true;
  },
  
  /*
    Function: refresh
      Checks to see if refresh can be called, and calls reloadData. Setting the force paremeter to true bypasses the initial checks.
      
    Parameters:
      force - A Boolean.
  */
  refresh: function(force)
  {
    this.parent();
    
    // no data nothing to do
    if(!this.data()) return;
    
    // collection not yet loaded nothing to do
    if(this.__pendingCollection) return;
    
    // don't refresh if we're visible
    if(!this.isVisible() && !force) 
    {
      return;
    }

    if(this.hasCollection())
    {
      this.data().read(this.reloadData.bind(this));
    }
    else
    {
      this.reloadData();
    }
  },
  
  newCellForItemData: function(itemData, index)
  {
    var filtered = this.filter(itemData, index);
    var newCell = this.cell().cloneWithData(itemData);
    if(itemData.id) newCell.store('refid', itemData.id);
    if(filtered) newCell.addClass('SSDisplayNone');
    return newCell;
  },
  
  /*
    Function: reloadData (private)
      Called by refresh(). Checks the length of the current collection data, and clears the currently loaded collection data.  If the collection array contains data, it resizes the elements and applies set filters. If the collection is not pending, the content is displayed. 
  */
  reloadData: function()
  {
    // check whether collection or array
    var len = ($type(this.data().length) == 'function' && this.data().length()) || this.data().length;
    
    // empty on refresh
    this.element.empty();
    
    if(len > 0 && this.cell())
    {
      var perPage = (this.pageControl() && this.pageControl().perPage()) || len;
      
      // set the width programmatically if horizontal
      if(this.options.horizontal && this.options.cellSize)
      {
        var modifer = (this.options.cellModifier && this.options.cellModifier.x) || 0;
        this.element.setStyle('width', (this.options.cellSize.x*perPage)+modifer);
      }
      
      var cells = this.data().map(this.newCellForItemData.bind(this));
      cells.each(function(cell) {
        this.element.grab(cell)
      }.bind(this));
      
      this.initSortables();
    }

    if(!this.__pendingCollection)
    {
      this.setNeedsDisplay(false);
    }

    if(this.pageControl()) this.pageControl().initializeInterface();
    
    this.fireEvent('onReloadData', this);
  },
  /*
    Function: boundsCheck
      Tests to see if the passed index is within the bounds of the SSCollection array. Throws a SSListViewError message if the index is out of bounds. 
    
    Parameters:
      index - the index of a SSCell object 
  */
  boundsCheck: function(index)
  {
    if(index < 0 || index >= this.count()) throw new SSListViewError.OutOfBounds(new Error(), index + " index is out bounds.");
  },
  
  /*
    Function: cellNodeForIndex
      Returns the SSCell object based on the passed index parameter.
    
    Parameters: 
      index - the index of a SSCell object.
  */
  cellNodeForIndex: function(index)
  {
    return this.cellNodes()[index];
  },
  
  /*
    Function: indexOf
      Returns the index of a SSCell objet that contains the passed object. If the object is not found in a SSCell, it returns -1.
    
    Parameters:
      object - An object.
      
    Returns:
      A cell node index or -1.
  */
  indexOf: function(object)
  {
    if($memberof(object, 'SSCell'))
    {
      return this.indexOfCellNode(object.lockedElement());
    }
    return -1;
  },
  
  /*
    Function: indexOfCellNode 
      Returns the index of the passed cell node.
    
    Parameter:
      cellNode - a cell's DOM node
      
    Returns:
      The index of a cell node
      
  */
  indexOfCellNode: function(cellNode)
  {
    return this.cellNodes().indexOf(cellNode);
  },
  
  /*
    Function: onCellClick 
      Accepts a cell node and sets a userDidClickListItem delegate with the index of the passed cell node.
      
    Parameter:
      cellNode - A cell's DOM node
      
  */
  onCellClick: function(cellNode)
  {
    var index = this.cellNodes().indexOf(cellNode);
    if(this.delegate() && this.delegate().userDidClickListItem)
    {
      this.delegate().userDidClickListItem(index);
    }
  },
  
  /*
    Function: __addEventsToCollection__ (private)
      Adds events to a collection. 
      
    Parameters:  
      coll - A SSCollection object.
  */
  __addEventsToCollection__: function(coll)
  {
    coll.addEvent('onCreate', function(event) {
      if(this.isVisible())
      {
        this.onAdd(event.data, event.userData);
      }
    }.bind(this));
    
    coll.addEvent('onDelete', function(index) {
      if(this.isVisible()) 
      {
        this.onRemove(index);
      }
    }.bind(this));
    
    coll.addEvent('onChange', function() {
    }.bind(this));
    
    coll.addEvent('onUpdate', function() {
    }.bind(this));
    
    coll.addEvent('onLoad', function() {
    }.bind(this));
  },
  
  /*
    Function: setSuppressRefresh
      Sets the suppressRefresh property to the passed boolean. If true, it prevents a refresh from occuring.
      
    Parameter:
      val - A boolean value.
  */
  setSuppressRefresh: function(val)
  {
    this.__suppressRefresh = val;
  },
  
  /*
    Function: suppressRefresh
      Returns the supressRefresh property.
     
    Parameter:
      val - A boolean value.
      
    Returns:
      A boolean value
      
   //NOTE: function just returns a property value, no need for a "val" parameter. - Justin 
  */
  suppressRefresh: function(val)
  {
    return this.__suppressRefresh;
  },
  
  /*
    Function: useCollection 
      Accepts a collection name and checks to see if it's valid. If the collection exists, the events and data are applied to the collection. Otherwise, it is set as a pending collection.
      
    Parameters: 
      collectionName - A string, the name of a collection
  */
  useCollection: function(collectionName)
  {
    var coll = SSCollectionForName(collectionName, this);
    this.setHasCollection(true);
    if(coll) 
    {
      this.__addEventsToCollection__(coll);
      this.setData(coll);
    }
    else
    {
      // not ready yet, controller loaded before collection
      this.__pendingCollection = collectionName;
    }
  },
  
  /*
    Function: setCellBeingEdited
      Accepts an index of a cell, and sets the __cellBeingEdited property value to that index.  Used to identify which cell is currently being edited by a user.
      
    Parameters:
      index - the index of a SSCell object.
  */
  setCellBeingEdited: function(index)
  {
    this.__cellBeingEdited = index;
  },

  /*
    Function: cellBeingEdited
      Returns the __cellBeingEdited property value.
  */
  cellBeingEdited: function()
  {
    return this.__cellBeingEdited;
  },

  /*
    Function: setNeedsDisplay
      Sets whether the display should be set for the ListView content.  When set to true, the SSListView is cleared.
      
    Parameter: 
      value - A boolean value.
  */
  setNeedsDisplay: function(value)
  {
    this.parent(value);
    if(value && this.element && this.cell() && !this.isVisible())
    {
      this.element.empty();
    }
  }
  
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.SSListView = SSListView;


// End ../client/views/SSListView/SSListView.js -------------------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('SSListView');

// === END PACKAGE [ShiftSpaceCoreUI] ===


// === START PACKAGE [MoMASocialBarUI] ===

if(__sysavail__) __sysavail__.packages.push("MoMASocialBarUI");

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAConsole/MoMAConsole.js...', SSInclude);

// Start ../momasocialbar/views/MoMAConsole/MoMAConsole.js --------------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSView
// ==/Builder==

var kUserAddedSetNotification = 'kUserAddedSetNotification';
var kUserUpdatedSetNotification = 'kUserUpdatedSetNotification';
var kUserRemovedSetNotificaiton = 'kUserRemovedSetNotificaiton';
var kUserAddedArtworkToSetNotification = 'kUserAddedArtworkToSetNotification';
var kUserRemovedArtworkFromSetNotifiction = 'kUserRemovedArtworkFromSetNotifiction';

var kUserAddedBookmarkNotification = 'kUserAddedBookmarkNotification';
var kUserUpdatedBookmarkNotifcation = 'kUserUpdatedBookmarkNotifcation';
var kUserRemovedBookmarkNotification = 'kUserRemovedBookmarkNotification';

var kUserAddedArtworkNotification = 'kUserAddedArtworkNotification';
var kUserUpdatedArtowrkNotifcation = 'kUserUpdatedArtowrkNotifcation';
var kUserRemovedArtworkNotification = 'kUserRemoveArtworkNotification';

var kUserUpdatedAccountNotification = 'kUserUpdatedAccountNotification';

String.implement({
  nsubstitute: function(object, regexp){
    return this.replace(regexp || (/\\?\{([^}]+)\}/g), function(match, name){
      if (match.charAt(0) == '\\') return match.slice(1);
      return (object[name] != undefined) ? object[name] : '{'+name+'}';
    });
  }
});

(function() {

Element.implement({
  isFixed: function()
  {
    var node = this;
    while(node)
    {
      if(node.getStyle('position') == 'fixed') return true;
      node = node.getParent();
    }
    return false;
  }
});

function isBody(element){
	return (/^(?:body|html)$/i).test(element.tagName);
};

})();

Drag.Move.implement({
  checkAgainst: function(el)
  {
    var position = el.getCoordinates();
    if(el.isFixed())
    {
      var scroll = el.getWindow().getScroll();
      position.left += scroll.x;
      position.right += scroll.x;
      position.top += scroll.y;
      position.bottom += scroll.y;
    }
    var now = this.mouse.now;
    return (now.x > position.left && now.x < position.right && now.y < position.bottom && now.y > position.top);
  }
});

var SSMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var SSDays = ['Sunday', 'Monday', 'Tuesday',' Wednesday', 'Thursday', 'Friday', 'Saturday'];

var MoMAConsole = new Class({

  Extends: SSView,
  name: "MoMAConsole",
           
  types: ['Exhibition', 'FilmExhibition', 'Event', 'FilmScreening', 'Object', 'Artist', 'Page', 'Set'],
  
  typeUrls: ['/visit/calendar/exhibitions/{objectid}.json',
             '/visit/calendar/films/{objectid}.json',
             '/visit/calendar/events/{objectid}.json',
             '/visit/calendar/film_screenings/{objectid}.json'],
             
  feedback:
  {
    'Set': "a set collected by {username}",
    'Page': "a page shared by {username}",
    'Exhibition': "an exhibition shared by {username}",
    'FilmExhibition':  "a film exhibition shared by {username}",
    'FilmScreening': "a film screening shared by {username}",
    'Event': "an event shared by {username}",
    'CollectionObject': "an artwork shared by {username}"
  },
  
  perspectives: ['First-time visitor', 'Returning visitor', 'Member', 'Filmgoer', 'Family', 'Researcher', 'Educator', 'Student'],
  
  
  currentPerspectiveLink: function()
  {
    var idx = (this.cookie.get('perspective') == null) ? 0 : this.cookie.get('perspective');
    if(typeof perspectiveLinks != 'undefined') return perspectiveLinks[idx][this.__currentPerspectiveLink];
  },
  
  
  nextPerspectiveLink: function()
  {
    if(typeof perspectiveLinks != 'undefined')
    {
      var idx = (this.cookie.get('perspective') == null) ? 0 : this.cookie.get('perspective');
      
      var links = perspectiveLinks[idx];
      this.__currentPerspectiveLink = (this.__currentPerspectiveLink + 1) % links.length;
      
      return links[this.__currentPerspectiveLink];
    }
  },
  
  
  initialize: function(el, options)
  {
    this.parent(el, options);
    
    // set this as the app delegate
    SSSetAppDelegate(this);
    
    // init current user prompt text
    this.__currentPrompt = 0;
    
    SSLog('MoMAConsole starting up!', SSLogForce);
    
    // set the user query flag to false (we haven't yet checked to see if the user is already logged in)
    this.setQueryRan(false);
    
    // starts closed
    this.setIsOpen(false);
    
    if(typeof SandalphonToolMode == 'undefined')
    {
      Sandalphon.load('client/compiledViews/MoMAConsole', this.buildInterface.bind(this));
    }
    
    // add to name table!
    ShiftSpaceNameTable.set('MoMAConsole', this);
    
    // set up the perspective cookie
    this.cookie = new Hash.Cookie('MoMASocialBarCookie');

    if(typeof perspectiveLinks != 'undefined')
    {
      if(this.cookie.get('perspective') && 
         this.cookie.get('perspective') > perspectiveLinks.length-1)
      {
        this.cookie.set('perspective', 0);
      }
      this.__currentPerspectiveLink = 0;
    }
    
    this.loadPerspectiveMenu();
    
    SSAddLoaderListener(this);
    
    ShiftSpaceUser.addEvent('onUserQuery', this.onUserQuery.bind(this));
  },
  
  showLoader: function()
  {
    if(this.isOpen())
    {
      this.element.getElement('.Logout').addClass('Loader');
    }
  },
  
  hideLoader: function()
  {
    this.element.getElement('.Logout').removeClass('Loader');
  },
  
  // call after user login
  initCollections: function(data)
  {
    if(!ShiftSpaceUser.isLoggedIn()) return;
    
    var userId = ShiftSpaceUser.getId();
    
    this.BookmarksCollection.updateConstraints('userid', userId);
    this.WorksCollection.updateConstraints('userid', userId);
    this.SetsCollection.updateConstraints('userid', userId);
    this.WorksViewCollection.updateConstraints('userid', userId);
    
    // add a plugin to SetsCollection to merge data
    this.SetsCollection.addPlugin('read', function(data) {
      for(var i = 0, len = data[0].length; i < len; i++)
      {
        data[0][i].count = data[1][i][0]["COUNT(*)"];
      }
      return data[0];
    });
    
    this.WorksCollection.addPlugin('read', function(data) {
      for(var i = 0, len = data[0].length; i < len; i++)
      {
        data[0][i].inSets = data[1][i].map(function(x){return x.setid});
      }
      return data[0];
    });
  },
  
  
  initNewsletterOptions: function()
  {
    $$('#MoMANewsLetterOptions li').addEvent('click', function(e) {
      var event = new Event(e);
      // ignore clicks on the checkbox
      if(event.target.get('tag') == 'input') return;
      var target = (event.target.get('tag') == 'li' && event.target) || event.target.getParent('li');
      if(target.hasClass('SSActive'))
      {
        target.removeClass('SSActive');
      }
      else
      {
        var lastActive = $$('#MoMANewsLetterOptions li.SSActive')[0];
        if(lastActive) lastActive.removeClass('SSActive');
        target.addClass('SSActive');
      }
    });
  },
  
  
  isIE6: function()
  {
    if(!this.__isIE6) this.__isIE6 = Browser.Engine.trident && (Browser.Engine.version < 5);
    return this.__isIE6;
  },
  
  
  loadPerspectiveMenu: function()
  {
    this.perspectiveMenuRequest = new Request({
      url: SSInfo().server + "momasocialbar/views/MoMAConsole/MoMAPerspectiveMenu.html",
      onSuccess: function(responseText, responseXml)
      {
        this.MoMAPerspectiveMenuContainer = new Element('div', {
          id: 'MoMAPerspectiveMenuContainer'
        });
        
        this.MoMAPerspectiveMenuContainer.set('html', responseText);
        
        this.MoMAPerspectiveMenuContainer.getElements('.MoMAPerspectiveMenuItem').addEvent('click', function(_evt) {
          var evt = new Event(_evt);
          var target = (evt.target.hasClass('MoMAPerspectiveMenuItem')) ? evt.target : evt.target.getParent('.MoMAPerspectiveMenuItem');
          var idx = this.MoMAPerspectiveMenuContainer.getElements('.MoMAPerspectiveMenuItem').indexOf(target)+2;
          this.setPerspective(idx);
          // update the user's account
          ShiftSpaceUser.update({perspective:idx}, function() {
            SSLog('User perspective updated to ' + idx, SSLogForce);
          }.bind(this));
        }.bind(this));
        
        if(this.isIE6()) this.MoMAPerspectiveMenuContainer.setStyle('position', 'absolute');
      }.bind(this),
      onFailure: function(responseText, responseXml)
      {
      }.bind(this)
    }).send();
  },
  
  
  setPerspective: function(index)
  {
    this.cookie.set('perspective', index);
    this.__currentPerspectiveLink = 0;
    
    if(index >= 2)
    {
      $('MoMASocialBarPerspective').addClass('MoMAPerspectiveSelected');
      $('MoMACurrentPerspective').set('html', "<span>perspective </span> "+ this.perspectives[index-2]);
    }
    else
    {
      $('MoMASocialBarPerspective').removeClass('MoMAPerspectiveSelected');
      $('MoMACurrentPerspective').set('text', 'Welcome. Are you...?');
    }
  },
  
  
  awake: function(context)
  {
    if(context == this.element)
    {
      // convenience
      this.mapOutletsToThis();
      
      this.attachEvents();
      
      this.MoMASocialBarPerspective.addEvent('click', this.showPerspectiveMenu.bind(this));
      
      this.MoMASocialBarUser.addEvent('click', function(_evt) {
        this.MoMALoginRegisterView.selectTab(0);
        this.showConsole();
      }.bind(this));
      
      this.MoMASocialBarAction.addEvent('click', this.toggleConsole.bind(this));
      
      // start fadeout animation
      this.fadeOutPerspective.delay(5000, this);
      
      this.BookmarksCollection = new SSCollection("Bookmarks", {
        table: 'bookmark',
        properties: '*',
        orderBy: ['>', 'id']
      });

      this.WorksCollection = new SSCollection("Works", {
        table: 'savedartwork',
        properties: '*',
        orderBy: ['>', 'savedartwork.id']
      });
      
      this.SetsCollection = new SSCollection("Sets", {
        table: 'workset',
        properties: '*',
        orderBy: ['>', 'id']
      });

      // this is used to allow adding works to a set from the WorksView
      // see MoMAWorksView.js
      this.WorksViewCollection = new SSCollection("WorksView", {
        table: 'worksinset',
        properties: '*'
      });

      // set collection delegates now
      this.WorksCollection.setDelegate(this);
      this.SetsCollection.setDelegate(this);
      
      this.initForms();
    }
  },
  
  
  afterAwake: function(context)
  {
    ShiftSpaceUser.query();
    this.activateLinks();
    this.watchViews();
  },
  
  
  activateLinks: function()
  {
    // phone links
    $$('.ss-send-to-phone').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      if(ShiftSpaceUser.isLoggedIn() && ShiftSpaceUser.phoneValidated() == 0)
      {
        if(!this.isOpen()) this.showConsole();
        this.MoMAMainView.selectTab(4);
      }
    }.bind(this));
    
    // Bookmark
    $$('a[href=#bookmark]').addEvent('click', this.handleBookmarkLink.bindWithEvent(this));
    
    // Share
    $$('a[href=#share]').addEvent('click', this.handleShareLink.bindWithEvent(this));
    
    // Share by Email
    if($('MoMAShareByEmail')) $('MoMAShareByEmail').addEvent('click', function(evt) {
      if(!this.validEvent(evt)) return;
      if(!this.isOpen()) this.showConsole();
      this.MoMAMainView.selectTab(0);
      this.MoMAShareView.showView(2);
      this.updateShareEmailForm();
      $('MoMAShareWidget').removeClass('visible');
    }.bind(this));
    
    // Share by SMS
    if($('MoMAShareBySMS')) $('MoMAShareBySMS').addEvent('click', function(evt) {
      if(!this.validEvent(evt)) return;
      if(!this.isOpen()) this.showConsole();
      this.MoMAMainView.selectTab(0);
      this.MoMAShareView.showView(1);
      this.updateShareSMSForm();
      $('MoMAShareWidget').removeClass('visible');
    }.bind(this));
    
    // Collect
    $$('a').filter(function(x) { return x.getProperty('href') == '#object'; }).each(function(x) {
      x.addEvent('click', this.handleCollectionLink.bindWithEvent(this));
    }.bind(this));
  },
  
  
  validEvent: function(_evt, suppress)
  {
    var event = new Event(_evt);
    
    if(!ShiftSpaceUser.isLoggedIn())
    {
      // check to see if queried user
      if(!suppress) alert("Sorry, you must be logged in to save a collection object or bookmark a page. Register or log in at the bottom of your browser.");
      return false;
    }
    
    event.preventDefault();
    event.stop();
    
    return true
  },
  
  
  handleBookmarkLink: function(_evt)
  {
    if(this.validEvent(_evt))
    {
      if(!this.isOpen()) this.showConsole();
      this.addObject('#bookmark');
    }
    return false;
  },
  
  
  handleShareLink: function(_evt)
  {
    if(this.validEvent(_evt, true))
    {
      this.MoMAShareView.showView(0);
      this.MoMAMainView.selectTab(0);
      this.shareObject('#bookmark');
    }
    return false;
  },
  
  
  handleCollectionLink: function(_evt)
  {
    if(this.queryRan() && this.validEvent(_evt))
    {
      var event = new Event(_evt);
      var target = $(event.target);
      var jsonRel = target.getProperty('rel');
      var action = (target.hasClass('ss-add-item') && 'add') || (target.hasClass('ss-share-item') && 'share');

      if(!this.isOpen()) this.showConsole();
      
      if(action == 'add') this.addObject("#object", jsonRel);
      if(action == 'share') this.shareObject("#object", jsonRel);
    }
    return false;
  },
  
  // TODO: this needs to be fixed, too hacky - David
  shareObject: function(type, jsonRel, data)
  {
    switch(type)
    {
      case '#bookmark':
        var meta = data || this.getBookmarkMetadata();
        
        if(meta.type >= 0 && meta.type <= 4)
        {
          // has event data
          this.getAllBookmarkData(meta, function(json, bail) {
            // malformed JSON exit
            if(bail) return;
            
            var data = json.data;
            var dateString = this.dateStringFromData(data);
            
            if(dateString) meta.date = dateString;
            if(data.description)
            {
              meta.description = (new Element('div')).set('html', data.description).get('text');
            }
            
            SSLog(data.locations, SSLogForce);

            if(data.locations && data.locations[0] && data.locations[0].room && data.locations[0].room.web)
            {
              meta.location = data.locations[0].room.web;
            }
            
            if(data.special_occurrence_information)
            {
              meta.special = data.special_occurrence_information;
            }
            
            this.MoMAMainView.selectTabByName('MoMAShareView');
            this.updateShareView(type, meta);
          }.bind(this));
        }
        else
        {
          // regular bookmark
          this.MoMAMainView.selectTabByName('MoMAShareView');
          this.updateShareView(type, meta);
        }
        break;
        
      case '#object':
        break;
        
      case '#set':
        var meta = data;
        
        meta.type = this.types.indexOf("Set");
        meta.href = ['/shared', meta.id].join("/");
        
        this.MoMAMainView.selectTabByName('MoMAShareView');
        this.updateShareView('#set', meta);
        break;
        
      default:
        break;
    }    
  },
  
  
  dayEventString: function(adateStr)
  {
    var dobj = Date.parse(adateStr);
    return [SSMonths[dobj.getMonth()], " ", dobj.getDate(), ", ", (1900+dobj.getYear())].join("");
  },
  
  
  timeEventString: function(adateStr)
  {
    var dateStr = adateStr.replace('T', ' ');
    var dobj = Date.parse(dateStr);
    return [SSDays[dobj.getDay()], this.dayEventString(dateStr), this.clockTimeString(dobj.strftime().split(' ')[1])].join(" ");
  },
  
  
  clockTimeString: function(clockStr)
  {
    var end = clockStr.substring(clockStr.length-2, clockStr.length);
    var amOrPM = (end == "AM" && "a.m.") || (end == "PM" && "p.m.");
    return [clockStr.substring(0, clockStr.length-2), amOrPM].join(" ");
  },
  
  
  addObject: function(object, jsonRel)
  {
    SSLog('addObject', SSLogForce);
    
    switch(object)
    {
      case '#bookmark':
        var meta = this.getBookmarkMetadata();

        if(meta.type >= 0 && meta.type <= 4)
        {
          SSLog('get date info', SSLogForce);
          this.getAllBookmarkData(meta, function(json, bail) {
            // Malformed JSON exit
            if(bail) return;
            
            var data = json.data;
            var dateString = this.dateStringFromData(data);

            SSLog(data, SSLogForce);

            if(dateString) meta.title = (data.title || meta.title) + " | " + dateString;
            
            this.MoMAMainView.selectTabByName('MoMABookmarksView');
            this.MoMABookmarksView.addBookmark(meta);
          }.bind(this));
        }
        else
        {
          this.MoMAMainView.selectTabByName('MoMABookmarksView');
          this.MoMABookmarksView.addBookmark(meta);
        }
        break;
        
      case '#object':
        this.getObjectMetadata(jsonRel, function(json) {
          var momaObject = json.data;

          var ourData = {};
          
          if(!this.isOpen()) this.showConsole();
          
          ourData.artworkid = momaObject.objectid;
          ourData.userid = ShiftSpaceUser.getId();
          
          // show the tab
          this.MoMAMainView.selectTabByName('MoMAWorksView');
          this.MoMAWorksView.addWork(ourData);
        }.bind(this));
        break;

      default:
        break;
    }
  },
  
  
  getBookmarkMetadata: function()
  {
    var matches = ['objectid', 'category', 'pagetype', 'stitle', 'image'];
    var metas = $$('meta').filter(function(x) {
      return matches.contains(x.getProperty('name'));
    });
    var content = metas.map(function(x) { return x.getProperty('content'); });
    var metadata = content.associate(metas.map(function(x) { return x.getProperty('name'); }));

    // remap
    metadata.momaid = metadata.objectid;

    if(!metadata.pagetype)
    {
      metadata.pagetype = "Page";
    }
    
    metadata.title = document.title;
    
    if(!metadata.title)  metadata.title = metadata.stitle;
    metadata.title = metadata.title.replace('MoMA | ', '');
    
    // for the bookmark cell
    metadata.type = this.types.indexOf(metadata.pagetype);
    metadata.href = window.location.pathname + (window.location.search || '');
    
    delete metadata.objectid;
    delete metadata.pagetype;
    delete metadata.section;
    delete metadata.category;
    delete metadata.stitle;
    
    // delete description, HTML markup changed since last time
    delete metadata.description;

    // CRAZY HACK: remove data from single artwork pages - David
    delete metadata.objectnumber;
    delete metadata.renditionid;

    return metadata;
  },
  
  
  getAllBookmarkData: function(data, callback)
  {
    SSIncPendingRequests();
    var url = this.typeUrls[data.type].substitute({objectid:data.momaid});
    
    new Request({
      method: 'get',
      url: url,
      onComplete: function(responseText, responseXml)
      {
        // for loading animation
        SSDecPendingRequests();
        
        try
        {
          if(callback && $type(callback) == 'function') callback(JSON.decode(responseText));
        }
        catch(err)
        {
          callback(null, true);
        }
      },
      onFailure: function(responseText, responseXml)
      {
        // for loading animation
        SSDecPendingRequests();
        SSLog('Error rel failed', SSLogForce)
      }      
    }).send();
  },
  
  
  dateStringFromData: function(data)
  {
    // check for event date
    if(data.begin_date)
    {
      dateString = this.dayEventString(data.begin_date);
    }
    
    if(data.end_date)
    {
      var endDate = this.dayEventString(data.end_date);
      
      if(Date.parse(data.begin_date) < (new Date()))
      {
        dateString = 'Through ' + endDate;
      }
      else
      {
        dateString += '-' + endDate;
      }
    }
    
    if(data.begin_datetime)
    {
      dateString = this.timeEventString(data.begin_datetime);
    }
    
    return dateString;
  },
  
  
  getObjectMetadata: function(jsonRel, callback)
  {
    SSIncPendingRequests();
    new Request({
      method: 'get',
      url: jsonRel,
      onComplete: function(responseText, responseXml)
      {
        SSDecPendingRequests();
        if(callback && $type(callback) == 'function') callback(JSON.decode(responseText));
      },
      onFailure: function(responseText, responseXml)
      {
        SSDecPendingRequests();
        SSLog('Error rel failed', SSLogForce)
      }
    }).send();
  },
  
  
  setQueryRan: function(value)
  {
    this.__queryRan = value;
  },
  
  
  queryRan: function()
  {
    return this.__queryRan;
  },
  
  
  onUserQuery: function(json)
  {
    // spit out the data
    SSLog('>>>>>>>>>>>>>>>>>>>>>>>>>>', SSLogForce);
    SSLog(json, SSLogForce);
    
    this.setQueryRan(true);

    // check the cookie
    if(!json)
    {
      if(this.cookie.get('perspective') == null)
      {
        if(!this.cookie.get('registeredUser'))
        {
          this.setPerspective(0);
        }
        else
        {
          this.setPerspective(1);
        }
      }
      else
      {
        this.setPerspective(this.cookie.get('perspective'));
      }
    }
    
    if(json && ShiftSpaceUser.isLoggedIn())
    {
      this.onLogin(json);
    }
    else
    {
      this.updatePrompt();
    }
  },
  
  
  fadeOutPerspective: function()
  {
    this.MoMASocialBarSuggestionAndType.set('morph', {
      duration: 200,
      transition: Fx.Transitions.Cubic.easeOut,
      onComplete: this.fadeInPerspective.bind(this)
    });
    
    this.MoMASocialBarSuggestionAndType.morph('.MoMAPerspectiveFadeOut');
  },
  
  
  fadeInPerspective: function()
  {
    if(typeof perspectiveLinks != 'undefined') this.updatePerspectiveLink(this.nextPerspectiveLink());
    
    this.MoMASocialBarSuggestionAndType.set('morph', {
      duration: 200,
      transition: Fx.Transitions.Cubic.easeIn,
      onComplete: this.fadeOutPerspective.delay(5000, this)
    });
    
    this.MoMASocialBarSuggestionAndType.morph('.MoMAPerspectiveFadeIn');
  },
  
  
  updatePerspectiveLink: function(linkData)
  {
    var link = $('MoMASocialBarPerspectiveLink');
    
    link.setProperty('href', linkData.href);
    
    var perspective = this.cookie.get('perspective');

    // clear the click events
    link.removeEvents('click');
    if(perspective == 0) link.addEvent('click', function() {
      if(!this.isOpen()) this.showConsole();
      this.MoMALoginRegisterView.selectTab(1);
    }.bind(this));
    if(perspective == 1) link.addEvent('click', function() {
      if(!this.isOpen()) this.showConsole();
    }.bind(this));

    link.getElementById('MoMASocialBarSuggestion').set('text', linkData.title);
    link.getElementById('MoMASocialBarSuggestionType').set('text', linkData.type);
  },
  
  
  toggleConsole: function()
  {
    if(!this.isOpen())
    {
      this.showConsole();
    }
    else
    {
      this.hideConsole();
    }    
  },
  
  
  isOpen: function()
  {
    return this.__isOpen;
  },

  
  setIsOpen: function(val)
  {
    this.__isOpen = val;
  },
  

  showConsole: function()
  {
    this.setIsOpen(true);
    this.MoMAPerspectiveMenuContainer.addClass('Opened');
    $('MoMASocialBarAction').setProperty('title', 'Close');
    this.MoMASocialBarFrame.morph('.MoMASocialBarOpened');
    this.MoMASocialBarAction.addClass('Opened');
  },
  
  
  hideConsole: function()
  {
    this.setIsOpen(false);
    this.MoMAPerspectiveMenuContainer.removeClass('Opened');
    $('MoMASocialBarAction').setProperty('title', 'Open');
    this.MoMASocialBarFrame.morph('.MoMASocialBarClosed');
    this.MoMASocialBarAction.removeClass('Opened');
  },
  
  
  showPerspectiveMenu: function()
  {
    if(!$('MoMAPerspectiveMenuContainer'))
    {
      $(document.body).grab(this.MoMAPerspectiveMenuContainer);
    }
    
    // out mouseleave events
    this.MoMAPerspectiveMenuContainer.addEvent('mouseleave', this.hidePerspectiveMenu.bind(this));
    this.MoMASocialBarPerspective.addEvent('mouseleave', function(_evt) {
      var evt = new Event(_evt);
      var inContainer = $(evt.relatedTarget).getParent('#MoMAPerspectiveMenuContainer');
      if($(evt.relatedTarget) != this.MoMAPerspectiveMenuContainer && !inContainer)
      {
        this.hidePerspectiveMenu();
      }
    }.bind(this));
    
    this.positionPerspectiveMenu();
  },
  
  
  hidePerspectiveMenu: function()
  {
    if(this.MoMAPerspectiveMenuContainer && this.MoMAPerspectiveMenuContainer.getParent())
    {
      this.MoMAPerspectiveMenuContainer.dispose();
      this.MoMAPerspectiveMenuContainer.removeEvent('mouseleave');
      this.MoMASocialBarPerspective.removeEvent('mouseleave');
    }
  },
  
  
  positionPerspectiveMenu: function(isIE6WindowResize)
  {
    var socialBarSize = this.MoMASocialBar.getSize();
    var persp = this.MoMASocialBarPerspective.getPosition();
    var perspSize = this.MoMASocialBarPerspective.getSize();
    if(this.isIE6())
    {
      var viewport = window.getViewport();
      var menuSize = this.MoMAPerspectiveMenuContainer.getSize();

      this.MoMAPerspectiveMenuContainer.setStyles({
        left: persp.x,
        top: viewport.bottom - menuSize.y - socialBarSize.y
      });
    }
    else if(isIE6WindowResize != null)
    {
      this.MoMAPerspectiveMenuContainer.setStyles({
        left: persp.x,
        bottom: socialBarSize.y
      });
    }
    else
    {
      var viewport = window.getSize();
      this.MoMAPerspectiveMenuContainer.setStyles({
        width: perspSize.x+1,
        left: persp.x-1
      });
    }
  },

  
  attachEvents: function()
  {
    if(Browser.Engine.trident)
    {
      document.attachEvent('onclick', this.handleWindowClick.bind(this));
    }
    else
    {
      window.addEvent('click', this.handleWindowClick.bind(this));
    }
    
    this.attachLoginEvents();
    this.attachVerifyPhoneEvents();
    this.attachShareEvents();
    this.attachSettingsEvents();
    this.attachNewsletterEvents();
    
    // Tab Clicked Events
    this.MoMAMainView.addEvent('tabClicked', function(tabEvent) {
      if(tabEvent.tabIndex == 1 && this.MoMAMySetsView.getIndexOfCurrentView() != 0)
      {
        this.MoMAMySetsView.showView(0);
      }
      if(tabEvent.tabIndex == 0)
      {
        this.MoMAShareView.showView(0);
      }
    }.bind(this));
  },
  
  
  attachLoginEvents: function()
  {
    $('MoMASubmitPhone').addEvent('click', this.getBookmarksForPhone.bind(this));
    
    $('IsMoMAMember').addEvent('click', this.toggleMoMAFields.bind(this));
    
    $('ForgotPasswordLink').addEvent('click', function() {
      username = $('UsernameLogin').get('value');
      if (username == '')
        alert('Please enter your username');
      else {
        SSServerCall('user.renew_password', {username: username});
        alert('An e-mail was sent with a new password');
      }
    });
    
    $('Register').addEvent('click', this.registerUser.bind(this));
    $('Login').addEvent('click', this.loginUser.bind(this));
    $('MoMASocialBarUser').getElement('.Logout').addEvent('click', this.logoutUser.bind(this));
    
    ShiftSpaceUser.addEvent('onUserJoin', this.onRegister.bind(this));
    ShiftSpaceUser.addEvent('onUserLogin', function(json) {
      if(!this.__verifyPhone)
      {
        this.onLogin(json);
      }
    }.bind(this));
    ShiftSpaceUser.addEvent('onUserLoginError', this.onLoginError.bind(this));
    ShiftSpaceUser.addEvent('onUserLogout', this.onLogout.bind(this));
    
    $('GotoLogin').addEvent('click', this.MoMALoginRegisterView.selectTab.bind(this.MoMALoginRegisterView, [0]));
    $('GotoRegister').addEvent('click', this.MoMALoginRegisterView.selectTab.bind(this.MoMALoginRegisterView, [1]));
  },
  
  
  attachVerifyPhoneEvents: function()
  {
    $('SubmitPasscode').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      ShiftSpaceUser.validatePhoneComplete($('MoMAPasscode').getProperty('value'), this.verifyPasscode.bind(this));
    }.bind(this));
    
    $('SkipPasscode').addEvent('click', this.onLogin.bind(this, [null, true]));
  },
  
  
  attachShareEvents: function()
  {
    $('MoMASelectShareByEmail').addEvent('click', function() {
      this.updateShareEmailForm();
    }.bind(this));
    $('MoMASelectShareByText').addEvent('click', function() {
      this.updateShareSMSForm();
    }.bind(this));
    $('send').addEvent('click', this.sendSms.bind(this));
    $('share_by_email').addEvent('click', this.sendEmail.bind(this));
  },
  
  
  attachSettingsEvents: function()
  {
    // Phone validation from Settings
    $('SettingsVerifyPhone').addEvent('click', function() {
      this.showSettingsVerifyPhone();
      // update phone on verify
      ShiftSpaceUser.update({phone:$('MoMAUserSettingsPhone').getProperty('value')}, function(data) {
        ShiftSpaceUser.validatePhone();
      }.bind(this));
    }.bind(this));
    
    // Submit the passcode
    $('SettingsSubmitPasscode').addEvent('click', function() {
      ShiftSpaceUser.validatePhoneComplete($('SettingsMoMAPasscode').getProperty('value'), function(json) {
        if(json['error'])
        {
          alert("Sorry we could not validate your phone");
        }
        else
        {
          ShiftSpaceUser.setPhoneValidated(1);
          this.enablePhone();
          this.updateAccountSettings();
        }
        this.hideSettingsVerifyPhone();
      }.bind(this));
    }.bind(this));
    
    $('SettingsCancelPasscode').addEvent('click', this.hideSettingsVerifyPhone.bind(this));
    
    $('GoToUpdate').addEvent('click', function(_evt) {
      var userData = this.getFormData('MoMAConsoleSettingsForm');
      
      //this.updateNewsletters();
      
      if(userData.password == '********') 
      {
        delete userData.password;
        delete userData.password_again;
      }
      
      ShiftSpaceUser.update(userData, function(json) {
        if(json.data)
        {
          alert("Your settings have been updated.");
          this.updateAccountSettings();
        }
        if(json.error)
        {
          alert(json.error.message);
        }
      }.bind(this));
    }.bind(this));  
  },
  
  
  updateNewsletters: function()
  {
    var newsletters = $$('.MoMANewsLetterSubscribe[checked]');
    SSLog(newsletters, SSLogForce);
    // the url request to make
    
    SSLog('making jsonp request', SSLogForce);
    // we need JSONP for this
    var url = "http://sdm3.rm04.net:80/servlet/UserSignUp?";
    new Request.JSONP({
      url: url,
      data: {
        f:2493,
        postMethod:'HTML'
      },
      onComplete: function(jsonObject)
      {
        SSLog('jsonp complete', SSLogForce);
        SSLog(jsonObject, SSLogForce);
      }.bind(this),
      onFailure: function(jsonObject)
      {
        SSLog('jsonp failed', SSLogForce);
        SSLog(jsonObject, SSLogForce);
      }.bind(this)
    }).send();
  },
  
  
  attachNewsletterEvents: function()
  {
    
  },
  
  
  showSettingsVerifyPhone: function()
  {
    $('SettingsVerifyPhoneFieldset').removeClass('SSDisplayNone');
    //$('MoMASubscribeSettings').addClass('SSDisplayNone');
    //$('MoMASettingsFieldset').addClass('SettingsHalfSize');
  },
  
  
  hideSettingsVerifyPhone: function()
  {
    $('SettingsVerifyPhoneFieldset').addClass('SSDisplayNone');
    //$('MoMASubscribeSettings').removeClass('SSDisplayNone');
    //$('MoMASettingsFieldset').removeClass('SettingsHalfSize');
  },
  
  
  getBookmarksForPhone: function()
  {
    var phone = $('mobile_number').getProperty('value');
    $('MobileNumber').setProperty('value', phone);
    
    ShiftSpaceUser.bookmarksByPhone(phone, function(json) {
      $('MoMARegisterForm').getElement('.SSMessage').setProperty('text', 'Complete your registration to view ' + json.data + ' saved works');
      this.MoMALoginRegisterView.selectTab(1);
    }.bind(this));
  },
  
  
  toggleMoMAFields: function()
  {
    var checked = $('IsMoMAMember').getProperty('checked');
    if(checked)
    {
      $('MoMARegisterFieldset').addClass('ShowMemberFields');
      $('MoMAMemberFields').removeClass('SSDisplayNone');
      $$('#MoMARegisterForm .MoMAOrMessage').addClass('SSDisplayNone');
      $('MoMAGoToLogin').addClass('SSDisplayNone');
    }
    else
    {
      $('MoMARegisterFieldset').removeClass('ShowMemberFields');
      $('MoMAMemberFields').addClass('SSDisplayNone');
      $$('#MoMARegisterForm .MoMAOrMessage').removeClass('SSDisplayNone');
      $('MoMAGoToLogin').removeClass('SSDisplayNone');
    }
  },
  
  
  getFormData: function(formId)
  {
    var formFields = $$('#'+formId+' input, #' + formId + ' textarea');
    var fieldNames = formFields.map(function(x) { return x.getProperty('name'); });
    var fieldValues = formFields.map(function(x) { return x.getProperty('value'); });
    return fieldValues.associate(fieldNames);
  },
  
  
  loginUser: function()
  {
    ShiftSpaceUser.login(this.getFormData('MoMALoginForm'));
  },
  
  
  onLoginError: function(json)
  {
    alert(json.error.message);
  },
  
  
  logoutUser: function()
  {
    if(confirm("Area you sure you want to logout?"))
    {
      ShiftSpaceUser.logout();
    }
  },
  
  
  sendSms: function()
  {
    
    if(ShiftSpaceUser.phoneValidated() != 1)
    {
      alert("You must first validate your phone number in the Account Settings tab");
      return;
    }
    
    var parameters = {
      phone: $('SharePhoneNumbers').getProperty('value'),
      msg: $('MoMAShareSMSBody').getProperty('value')
    };
    
    if(parameters.phone == 'one or more number separated by commas')
    {
      alert("Please enter at least one phone number.")
      return;
    }

    if($('SendTextToCurrentUser').getProperty('checked'))
    {
      SSServerCall('sms.send', parameters, function(data) {
        if(data.error)
        {
          this.showError(data.error);
          return;
        }
        else
        {
          parameters.msg = this.smsSelfCopy;
          parameters.toself = 1;
          delete parameters.phone;
          
          SSServerCall('sms.send', parameters, function(data) {
            alert("Your text was sent");
            this.MoMAShareView.showView(0);
          }.bind(this));
        }
      }.bind(this));
    }
    else
    {
      SSServerCall('sms.send', parameters, function(data) {
        if(data.error)
        {
          this.showError(data.error);
          return;
        }
        else
        {
          alert("Your text was sent");
          this.MoMAShareView.showView(0);
        }
      }.bind(this));
    }
  },
  
  
  showError: function(error)
  {
    SSLog('sms error! ' + error, SSLogForce);
    alert(error.message);
  },
  

  sendEmail: function()
  {
    var parameters = {
      email_addresses: $('MoMAShareEmails').getProperty('value'),
      subject: $('MoMAShareEmailSubject').getProperty('value'),
      body: $('MoMAShareEmailBody').getProperty('value')
    };
    
    if($('send_email_to_current_user').getProperty('checked'))
    {
      SSServerCall('email.send', parameters);
    
      parameters.subject = this.mailSelfSubjectCopy;
      parameters.body = this.mailSelfBodyCopy;
      parameters.toself = 1;
      delete parameters.email_addresses;
      
      SSServerCall('email.send', parameters, function(data) {
        alert("Your e-mail was sent");
        this.MoMAShareView.showView(0);
      }.bind(this));
    }
    else
    {
      SSServerCall('email.send', parameters, function(data) {
        alert("Your e-mail was sent");
        this.MoMAShareView.showView(0);
      }.bind(this));
    }
  },
  
  
  linkToPhone: function()
  {
  },
  
  
  enablePhone: function()
  {
    if(ShiftSpaceUser.phone() && ShiftSpaceUser.phoneValidated() == 1)
    {
      $(document.body).addClass('ss-phone-enabled');
      $('MoMASendSMSToSelf').setStyles({
        visibility: ''
      });
      $$('.MoMAUserMobilePhone').set('text', ShiftSpaceUser.phone());
    }
    else
    {
      this.disablePhone();
    }
  },
  
  
  disablePhone: function()
  {
    $(document.body).removeClass('ss-phone-enabled');
    $('MoMASendSMSToSelf').setStyles({
      visibility: 'hidden'
    });
    $$('.MoMAUserMobilePhone').set('text', '');
  },
  
  
  onLogin: function(json, force)
  {
    // show error and quit
    if(json && json.error)
    {
      alert(json.error.message);
      return;
    }

    if((json && json.data) || force)
    {
      // set the perspective from user data
      if(json && json.data) this.setPerspective(json.data.perspective || 1);
      
      // update the user prompt
      this.updatePrompt();

      // default to sharing a bookmark of the page
      this.shareObject('#bookmark');
      this.initCollections();
      this.updateAccountSettings();
      
      // enable phone if allowed
      this.enablePhone();
      this.showView('MoMAMainView');
      
      if(force)
      {
        // from user phone verification, show the settings
        this.MoMAMainView.selectTabByName('MoMAHelpView');
      }
      else
      {
        // normal show share
        this.MoMAMainView.selectTab(0);
      }
    }
    else if(json['error'])
    {
      alert('Sorry the username and/or password is incorrect.');
    }
  },
  
  
  onLogout: function(json)
  {
    this.updatePrompt();
    this.clearAccountSettings();
    
    this.disablePhone();

    // clear the collections plugins
    SSCollectionsClearAllPlugins();
    
    this.showView("MoMALoginRegisterView");
    // show the right register form
    this.MoMARegisterView.showView(0);
    
    // invalidate all the views
    this.invalidateViews();
    
    // empty all the app collections
    this.BookmarksCollection['reset']();
    this.WorksCollection['reset']();
    this.SetsCollection['reset']();
    this.WorksViewCollection['reset']();
  },
  
  
  updatePrompt: function()
  {
    if(ShiftSpaceUser.isLoggedIn())
    {
      username = ShiftSpaceUser.getUsername();
      if (username.length > 8) {
        username = username.substring(0, 8) + '...';
      }
      
      $('MoMASocialBarUser').getElement('.Status').set('text', username);
      $('MoMASocialBarUser').getElement('.Logout').removeClass('SSDisplayNone');
    }
    else
    {
      $('MoMASocialBarUser').getElement('.Logout').addClass('SSDisplayNone');
      
      if(this.cookie.get('registeredUser'))
      {
        $('MoMASocialBarUser').getElement('.Status').set('text', "Sign in here");
      }
      else
      {
        //$('MoMASocialBarUser').getElement('.Status').set('text', "Hi visitor, join here!");
        $('MoMASocialBarUser').getElement('.Status').set('text', "Sign in here");
      }
    }
  },
  
  
  clearAccountSettings: function()
  {
    var textFields = $$('#MoMAConsoleSettingsForm input[type=text]');
    var textPasswords = $$('#MoMAConsoleSettingsForm input[name=password], #MoMAConsoleSettingsForm input[name=password_again]');
    textFields.setProperty('value', '');
    textPasswords.setProperty('value', '********');
  },
  
  
  updateAccountSettings: function()
  {
    var userData = {
      username: ShiftSpaceUser.getUsername(),
      email: ShiftSpaceUser.email(),
      phone: ShiftSpaceUser.phone()
    };
    
    var fields = $$('#MoMAConsoleSettingsForm input[type=text]');
    var fieldsHash = fields.associate(fields.map(function(x) { return x.getProperty('name'); } ));
    $H(userData).each(function(value, key) {
      if(fieldsHash[key] && key != 'password') fieldsHash[key].setProperty('value', value);
    });
    
    SSLog(ShiftSpaceUser.phoneValidated(), SSLogForce);
    if(ShiftSpaceUser.phoneValidated() == 0)
    {
      SSLog('invalid phone', SSLogForce);
      $('UnverifiedPhone').removeClass('SSDisplayNone');
      $('SettingsVerifyPhone').removeClass('SSDisplayNone');
    }
    else
    {
      SSLog('valid phone', SSLogForce);
      $('UnverifiedPhone').addClass('SSDisplayNone');
      $('SettingsVerifyPhone').addClass('SSDisplayNone');
    }
  },
  
  
  validRegisterForm: function(formData)
  {
    if(formData.email == '')
    {
      alert("Please enter an email address.");
      return false;
    }
    return true;
  },
  
  
  registerUser: function()
  {
    var formData = this.getFormData('MoMARegisterForm');
    if(!this.validRegisterForm(formData))
    {
      return;
    }
    
    // set the user perspective to one
    formData.perspective = 1;
    
    ShiftSpaceUser.join(formData, function(json) {
      if(json.error)
      {
        alert(json.error.message);
        return;
      }

      this.cookie.set('registeredUser', true);
      this.setPerspective(1);
      
      // if phone
      if(json.data.phone)
      {
        this.__verifyPhone = true;
        
        // validate
        ShiftSpaceUser.validatePhone(function(validateJson) {
          // already validated
          if(validateJson.data == 'ok')
          {
            this.onLogin(validateJson, true);
          }
          else if(validateJson.data == 'key')
          {
            // show verify phone
            this.showVerifyKeyForm();
          }
        }.bind(this));
        
      }
      else
      {
        // no phone just login
        this.onLogin(json, true);
      }
    }.bind(this));
  },
  
  
  showVerifyKeyForm: function()
  {
    // show the passcode form
    this.MoMARegisterView.showView(1);
  },
  
  
  verifyPasscode: function(json)
  {
    if(json['error'])
    {
      alert("Oops we could not validate your phone, you can validate your phone at anytime");
    }
    this.onLogin(null, true);
  },
  
  
  onRegister: function(userData)
  {
    if(userData && !this.__verifyPhone)
    {
      this.showView('MoMAMainView');
    }
  },
  
  
  handleWindowClick: function(_evt)
  {
    var evt = new Event(_evt);
    var target = $(evt.target);
    
    // don't close the console on these
    if(target.get('tag') == 'a' || target.get('tag') == 'div')
    {
      var type = target.getProperty('href');
      if(type == '#bookmark' ||
         type == '#artwork' ||
         type == "#share" ||
         target.hasClass('ss-add-item') ||
         target.hasClass('ss-share-item') ||
         target.getProperty('id') == 'MoMAShareByEmail' ||
         target.getProperty('id') == 'MoMAShareBySMS' ||
         target.getParent('#MoMAShareByEmail') ||
         target.getParent('#MoMAShareBySMS'))
      {
        return;
      }
    }
    
    if(target != this.MoMASocialBarPerspective && !target.getParent('#MoMASocialBarPerspective'))
    {
      this.hidePerspectiveMenu();
    }
    
    if(!this.element.hasChild(target))
    {
      this.hideConsole();
    }
  },
  
  
  setActiveSocialBarView: function(view)
  {
    // hide the others
    this.MoMASocialBarFrame.getChildren().each(function(view){view.removeClass('SSActive')});
    // make this view the active one
    view.addClass('SSActive');
  },
  
  
  buildInterface: function(ui)
  {
    // create the iframe where the console will live
    if(!$('social'))
    {
      this.element.injectInside(document.body);
    }
    else
    {
      this.element.injectInside($('social')); // DP
    }

    // add the styles into the iframe
    if(!ui.styles) return; // BAIL
    Sandalphon.addStyle(ui.styles);
    
    // to adjust for flashing bug
    (function() {
      // convert to DOM
      this.element = Sandalphon.convertToFragment(ui['interface']);

      // since we're creating the frame via code we need to hook up the controller manually
      SSSetControllerForNode(this, this.element);

      // place it in the frame
      if(!$('social'))
      {
        $(document.body).grab(this.element);
      }
      else
      {
        $('social').grab(this.element);
      }

      // fix for IE6
      if(this.isIE6())
      {
        //this.element.addClass('IE6');
        this.element.setStyle('position', 'absolute');
        window.addEvent('scroll', this.adjustForIE6.bind(this));
        this.adjustForIE6();
      }

      // activate the iframe context: create controllers hook up outlets
      Sandalphon.activate(this.element);

      this.setupTransitions();
      this.initNewsletterOptions();
      this.initTextInputs();

      this.checkHash();
      
      if(typeof perspectiveLinks != 'undefined') this.updatePerspectiveLink(this.currentPerspectiveLink());
    }).delay(100, this);
  },
  
  
  checkHash: function()
  {
    // open console if the #open hash is in the url
    if(window.location.hash == '#open')
    {
      this.showConsole();
    }
    
    // check to see if there is a #txt on the hash
    if(window.location.hash == '#txt')
    {
      this.MoMALoginRegisterView.selectTab(2);
      this.showConsole();
    }
    
    if(window.location.hash == "#login")
    {
      this.MoMALoginRegisterView.selectTab(0);
      this.showConsole();
    }
    
    if(window.location.hash == "#register")
    {
      this.MoMALoginRegisterView.selectTab(1);
      this.showConsole();
    }
  },
  
  
  adjustForIE6: function()
  {
    var viewport = window.getViewport();
    this.element.setStyle('top', viewport.bottom - this.element.getSize().y);
    
    if(this.MoMAPerspectiveMenuContainer.getParent())
    {
      this.positionPerspectiveMenu(true);
    }
  },

  
  setupTransitions: function()
  {
    this.MoMASocialBarFrame.set('morph', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeInOut,
      onStart: function()
      {
        if(!this.isOpen()) $('MoMAWorksListPagination').addClass('SSDisplayNone');
      }.bind(this),
      onComplete: function()
      {
        if(this.isOpen()) $('MoMAWorksListPagination').removeClass('SSDisplayNone');
      }.bind(this)
    });
  },
  
  
  showView: function(viewName)
  {
    this.MoMASocialBarFrame.getElement('> .SSActive').removeClass('SSActive');
    if(this[viewName])
    {
      if(this[viewName].show)
      {
        this[viewName].show();
      }
      else
      {
        this[viewName].addClass('SSActive');
      }
    }
    else
    {
      // Log an error, no such view w/o crashing and show the default
    }
  },
  
  
  clearShareData: function()
  {
    this.setShareData(null);
  },
  
  
  fetchShareCopy: function(type, method, self, callback)
  {
    var typeStr = (parseInt(type) == 6) ? 'other' : this.types[type].toLowerCase();
    var jsonFile = (self) ? [method, 'self', 'json'].join('.') : [method, 'json'].join('.');
    
    new Request({
      url: ['/social/shiftspace/momasocialbar/sharecopy', typeStr, jsonFile].join('/'),
      method: 'get',
      onComplete: function(responseText, responseXml)
      {
        if(callback && $type(callback) == 'function') callback(JSON.decode(responseText));
      },
      onFailure: function(responseText, responseXml)
      {
      }
    }).send();
  },
  
  
  updateShareView: function(type, shareData)
  {
    var title = shareData.title;
    
    if(type == "#set")
    {
      this.element.getElements('#MoMAShareView .MoMAPreUserSelection').set('text', "Share: Your set ");
    }
    else
    {
      this.element.getElements('#MoMAShareView .MoMAPreUserSelection').set('text', "Share: ");
    }
    
    // Update all titles
    this.element.getElements('#MoMAShareView .MoMAUserSelection b').set('text', title);
    this.__currentShareData = $H(shareData).copy().getClean();
    
    // Empty out fields
    this.element.getElementById('SharePhoneNumbers').setProperty('value', "one or more number separated by commas");
    this.element.getElementById('MoMAShareEmails').setProperty('value', "One or more e-mail addresses separated by commas");
    this.element.getElements('#MoMAShareEmailSubject, #MoMAShareEmailBody, #MoMAShareSMSBody').setProperty('value', '');
  },
  
  
  getDomain: function()
  {
    var domain = "http://" + window.location.hostname;
    if(window.location.port) domain += ":" + window.location.port;
    domain = "http://moma.org";
    return domain;
  },
  
  
  updateShareEmailForm: function()
  {
    var shareData = this.__currentShareData;
    
    // Update Email!
    if(shareData.image == null)
    {
      $('MoMAShareByWrapper').addClass('NoImage');
      $('MoMAShareByFeedback').addClass('SSDisplayNone');
    }
    else
    {
      $('MoMAShareByWrapper').removeClass('NoImage');
      $('MoMAShareByFeedback').removeClass('SSDisplayNone');
      // Update email feedback
      this.element.getElement('#MoMAShareByFeedback p').setStyles({
        backgroundImage: "url({image})".nsubstitute(shareData)
      });
    }
    
    // check to see if this an event, if it is we need to modify the shareData.title
    // because events have had their titles modified to be "title | datetimeinfo"
    if(shareData.type == 2)
    {
      shareData.title = shareData.title.split('|')[0];
    }
    
    // not sure when we'll get these into the data
    if(shareData.filterterm) shareData.filterterm = shareData.filterterm + '\n';
    if(shareData.programname) shareData.programname = shareData.programname + '\n';
    
    this.fetchShareCopy(shareData.type, 'mail', false, function(json) {
      var subject = json.subject.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title || '',
        date: shareData.date || '',
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || ''
      });
      var message = json.body.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title || '',
        date: (shareData.date && shareData.date.trim()) || '',
        description: shareData.description || '',
        location: shareData.location || '',
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || '',
        filterterm: shareData.filterterm || '',
        programname: shareData.programname || '',
        language: shareData.language || '',
        special: shareData.special || ''
      });
      $('MoMAShareEmailSubject').setProperty('value', subject);
      $('MoMAShareEmailBody').setProperty('value', message);
    }.bind(this));
    
    this.fetchShareCopy(shareData.type, 'mail', true, function(json) {
      this.mailSelfSubjectCopy = json.body.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title || '',
        date: shareData.date || '',
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || ''
      });
      this.mailSelfBodyCopy = json.body.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title || '',
        date: (shareData.date && shareData.date.trim()) || '',
        description: shareData.description || '',
        location: shareData.location || '',
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || '',
        filterterm: shareData.filterterm || '',
        programname: shareData.programname || '',
        language: shareData.language || '',
        special: shareData.special || ''
      });
    }.bind(this));
    
    var feedBackCopy = this.feedback[this.types[shareData.type]];
    if(feedBackCopy)
    {
      this.element.getElement('#MoMAShareByFeedback span.attribution').set('text', feedBackCopy.nsubstitute({
        username:ShiftSpaceUser.getUsername()
      }));
      this.element.getElement('#MoMAShareByFeedback span.title').set('text', shareData.title);
    }
  },
  
  
  updateShareSMSForm: function()
  {
    var shareData = this.__currentShareData;
    
    $$('.MoMAUserMobilePhone').set('text', ShiftSpaceUser.phone());
    
    if(ShiftSpaceUser.phoneValidated() == 1)
    {
      $('SendTextToCurrentUser').removeProperty('disabled');
      $('SendTextToCurrentUser').setProperty('enabled');
    }
    else
    {
      $('SendTextToCurrentUser').removeProperty('enabled');
      $('SendTextToCurrentUser').setProperty('disabled');
    }
    
    // Update SMS!
    this.fetchShareCopy(shareData.type, 'text', false, function(json) {
      var message = json.body.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title,
        date: shareData.date,
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || '',
        location: shareData.location || '',
        filterterm: shareData.filterterm || ''
      });
      $('MoMAShareSMSBody').setProperty('value', message);
    }.bind(this));
    
    this.fetchShareCopy(shareData.type, 'text', true, function(json) {
      this.smsSelfCopy = json.body.nsubstitute({
        username: ShiftSpaceUser.getUsername(),
        title: shareData.title,
        date: shareData.date,
        href: (shareData.href && [this.getDomain(), shareData.href].join('')) || '',
        location: shareData.location || '',
        filterterm: shareData.filterterm || ''
      });
    }.bind(this));
  },
  
  
  onTransact: function(collection, transaction)
  {
    var newTransaction = transaction;
    
    if(collection == this.WorksCollection && transaction.action == 'delete')
    {
      // remove these works from any sets as well
      var ary = [transaction];
      
      var deleteFromAllUserSets = {
        action: 'delete',
        table: 'worksinset',
        constraints: {
          artworkid: this.WorksCollection.byId(transaction.constraints.id).artworkid,
          userid: ShiftSpaceUser.getId()
        }
      };
      
      ary.push(deleteFromAllUserSets);
      newTransaction = ary;
    }
    else if(collection == this.WorksCollection && transaction.action == 'read')
    {
      // grab the list of sets to which an artwork belongs
      var ary = [transaction];
      
      var getInSets = {
        action: 'read',
        table: 'worksinset',
        properties: "worksinset.setid",
        constraints: {
          'worksinset.artworkid': '%artworkid%',
          'worksinset.userid': ShiftSpaceUser.getId()
        }
      };
      
      ary.push(getInSets);
      newTransaction = ary;
    }
    else if(collection == this.SetsCollection && transaction.action == 'read')
    {
      var ary = [transaction];
      
      var getArtworkCountInSet = {
        action: 'read',
        table: 'worksinset',
        properties: 'COUNT(*)',
        constraints: {setid: '%id%'}
      };
      
      ary.push(getArtworkCountInSet);
      newTransaction = ary;
    }
    else if(collection == this.SetsCollection && transaction.action == 'delete')
    {
      // remove these works from any sets as well
      var ary = [transaction];
      
      var deleteAllWorksInSet = {
        action: 'delete',
        table: 'worksinset',
        constraints: {
          setid: transaction.constraints.id,
          userid: ShiftSpaceUser.getId()
        }
      };
      
      ary.push(deleteAllWorksInSet);
      newTransaction = ary;
    }
    
    return newTransaction;
  },
  
  
  showSet: function(e)
  {
    var event = new Event(e);
    var setId = event.target.getProperty('id').split('-')[1];
    var setIndex = SSCollectionForName('Sets').indexWhere(function(x){return x.id == setId;});
    
    this.MoMAMainView.selectTabByName('MoMAMySetsView');
    this.MoMAMySetsView.editSetByIndex(setIndex);
  },
  
  
  save: function(_json)
  {
    var json = _json;
    
    if(!ShiftSpaceUser.isLoggedIn())
    {
      alert("Sorry, you must be logged in to do that.");
      return;
    }

    if(!this.isOpen())
    {
      this.showConsole();
    }
    
    // assign the userid to it
    var data = $merge(json.data, {userid:ShiftSpaceUser.getId()});

    switch(json.type)
    {
      case 'bookmark':
        this.saveBookmark(data);
        break;
      case 'work':
        this.saveWork(data);
        break;
      default:
        break;
    }
  },
  
  
  initForm: function(aForm)
  {
    aForm.addEvent('submit', function(_evt) {
      var event = new Event(_evt);
      event.preventDefault();
      aForm.fireEvent('onSubmit');
    });
  },
  
  
  initForms: function()
  {
    $$('#MoMALoginRegisterView form').each(this.initForm.bind(this));
    
    $('MoMALoginForm').addEvent('onSubmit', this.loginUser.bind(this));
    $('MoMARegisterForm').addEvent('onSubmit', this.registerUser.bind(this));
    $('MoMAMobileForm').addEvent('onSubmit', this.linkToPhone.bind(this));
  },
  
  
  initTextInputs: function()
  {
    $$('#MoMAConsole input, #MoMAConsole textarea').addEvent('focus', function(_evt) {
      var evt = new Event(_evt);
      evt.target.select();
      evt.preventDefault();
    }.bind(this));
  },
  
  
  openToRegister: function()
  {
    this.showConsole();
    this.MoMALoginRegisterView.selectTabByName('MoMARegisterView');
  },
  
  
  watchViews: function()
  {
    this.MoMAMySetsView.MoMAEditSetView.addEvent('viewChanged', this.onViewChanged.bind(this));
    this.MoMAMySetsView.addEvent('viewChanged', this.onViewChanged.bind(this))
  },
  
  
  onViewChanged: function(view)
  {
    // do some stuff here to act on view changes
    switch(view)
    {
      case this.MoMAMySetsView:
        // update the sets list
        this.MoMAMySetsView.MoMASetsListView.setNeedsDisplay(true);
        // update the saved artworks
        this.MoMAWorksView.MoMAWorksListView.setNeedsDisplay(true);
        break;
        
      case this.MoMAMySetsView.MoMAEditSetView:
        // update the sets list
        this.MoMAMySetsView.MoMASetsListView.setNeedsDisplay(true);
        
        // update the current set
        this.MoMAMySetsView.MoMAEditSetView.MoMACurrentSet.setNeedsDisplay(true);
        // update the edit set bookmark list
        this.MoMAMySetsView.MoMAEditSetView.MoMABookmarkedWorks.setNeedsDisplay(true);
        
        // update the saved artworks
        this.MoMAWorksView.MoMAWorksListView.setNeedsDisplay(true);
        break;
    
      case this.MoMAWorksView:
        // Update the sets list
        this.MoMAMySetsView.MoMASetsListView.setNeedsDisplay(true);
        
        // update the current set
        this.MoMAMySetsView.MoMAEditSetView.MoMACurrentSet.setNeedsDisplay(true);
        // update the edit set bookmark list
        this.MoMAMySetsView.MoMAEditSetView.MoMABookmarkedWorks.setNeedsDisplay(true);
        break;
        
      default:
        break;
    }
  },
  

  invalidateViews: function()
  {
    this.MoMAMySetsView.MoMASetsListView.setNeedsDisplay(true);
    this.MoMAMySetsView.MoMAEditSetView.MoMACurrentSet.setNeedsDisplay(true);
    this.MoMAMySetsView.MoMAEditSetView.MoMABookmarkedWorks.setNeedsDisplay(true);
    this.MoMAWorksView.MoMAWorksListView.setNeedsDisplay(true);
  }

});

if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAConsole = MoMAConsole;


// End ../momasocialbar/views/MoMAConsole/MoMAConsole.js ----------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAConsole');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAWorksView/MoMAWorksCell.js...', SSInclude);

// Start ../momasocialbar/views/MoMAWorksView/MoMAWorksCell.js ----------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSCell
// ==/Builder==

var MoMAWorksCell = new Class({

  Extends: SSCell,

  name: "MoMAWorksCell",
  
  clone: function()
  {
    var clone = this.parent();
    
    clone.getElement('select.addToSet').addEvent('change', function(_evt) {
      
      var event = new Event(_evt);
      var target = event.target;
      var cellNode = target.getParent('li');
      
      this.lock(cellNode);
      ShiftSpaceNameTable.get('MoMAWorksView').addToSet(this.delegate().indexOf(this), target.getProperty('value'));
      this.unlock();
      
    }.bind(this));
    
    clone.getElement('textarea').addEvent('focus', function(_evt) {
      var evt = new Event(_evt);
      evt.target.select();
    }.bind(this));
    
    return clone;
  },
  
  
  awake: function(context)
  {
    this.mapOutletsToThis();
  },

  
  setOnview: function(onView)
  {
    var el = this.lockedElement();
    if(onView == 1)
    {
      el.getElement('.notonview').set('text', 'On View');
    }
  },
  
  
  getOnview: function()
  {
    
  },
  
  
  edit: function()
  {
    this.parent();
    var el = this.lockedElement();
  },
  
  
  leaveEdit: function()
  {
    this.parent();
  },
  
  
  setInSets: function(inSets)
  {
    var el = this.lockedElement();
    
    // TODO: only calculate the following on edit, use DOM storage to hold the array - David
    var byId = function(id) {
      return function(x) {
        return x.id == id;
      }
    };

    var coll = SSCollectionForName('Sets');
    
    // IE6 fix
    if(!coll.getArray()) return;
    
    var theSets = inSets.map(function(setId) {
      return coll.query(byId(setId), ['id', 'title']);
    });
    theSets = theSets.flatten();
    
    if(theSets.length == 0) 
    {
      el.getElement('.WorksEditFeedback > span').addClass('SSDisplayNone');
    }
    else
    {
      el.getElement('.WorksEditFeedback > span').removeClass('SSDisplayNone');
    }
    
    var spanEl = el.getElement('.setList');
    spanEl.empty();
    theSets.reverse().each(function(set) {
      var setLink = new Element('a');
      setLink.set('id', 'showSet-' + set.id);
      setLink.set('text', set.title);
      setLink.addEvent('click', SSAppDelegate().showSet.bindWithEvent(SSAppDelegate()));
      spanEl.grab(setLink);
    });
    
    // update select drop down menu
    var addToSetList = SSCollectionForName('Sets').getArray().filter(function (row) {
      return !inSets.contains(row.id);
    });
    
    // update the dom list
    var addToSetNode = el.getElement('select.addToSet');
    el.getElements('option.set').dispose();

    addToSetList.each(function(aset) {
      var setOption = new Element('option');
      setOption.setProperty('value', aset.id);
      setOption.addClass('set');
      setOption.set('text', aset.title);
      addToSetNode.grab(setOption);
    });
  },
  
  
  setArtworkid: function(artworkId)
  {
    var el = this.lockedElement();
    el.store('artworkId', artworkId);
  },
  
  
  getArtworkid: function()
  {
  },
  
  
  setHref: function(href)
  {
    var el = this.lockedElement();
    
    el.getElement('.NormalView').removeEvents('click');
    el.getElement('.NormalView').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      var target = evt.target;

      if(target.hasClass('edit') ||
         target.hasClass('close'))
      {
        return;
      }

      if(target.get('tag') != 'li') target = target.getParent('li');
      window.open('/collection/object.php?object_id=' + target.retrieve('artworkId'));
    }.bind(this));
  },
  
  
  getHref: function()
  {
    
  },
  
  
  getInSets: function()
  {
    
  },
  
  
  setImage: function(imageSrc)
  {
    var el = this.lockedElement();
    if(imageSrc == 'NULL' || imageSrc == null)
    {
      el.getElement('img').addClass('noImage');
      el.getElement('.artist').addClass('noImage');
      el.getElement('.title').addClass('noImage');
      el.getElement('.noImageMessage').addClass('noImage');
    }
    else
    {
      el.getElement('.artist').removeClass('noImage');
      el.getElement('.title').removeClass('noImage');
      el.getElement('.noImageMessage').removeClass('noImage');
      el.getElement('img').removeClass('noImage');
      el.getElement('img').setProperty('src', imageSrc);
    }
  },
  
  
  getImage: function()
  {
  },
  
  
  setUsernote: function(note)
  {
    var el = this.lockedElement();
    el.getElement('.note').setProperty('value', note);
  },
  
  
  getUsernote: function()
  {
    var el = this.lockedElement();
    return el.getElement('.note').getProperty('value');
  },
  
  
  setTitle: function(title)
  {
    var el = this.lockedElement();
    el.getElement('.title').set('html', title);
  },
  
  
  getTitle: function()
  {
    return this.__title
  },
  

  setArtist: function(artist)
  {
    var el = this.lockedElement();
    el.getElement('.artist').set('html', artist);
  },
  
  
  getArtist: function()
  {
    
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAWorksCell = MoMAWorksCell;


// End ../momasocialbar/views/MoMAWorksView/MoMAWorksCell.js ------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAWorksCell');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAWorksView/MoMAWorksView.js...', SSInclude);

// Start ../momasocialbar/views/MoMAWorksView/MoMAWorksView.js ----------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSView
// ==/Builder==

var MoMAWorksView = new Class({

  Extends: SSView,

  name: "MoMAWorksView",

  initialize: function(el, options)
  {
    this.parent(el, options);
    
    ShiftSpaceUser.addEvent('onUserQuery', this.onUserQuery.bind(this));
  },
  
  
  canRemove: function(data)
  {
    if(data.listView == this.MoMAWorksListView)
    {
      if(confirm("Are you sure you want to delete this artwork? There is no undo."))
      {
        return true;
      }
    }
    return false;
  },
  
  
  byArtworkId: function(artworkid)
  {
    return function(x) { return x.artworkid == artworkid; };
  },


  addWork: function(data)
  {
    // if in the middle of a read, restart
    if(SSCollectionForName('Works').isReading())
    {
      SSCollectionForName('Works').addOnReadFn(this.addWork.bind(this, [data]));
      return;
    }
    
    // if unread restart
    if(SSCollectionForName('Works').isUnread())
    {
      SSCollectionForName('Works').read(this.addWork.bind(this, [data]));
      return;
    }

    // add only if it hasn't been saved before
    if(!SSCollectionForName('Works').find(this.byArtworkId(data.artworkid)))
    {
      this.MoMAWorksListView.add(data, {atIndex:0});
    }
  },
  
  
  updateMessage: function(message)
  {
    SSLog('updateMessage', SSLogForce);
    
    // don't flash 0 count - David
    if(!SSCollectionForName('Works').isUnread())
    {
      var len = SSCollectionForName('Works').length();
      this.element.getElement('.SSMessage').set('text', 'You have saved {count} {countCopy} from the collection'.substitute({
        count: len,
        countCopy: ((len == 1) && 'work') || 'works'
      }));
    }
  },
  
  
  show: function()
  {
    // Sets needs to be read first
    if(SSCollectionForName('Sets').isUnread())
    {
      SSCollectionForName('Sets').read(this.show.bind(this));
      return;
    }
    
    this.parent();
    this.updateMessage();
  },
  
  
  onUserQuery: function()
  {
    SSCollectionForName('WorksView').addEvent('onCreate', this.updateCell.bind(this));
  },
  
  
  setCellIndexToUpdate: function(index)
  {
    this.__cellIndexToUpdate = index;
  },

  
  cellIndexToUpdate: function()
  {
    return this.__cellIndexToUpdate;
  },
  
  
  awake: function(context)
  {
    this.mapOutletsToThis();
    SSLog('>>>>>>>>>>>>>>>>> creating page control', SSLogForce);
    this.MoMAWorksListView.addEvent('onReloadData', this.updateMessage.bind(this));
    this.pageControl = new SSPageControl($('MoMAWorksListPagination'), this.MoMAWorksListView);
  },
  

  afterAwake: function(context)
  {
    SSCollectionForName('Works').addEvent('onLoad', function() {
      this.updateMessage();
    }.bind(this));
    
    SSCollectionForName('Works').addEvent('onRead', function() {
      this.updateMessage();
    }.bind(this));
    
    this.MoMAWorksListView.addEvent('onRemove', function(index) {
      this.updateMessage();
      this.fireEvent('viewChanged', this);
    }.bind(this));
  },
  
  
  animationFor: function(data)
  {
    if(data.action == "remove")
    {
      return this.removeAnimation(data);
    }
    else if(data.action == "edit")
    {
      return this.editAnimation(data);
    }
    else if(data.action == 'leaveEdit')
    {
      return this.leaveEditAnimation(data);
    }
    else if(data.action == 'add')
    {
      return this.addAnimation(data);
    }
  },
  
  // FIXME: move into MoMAConsole this code repeated from MoMAEditSetView.js - David
  updateSetImage: function(data, setId)
  {
    var oldConstraints = SSCollectionForName('Sets').constraints();
    SSCollectionForName('Sets').updateConstraints('id', setId);
    SSCollectionForName('Sets').update({image: data.setimage});
    SSCollectionForName('Sets').setConstraints(oldConstraints);
  },
  
  
  addToSet: function(index, setId)
  {
    SSCollectionForName('Sets').read(function() {
      var workData = SSCollectionForName('Works').get(index);
      this.setCellIndexToUpdate(index);
    
      var byId = function(id) {
        return function(x) {
          return x.id == id;
        }
      };
    
      var theSet = SSCollectionForName('Sets')['find'](byId(setId));
      var position = parseInt(theSet.count) + 1;
    
      if(position == 1)
      {
        // update the set image
        this.updateSetImage(workData, setId);
      }
      
      // TODO: we probably need to upate that set, probably just incrementing it ourselves is fine - David
      SSCollectionForName('WorksView').create({
        artworkid: workData.artworkid,
        savedartworkid: workData.id,
        setid: setId,
        userid: ShiftSpaceUser.getId(),
        position: position
      });
    }.bind(this));
  },
  
  
  updateCell: function()
  {
    var index = this.cellIndexToUpdate(); 
    
    SSCollectionForName('Works').readIndex(index, 'artworkid', function(data) {
      // update the view, not the model
      this.MoMAWorksListView.updateCellView(data, index);
      // clear out the index
      this.setCellIndexToUpdate(null);
      this.fireEvent('viewChanged', this);
    }.bind(this));
  },
  
  
  canDelete: function(listView)
  {
    var value = confirm("Are you sure that you want to delete this artwork? This will also remove this artwork from any sets to which it belongs.  There is no undo.");
    return value;
  },
  
  
  addAnimation: function(data)
  {
    var cellSize = this.MoMAWorksListView.options.cellSize;
    
    // create the background cell
    var backgroundCell = new Element('li', {
      'class': 'backgroundCell',
      styles:
      {
        top: 0,
        width: 0,
        height: cellSize.y,
        zIndex: 0,
        backgroundColor: 'black',
        opacity: '0.3'
      }
    });
    
    // set the tween
    backgroundCell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    var newCell = data.listView.newCellForItemData(data.userData, 0);
    newCell.setStyles({
      opacity: 0.3
    });
    newCell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    return function() {
      return {
        animation: function(callback) {
          backgroundCell.inject(data.listView.element, 'top');
          backgroundCell.get('tween').start('width', 154).chain(function() {
            newCell.replaces(backgroundCell);
            newCell.get('tween').start('opacity', 1).chain(callback);;
          });
        }.bind(this),
        cleanup: function() {
          SSLog('cleaning up', SSLogForce);
          this.fireEvent('viewChanged');
        }.bind(this)
      };
    };
  },
  
  
  removeAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    var cellSize = cell.getSize(data.listView.element);
    var cellPos = cell.getPosition(data.listView.element);
    
    cell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    // create the background cell
    var backgroundCell = new Element('div', {
      'class': 'backgroundCell',
      styles:
      {
        left: cellPos.x,
        top: cellPos.y,
        width: cellSize.x,
        height: cellSize.y,
        position: 'relative',
        zIndex: 0,
        backgroundColor: 'black',
        opacity: '0.3'
      }
    });
    
    backgroundCell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    return function() {
      return {
        animation: function() {
          data.listView.element.grab(backgroundCell);
          return cell.get('tween').start('opacity', 0).chain(function() {
            backgroundCell.get('tween').start('width', 0);
            cell.get('tween').start('width', 0);
          });
        }
      }
    };
  },
  
  
  editAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    
    cell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeIn
    });
    
    return function() {
      return {
        animation: function() {
          cell.setStyle('backgroundPosition', '0px -280px');
          return cell.get('tween').start('width', 535);
        },
        cleanup: function() {
          cell.removeClass('WorksCell85');
        }
      };
    };
  },
  
  
  leaveEditAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    
    cell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeIn
    });
    
    return function() {
      return {
        animation: function() {
          cell.getElement('.SSEditView').removeClass('SSActive');
          return cell.get('tween').start('width', 154);
        },
        cleanup: function() {
          cell.setStyle('width', '');
          cell.setStyle('backgroundPosition', '');
        } 
      };
    };
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAWorksView = MoMAWorksView;


// End ../momasocialbar/views/MoMAWorksView/MoMAWorksView.js ------------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAWorksView');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMABookmarksView/MoMABookmarksCell.js...', SSInclude);

// Start ../momasocialbar/views/MoMABookmarksView/MoMABookmarksCell.js --------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSCell
// ==/Builder==

var MoMABookmarksCell = new Class({

  Extends: SSCell,

  name: "MoMABookmarksCell",
  
  initialize: function(el, options)
  {
    this.parent(el, options);
    // TODO: should probably come from MoMA metadata - David
    this.types = ['Exhibition', 'Film Exhibition', 'Event', 'Film Screening', 'Artwork', 'Artist', 'Page'];
  },
  
  
  clone: function()
  {
    var clone = this.parent();
    
    // update cell on enter
    clone.getElement('.note').addEvent('keyup', function(_evt) {
      
      var event = new Event(_evt);
      var target = event.target;
      var cellNode = target.getParent('li');
      var delegate = this.delegate();
      
      if(event.key == 'enter')
      {
        this.lock(cellNode);
        delegate.update(this.getData('note'), delegate.indexOfCellNode(cellNode));
        this.unlock();
      }
      
    }.bind(this));
    
    // edit cell on click
    clone.getElement('.note').addEvent('click', function(_evt) {
      
      var evt = new Event(_evt);
      var cellNode = evt.target.getParent('li');
      
      if(!cellNode.hasClass('SSIsBeingEdited'))
      {
        this.lock(cellNode);
        this.delegate().editObject(this);
        this.unlock(cellNode);
      }
      
      return true;
    }.bind(this));
    
    clone.getElement('.note').addEvent('keyup', function(_evt) {
      
      var evt = new Event(_evt);
      var cellNode = evt.target.getParent('li');
      
      if(cellNode.hasClass('SSIsBeingEdited') && evt.key == 'enter')
      {
        this.lock(cellNode);
        this.delegate().updateObject(this);
        this.unlock(cellNode);
        cell.getElement('.note').blur();
      }
      
      return true;
    }.bind(this));
    
    return clone;
  },
  
  
  setImage: function(imageSrc)
  {
    var el = this.lockedElement();
    if(!imageSrc)
    {
      el.getElement('img').setStyle('display', 'none');
      el.getElement('.MoMABookmarkMeta').addClass('NoImage');
    }
    else
    {
      el.getElement('img').setProperty('src', imageSrc);
    }
  },
  
  
  getImage: function()
  {
  },
  
  
  setHref: function(href)
  {
    var el = this.lockedElement();
    
    el.removeEvents('click');
    el.getElement('.href').setProperty('href', href);
    
    el.addEvent('click', function(_evt) {
      var event = new Event(_evt);
      
      // don't capture click on share or delete buttons
      if(event.target.hasClass('share') ||
         event.target.hasClass('edit') ||
         event.target.hasClass('save') ||
         event.target.hasClass('cancel') ||
         event.target.hasClass('.MoMAInputUI') ||
         event.target.getParent('.MoMAInputUI')) return;;
      
      if(href)
      {
        window.open(href);
      }
      else
      {
      }
    }.bind(this));
  },
  

  edit: function()
  {
    this.parent();
    
    var el = this.lockedElement();

    var noteInput = el.getElement('.note');
    
    noteInput.setStyles({
      '-moz-user-select': 'text',
      'user-select': 'text',
      '-webkit-user-select': 'text'
    });
    noteInput.removeClass('SSUserSelectNone');
    noteInput.removeProperty('readonly');
    
    noteInput.select();
  },
  

  leaveEdit: function()
  {
    this.parent();
    
    var el = this.lockedElement();
    
    var noteInput = el.getElement('.note');
    noteInput.setStyles({
      '-moz-user-select': '',
      'user-select': '',
      '-webkit-user-select': ''
    });
    noteInput.addClass('SSUserSelectNone');
    noteInput.setProperty('readonly', "");
  },

  
  getHref: function()
  {
  },
  

  setTitle: function(title)
  {
    var el = this.lockedElement();
    el.getElement('.title').set('text', title);
  },
  
  
  getTitle: function()
  {
  },
  
  
  setNote: function(note)
  {
    var el = this.lockedElement();
    var noteText = note || 'Add your notes here';
    el.getElement('.note').set('value', noteText);
  },
  
  
  getNote: function()
  {
    var el = this.lockedElement();
    var newNote = el.getElement('.note').getProperty('value');
    if(newNote == 'Add your notes here') newNote = null;
    return newNote;
  },
  
  
  setType: function(type)
  {
    var el = this.lockedElement();
    el.getElement('.type').set('text', this.types[type]);
  },
  
  
  getType: function()
  {
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMABookmarksCell = MoMABookmarksCell;


// End ../momasocialbar/views/MoMABookmarksView/MoMABookmarksCell.js ----------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMABookmarksCell');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMABookmarksView/MoMABookmarksView.js...', SSInclude);

// Start ../momasocialbar/views/MoMABookmarksView/MoMABookmarksView.js --------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSView
// ==/Builder==

var MoMABookmarksView = new Class({

  Extends: SSView,

  name: "MoMABookmarksView",
  
  awake: function(context)
  {
    this.mapOutletsToThis();
    
    // initialize the message
    this.element.getElement('.SSMessage').set('tween', {
      duration: 500,
      transition: Fx.Transitions.Cubic.easeOut,
      onComplete: function()
      {
        this.element.getElement('.SSMessage').setStyle('opacity', 1);
        this.updateMessage();
      }.bind(this)
    });
  },
  
  
  canRemove: function(data)
  {
    if(data.listView == this.MoMABookmarksListView)
    {
      if(confirm("Are you sure you want to delete this bookmark? There is no undo."))
      {
        return true;
      }
    }
    return false;
  },
  
  
  afterAwake: function(context)
  {
    this.MoMABookmarksListView.addEvent('onAdd', function(data) {
      this.updateMessage('<b>' + data.title + '</b> saved');
      this.fadeMessage.delay(3000, this);
    }.bind(this));
    
    this.MoMABookmarksListView.addEvent('onRemove', function(data) {
      this.updateMessage();
    }.bind(this));
    
    this.MoMABookmarksListView.addEvent('onReloadData', function(data) {
      this.updateMessage();
    }.bind(this));
  },
  
  
  fadeMessage: function()
  {
    this.element.getElement('.SSMessage').get('tween').start('opacity', 0);
  },
  
  
  updateMessage: function(_message)
  {
    var message = _message;
    if(!message)
    {
      // don't flash 0 count - David
      if(!SSCollectionForName('Bookmarks').isUnread())
      {
        var len = SSCollectionForName('Bookmarks').length();
        message = 'You currently have {count} {countCopy}'.substitute({
          count: len,
          countCopy: ((len == 1) && 'bookmark') || 'bookmarks'
        });
      }
    }
    
    this.element.getElement('.SSMessage').set('html', message);
  },
  
  
  show: function()
  {
    this.parent();
    this.updateMessage();
  },
  
  
  dataFor: function(action, sender)
  {
    if(action == 'add' && sender == this.MoMABookmarksListView)
    {
      return {userid:ShiftSpaceUser.getId()};
    }
    return null;
  },
  
  
  refresh: function()
  {
    this.parent();
  },
  
  
  byHref: function(href)
  {
    return function(x) { return x.href == href; };
  },
  
  
  addBookmark: function(data)
  {
    if(SSCollectionForName('Bookmarks').isReading())
    {
      SSCollectionForName('Bookmarks').addOnReadFn(this.addBookmark.bind(this, [data]));
    }
    else
    {
      if(!SSCollectionForName('Bookmarks').find(this.byHref(data.href)))
      {
        this.MoMABookmarksListView.add(data);
      }
    }
  },
  
  
  sharePage: function(sender)
  {
    var index = this.MoMABookmarksListView.indexOf(sender);
    var data = this.MoMABookmarksListView.get(index);
    // FIXME: needs to be fixed too hacky! - David
    SSAppDelegate().shareObject('#bookmark', null, data);
  },
  
  
  animationFor: function(data)
  {
    if(data.action == 'remove')
    {
      return this.removeAnimation(data);
    }
  },
  
  
  addAnimation: function(data)
  {
    // we have to create cell with the userData
  },
  
  
  removeAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    var cellSize = cell.getSize(data.listView.element);
    var cellPos = cell.getPosition(data.listView.element);

    cell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    // create the background cell
    var backgroundCell = new Element('div', {
      'class': 'backgroundCell',
      styles:
      {
        left: cellPos.x,
        top: cellPos.y,
        width: cellSize.x,
        height: cellSize.y,
        position: 'absolute',
        zIndex: 0,
        backgroundColor: 'black',
        opacity: '0.3'
      }
    });
    
    backgroundCell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    return function() {
      return {
        animation: function() {
          data.listView.element.grab(backgroundCell);
          return cell.get('tween').start('opacity', 0).chain(function() {
            backgroundCell.get('tween').start('height', 0);
            cell.get('tween').start('height', 0);
          });
        }
      }
    };
  }
  
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMABookmarksView = MoMABookmarksView;


// End ../momasocialbar/views/MoMABookmarksView/MoMABookmarksView.js ----------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMABookmarksView');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAMySetsView/MoMAMySetsView.js...', SSInclude);

// Start ../momasocialbar/views/MoMAMySetsView/MoMAMySetsView.js --------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSMultiView
// ==/Builder==

var MoMAMySetsView = new Class({

  Extends: SSMultiView,

  name: "MoMAMySetsView",

  initialize: function(el, options)
  {
    this.parent(el, options);
  },
  
  
  attachEvents: function()
  {
    this.element.getElementById('MoMACreateSet').addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      
      // FIXME: remove when we support read - David
      this.MoMAEditSetView.newSet(function() {
        this.showView(1);
        this.fireEvent('viewChanged', this);
      }.bind(this));
      
    }.bind(this));
  },
  

  awake: function(context)
  {
    this.mapOutletsToThis();
    this.attachEvents();
  },
  
  
  afterAwake: function(context)
  {
    this.MoMASetsListView.addEvent('onReloadData', this.updateMessage.bind(this));
  },
  
  
  show: function()
  {
    this.parent();
    this.updateMessage();
  },
  
  
  updateMessage: function()
  {
    // don't flash 0 count - David
    if(!SSCollectionForName('Sets').isUnread())
    {
      var len = SSCollectionForName('Sets').length();
      this.element.getElement('#MoMAAllSetsView .SSMessage').set('text', "You currently have {count} {countCopy}".substitute({
        count: len,
        countCopy: ((len == 1) && 'set') || 'sets'
      }));
    }
  },
  

  showView: function(idx)
  {
    this.parent(idx);
    if(idx == 0)
    {
      this.updateMessage();
    }
  },
  
  
  editSet: function(sender)
  {
    var index = this.MoMASetsListView.indexOf(sender);
    this.editSetByIndex(index);
  },
  
  
  editSetByIndex: function(index)
  {
    var data = this.MoMASetsListView.query(index, ['id', 'title', 'setnote', 'count']);
    // add the set index
    data.setIndex = index;
    
    // set the current set
    this.MoMAEditSetView.setSet(data);
    this.MoMAEditSetView.MoMABookmarkedWorks.setNeedsDisplay(true);
    this.MoMAEditSetView.MoMACurrentSet.setNeedsDisplay(true);
    
    this.showViewByName('MoMAEditSetView');
  },
  
  
  shareSet: function(sender)
  {
    var index = this.MoMASetsListView.indexOf(sender);
    var data = this.MoMASetsListView.get(index);
    SSAppDelegate().shareObject('#set', null, data);
  }

});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAMySetsView = MoMAMySetsView;


// End ../momasocialbar/views/MoMAMySetsView/MoMAMySetsView.js ----------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAMySetsView');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAMySetsView/MoMAAllSetsCell.js...', SSInclude);

// Start ../momasocialbar/views/MoMAMySetsView/MoMAAllSetsCell.js -------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSCell
// ==/Builder==

var MoMAAllSetsCell = new Class({

  Extends: SSCell,

  name: "MoMAAllSetsCell",
  
  clone: function()
  {
    var clone = this.parent();
    
    clone.addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      var target = evt.target;
      var cellNode = (target.get('tag') != 'li') ? target.getParent('li') : target;
      
      if(!target.hasClass('MoMAShareSet') &&
         !target.hasClass('MoMAEditSet') &&
         !target.hasClass('MoMAShareOrEditButtons'))
      {
        this.lock(cellNode);
        var idx = this.delegate().indexOf(this);
        window.location = '/shared/' + this.delegate().get(idx).id;
        this.unlock();
      }
    }.bind(this));
    
    return clone;
  },
  
  setTitle: function(title)
  {
    var el = this.lockedElement();
    el.getElement('span.title').set('text', title);
    this.__title = title;
  },
  
  
  getTitle: function()
  {
    return this.__title
  },
  
  
  setImage: function(imageSrc)
  {
    var el = this.lockedElement();
    
    el.getElement('.MoMASetWrapper').setStyles({
      backgroundImage: 'url('+imageSrc+')'
    });
    
    this.__imageSrc = imageSrc
  },
  
  
  getImage: function()
  {
    return this.__imageSrc;
  },
  
  
  setSetnote: function(setNote)
  {
    var el = this.lockedElement();
    el.getElement('span.MoMASetDescription').set('text', setNote);
    this.__setNote = setNote;
  },
  
  
  getSetnote: function()
  {
    this.__setNote;
  },
  
  
  setCount: function(count)
  {
    var el = this.lockedElement();
    el.getElement('span.MoMASetAmount').set('text', '('+count+')');
    this.__count = count;
  },
  
  
  getCount: function()
  {
    return this.__count;
  }
});


if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAAllSetsCell = MoMAAllSetsCell;


// End ../momasocialbar/views/MoMAMySetsView/MoMAAllSetsCell.js ---------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAAllSetsCell');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAEditSetView/MoMAEditSetsWorksCell.js...', SSInclude);

// Start ../momasocialbar/views/MoMAEditSetView/MoMAEditSetsWorksCell.js ------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSCell
// ==/Builder==

var MoMAEditSetsWorksCell = new Class({

  Extends: SSCell,

  name: "MoMAEditSetsWorksCell",
  
  
  setNew: function(isNew)
  {
    var el = this.lockedElement();
    if(isNew)
    {
      el.getElement('.new').removeClass('SSDisplayNone')
    }
    else
    {
      el.getElement('.new').addClass('SSDisplayNone');
    }
  },
  
  
  getNew: function()
  {
    
  },
  
  
  setPosition: function(position)
  {
    var el = this.lockedElement();
    el.getElement('.position').set('text', position);
  },
  
  
  getPosition: function()
  {
    
  },


  setTitle: function(title)
  {
    var el = this.lockedElement();
    el.getElement('.title').set('html', title);
  },
  
  
  getTitle: function()
  {
    return this.__title
  },
  

  setArtist: function(artist)
  {
    var el = this.lockedElement();
    el.getElement('.artist').set('html', artist);
  },
  
  
  getArtist: function()
  {
    
  },
  
  
  setDatetext: function(datetext)
  {
    var el = this.lockedElement();
    el.getElement('.datetext').set('text', datetext);
  },
  
  
  getDatetext: function()
  {
    
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAEditSetsWorksCell = MoMAEditSetsWorksCell;


// End ../momasocialbar/views/MoMAEditSetView/MoMAEditSetsWorksCell.js --------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAEditSetsWorksCell');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAEditSetView/MoMAEditSetView.js...', SSInclude);

// Start ../momasocialbar/views/MoMAEditSetView/MoMAEditSetView.js ------------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSView
// ==/Builder==

var MoMAEditSetView = new Class({

  Extends: SSView,

  name: "MoMAEditSetView",
  
  initialize: function(el, options) 
  {
    this.parent(el, options);
    
    this.setCollectionsInitialized(false);
    this.setItemToAdd(-1);
    
    this.initCollections();
    
    // don't initialize the content until the user is logged in
    ShiftSpaceUser.addEvent('onUserQuery', this.onUserLogin.bind(this));
    ShiftSpaceUser.addEvent('onUserLogin', this.onUserLogin.bind(this));
    ShiftSpaceUser.addEvent('onUserJoin', this.onUserLogin.bind(this));
  },
  
  
  setCollectionsInitialized: function(value)
  {
    this.__isinitialized = value;
  },
  
  
  collectionsInitialized: function()
  {
    return this.__isinitialized;
  },
  
  
  setBlocked: function(val)
  {
    this.__blocked = val;
  },
  
  
  isBlocked: function()
  {
    return this.__blocked;
  },
  
  
  initCollections: function()
  {
    this.CurrentSetCollection = new SSCollection("CurrentSet", {
      table: 'worksinset',
      properties: '*',
      orderBy: ['<', 'position']
    });
  },
  
  
  onUserLogin: function(data)
  {
    if(ShiftSpaceUser.isLoggedIn())
    {
      this.CurrentSetCollection.updateConstraints('worksinset.userid', ShiftSpaceUser.getId());
      
      if(!this.collectionsInitialized())
      {
        this.setCollectionsInitialized(true);
        
        this.CurrentSetCollection.addEvent('onCreate', this.onAddItem.bind(this));
      
        SSCollectionForName('Sets').addEvent('onUpdate', this.onUpdate.bind(this));
        SSCollectionForName('Sets').addEvent('onDelete', this.onDeleteSet.bind(this));
      }
    }
  },
  
  
  newSet: function(callback)
  {
    // FIXME: remove when we support read
    SSCollectionForName('Sets').create({userid: ShiftSpaceUser.getId(), title:'A New Set'}, {
      onCreate: function(json) 
      {
        this.setSet({id:json.id, setIndex:0, count:0});
        if(callback && $type(callback) == 'function') callback(json);
      }.bind(this)
    });
  },
  
  
  setSet: function(setData)
  {
    // set the current set
    this.setCurrentSet(setData.id);
    this.setCurrentSetIndex(setData.setIndex);
    
    if(typeof setData.count != 'undefined')
    {
      if(setData.count == 0)
      {
        this.MoMASetBuilder.selectTab(1);
      }
      else
      {
        this.MoMASetBuilder.selectTab(0);
      }
    }

    // load it
    this.loadSet(setData.id);

    // update UI
    if(setData.title)
    {
      this.MoMAEditSetTitle.setProperty('value', setData.title);
    }
    else
    {
      this.MoMAEditSetTitle.setProperty('value', 'A New Set');
    }
    
    if(setData.setnote)
    {
      this.MoMAEditSetDescription.setProperty('value', setData.setnote);
    }
    else
    {
      this.MoMAEditSetDescription.setProperty('value', 'Add your notes here');
    }
  },
  
  
  setCurrentSet: function(id)
  {
    this.__currentSet = id;
  },

  
  currentSet: function()
  {
    return this.__currentSet;
  },
  
  // set the actual index (not id) of this as set as it is referred to in the sets collection
  setCurrentSetIndex: function(index)
  {
    this.__currentSetIndex = index;
  },
  
  
  currentSetIndex: function()
  {
    return this.__currentSetIndex;
  },
  
  
  loadSet: function(setId)
  {
    // TODO: update the setid constraint
    this.CurrentSetCollection.updateConstraints('setid', setId);
  },
  
  
  awake: function()
  {
    this.mapOutletsToThis();
    
    this.attachEvents();
    
    function acontains(a, n)
    {
      for(var i = 0, len = a.length; i < len; i++) { if(a[i] == n) return true; }
      return false;
    }
    
    // set the filter for the add work list
    this.MoMABookmarkedWorks.setFilter(function(x) {
      var inSets = x.inSets.map(function(n){return parseInt(n);});
      var inCurrentSet = acontains(inSets, this.currentSet());
      return inCurrentSet;
    }.bind(this));
    
    // listen for delete events on the current set
    /*
    this.MoMACurrentSet.addEvent('onRemove', function() {
      this.fireEvent('viewChanged', this);
    }.bind(this));
    */
  },
  
  
  attachEvents: function()
  {
    this.MoMADeleteSet.addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      if(confirm("Are you sure that you want to delete this set? There is no undo."))
      {
        this.deleteSet();
      }
    }.bind(this));
    
    // go back
    this.MoMACancelEditSet.addEvent('click', function(_evt) {
      var evt = new Event(_evt);
      this.getSuperView().showView(0);
    }.bind(this));
    
    this.element.getElementById('MoMASaveSet').addEvent('click', this.saveSet.bind(this));
    this.MoMACurrentSet.addEvent('onOrderChange', this.updatePositions.bind(this));
  },
  
  
  updatePositions: function()
  {
    positionUpdate = this.MoMACurrentSet.element.getElements('li').map(function(node) {
      var newIndex = this.MoMACurrentSet.indexOfCellNode(node)+1;
      var id = $(node).retrieve('refid');

      if(newIndex == 1)
      {
        this.updateSetImage(this.CurrentSetCollection.byId(id));
      }
      
      return this.CurrentSetCollection.updateById({position:newIndex}, id, true);
    }.bind(this));
    
    this.CurrentSetCollection.bulkTransact(positionUpdate, {
      onComplete: this.onPositionUpdate.bind(this)
    });
  },
  
  
  onPositionUpdate: function(response)
  {
    // force a refresh
    this.MoMACurrentSet.refresh(true);
    this.fireEvent('viewChanged', this);
  },
  
  
  saveSet: function()
  {
    var updateProperties = {
      title: this.MoMAEditSetTitle.getProperty('value'),
      setnote: this.MoMAEditSetDescription.getProperty('value')
    };
    
    // read then update
    SSCollectionForName('Sets').read(function() {
      SSCollectionForName('Sets').update(updateProperties, this.currentSetIndex());
    }.bind(this));
  },
  
  
  onUpdate: function(index)
  {
    if(this.isVisible())
    {
      var message = this.element.getElement('.SSMessage');
      message.set('text', 'Your set was updated');
      message.set('tween', {
        duration: 300,
        transition: Fx.Transitions.Cubic.easeOut,
        onComplete: function()
        {
          message.setStyle('opacity', 1);
          message.set('text', 'Edit your set');
        }.bind(this)
      });
      message.get('tween').start.delay(3000, message.get('tween'), ['opacity', 0]);
    }
    
    this.fireEvent('viewChanged', this);
  },
  
  
  deleteSet: function()
  {
    SSCollectionForName('Sets').read(function() {
      SSCollectionForName('Sets')['delete'](this.currentSetIndex());
    }.bind(this));
  },
  
  
  onDeleteSet: function(index)
  {
    // show the main view
    this.fireEvent('viewChanged', this);
    this.getSuperView().showView(0);
  },
  
  
  setItemToAdd: function(index)
  {
    this.__itemToAdd = index;
  },
  
  
  itemToAdd: function()
  {
    return this.__itemToAdd;
  },
  
  
  updateSetImage: function(data)
  {
    var oldConstraints = SSCollectionForName('Sets').constraints();
    SSCollectionForName('Sets').updateConstraints('id', this.currentSet());
    SSCollectionForName('Sets').update({image: data.setimage});
    SSCollectionForName('Sets').setConstraints(oldConstraints);
  },
  
  
  addToCurrentSet: function(sender)
  {
    if(!this.isBlocked())
    {
      this.setBlocked(true);
      var el = sender.lockedElement();
    
      // FIXME: remove when we support read, massive hack - David
      this.CurrentSetCollection.read(function(json) {
        var index = this.MoMABookmarkedWorks.indexOfCellNode(el);
        var data = this.MoMABookmarkedWorks.query(index, ['id', 'artworkid', 'setimage']);
      
        this.setItemToAdd(index);
      
        // FIXME: this should be a lot simpler - David
        if(this.CurrentSetCollection.length() == 0)
        {
          this.updateSetImage(data);
        }
        
        // add to the current set
        this.CurrentSetCollection.create({
          artworkid: data.artworkid,
          savedartworkid: data.id,
          setid: this.currentSet(),
          position: this.CurrentSetCollection.length()+1,
          userid: ShiftSpaceUser.getId()
        });
      }.bind(this));
    }
  },
  
  // called after create
  onAddItem: function(data)
  {
    if(this.itemToAdd() != -1)
    {
      // run hide animation on the list view
      this.MoMABookmarkedWorks.hideItem(this.itemToAdd());
      this.setItemToAdd(-1);
    }
    
    this.fireEvent('viewChanged', this);
  },
  
  
  onDeleteItem: function()
  {
    // reload the DOM
    this.MoMACurrentSet.reloadData();
    // update the positions
    this.updatePositions();
    // let everyone else know
    this.fireEvent('viewChanged', this);
  },
  
  
  animationFor: function(data)
  {
    if(data.action == "hide")
    {
      return this.hideAnimation(data);
    }
    else if(data.action == 'remove')
    {
      return this.removeAnimation(data);
    }
  },
  
  
  removeAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    var cellSize = cell.getSize();
    
    cell.set('tween', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeOut
    });
    
    cell.setStyles({
      width: cellSize.x,
      height: cellSize.y,
      opacity: 0.5,
      backgroundColor: 'black',
      backgroundImage: 'none'
    });

    cell.addClass('SSHideChildren');

    return function() {
      return {
        animation: function() {
          return cell.get('tween').start('opacity', 0.3).chain(function() {
            cell.get('tween').start('height', 0);
          });
        },
        cleanup: function() {
          this.onDeleteItem();
        }.bind(this)
      };
    }.bind(this)
  },
  
  
  hideAnimation: function(data)
  {
    var cell = data.listView.cellNodeForIndex(data.index);
    
    cell.set('morph', {
      duration: 300,
      transition: Fx.Transitions.Cubic.easeIn
    });
    
    return function() {
      return {
        animation: function() {
          return cell.get('morph').start({
            marginLeft: -20,
            opacity: 0.0
          }).chain(function() {
            cell.get('morph').start({height:0});
          });
        }.bind(this),
        cleanup: function() {
          this.MoMABookmarkedWorks.refresh(true);
          this.setBlocked(false);
        }.bind(this)
      };
    }.bind(this);
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAEditSetView = MoMAEditSetView;


// End ../momasocialbar/views/MoMAEditSetView/MoMAEditSetView.js --------------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAEditSetView');

if (SSInclude != undefined) SSLog('Including ../momasocialbar/views/MoMAEditSetView/MoMAChooseWorkCell.js...', SSInclude);

// Start ../momasocialbar/views/MoMAEditSetView/MoMAChooseWorkCell.js ---------

// ==Builder==
// @uiclass
// @optional
// @package           MoMASocialBarUI
// @dependencies      SSCell
// ==/Builder==

var MoMAChooseWorkCell = new Class({

  Extends: SSCell,

  name: "MoMAChooseWorkCell",
  

  setTitle: function(title)
  {
    var el = this.lockedElement();
    el.getElement('.title').set('html', title);
  },
  
  
  getTitle: function()
  {
    return this.__title;
  },
  

  setArtist: function(artist)
  {
    var el = this.lockedElement();
    el.getElement('.artist').set('html', artist);
  },
  
  
  getArtist: function()
  {
    
  },
  
  
  setDatetext: function(datetext)
  {
    var el = this.lockedElement();
    el.getElement('.datetext').set('text', datetext);
  },
  
  
  getDatetext: function()
  {
    
  }
});
if(typeof ShiftSpaceUI != 'undefined') ShiftSpaceUI.MoMAChooseWorkCell = MoMAChooseWorkCell;


// End ../momasocialbar/views/MoMAEditSetView/MoMAChooseWorkCell.js -----------


if (SSInclude != undefined) SSLog('... complete.', SSInclude);

if(__sysavail__) __sysavail__.files.push('MoMAChooseWorkCell');

// === END PACKAGE [MoMASocialBarUI] ===

window.addEvent('domready', InitMoMASocialBar);

var Console;
function InitMoMASocialBar()
{
  if(Browser.Engine.trident && (Browser.Engine.version < 5))
  {
    return;
  }
  Console = new MoMAConsole();
}