<commit_before>/** File: core.js
 * Candy - Chats are not dead yet.
 *
 * Authors:
 *   - Patrick Stadler <patrick.stadler@gmail.com>
 *   - Michael Weibel <michael.weibel@gmail.com>
 *
 * Copyright:
 *   (c) 2011 Amiado Group AG. All rights reserved.
 *   (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
 */
'use strict';

/* global Candy, window, Strophe, jQuery */

/** Class: Candy.Core
 * Candy Chat Core
 *
 * Parameters:
 *   (Candy.Core) self - itself
 *   (Strophe) Strophe - Strophe JS
 *   (jQuery) $ - jQuery
 */
Candy.Core = (function(self, Strophe, $) {
		/** PrivateVariable: _connection
		 * Strophe connection
		 */
	var _connection = null,
		/** PrivateVariable: _service
		 * URL of BOSH service
		 */
		_service = null,
		/** PrivateVariable: _user
		 * Current user (me)
		 */
		_user = null,
		/** PrivateVariable: _rooms
		 * Opened rooms, containing instances of Candy.Core.ChatRooms
		 */
		_rooms = {},
		/** PrivateVariable: _anonymousConnection
		 * Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
		 */
		_anonymousConnection = false,
		/** PrivateVariable: _status
		 * Current Strophe connection state
		 */
		_status,
		/** PrivateVariable: _options
		 * Options:
		 *   (Boolean) debug - Debug (Default: false)
		 *   (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
		 */
		_options = {
			/** Boolean: autojoin
			 * If set to `true` try to get the bookmarks and autojoin the rooms (supported by ejabberd, Openfire).
			 * You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
			 */
			autojoin: undefined,
			debug: false,
			disableWindowUnload: false,
			/** Integer: presencePriority
			 * Default priority for presence messages in order to receive messages across different resources
			 */
			presencePriority: 1,
			/** String: resource
			 * JID resource to use when connecting to the server.
			 * Specify `''` (an empty string) to request a random resource.
			 */
			resource: Candy.about.name
		},

		/** PrivateFunction: _addNamespace
		 * Adds a namespace.
		 *
		 * Parameters:
		 *   (String) name - namespace name (will become a constant living in Strophe.NS.*)
		 *   (String) value - XML Namespace
		 */
		_addNamespace = function(name, value) {
			Strophe.addNamespace(name, value);
		},

		/** PrivateFunction: _addNamespaces
		 * Adds namespaces needed by Candy.
		 */
		_addNamespaces = function() {
			_addNamespace('PRIVATE', 'jabber:iq:private');
			_addNamespace('BOOKMARKS', 'storage:bookmarks');
			_addNamespace('PRIVACY', 'jabber:iq:privacy');
			_addNamespace('DELAY', 'jabber:x:delay');
			_addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
		},

		_getEscapedJidFromJid = function(jid) {
			var node = Strophe.getNodeFromJid(jid),
				domain = Strophe.getDomainFromJid(jid);
			return node ? Strophe.escapeNode(node) + '@' + domain : domain;
		};

	/** Function: init
	 * Initialize Core.
	 *
	 * Parameters:
	 *   (String) service - URL of BOSH/Websocket service
	 *   (Object) options - Options for candy
	 */
	self.init = function(service, options) {
		_service = service;
		// Apply options
		$.extend(true, _options, options);

		// Enable debug logging
		if(_options.debug) {
			if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
				// Strophe has a polyfill for bind which doesn't work in IE8.
				if(Function.prototype.bind && Candy.Util.getIeVersion() > 8) {
					self.log = Function.prototype.bind.call(console.log, console);
				} else {
					self.log = function() {
						Function.prototype.apply.call(console.log, console, arguments);
					};
				}
			}
			self.log('[Init] Debugging enabled');
		}

		_addNamespaces();

		// Connect to BOSH/Websocket service
		_connection = new Strophe.Connection(_service);
		_connection.rawInput = self.rawInput.bind(self);
		_connection.rawOutput = self.rawOutput.bind(self);

		// set caps node
		_connection.caps.node = 'https://candy-chat.github.io/candy/';

		// Window unload handler... works on all browsers but Opera. There is NO workaround.
		// Opera clients getting disconnected 1-2 minutes delayed.
		if (!_options.disableWindowUnload) {
			window.onbeforeunload = self.onWindowUnload;
		}
	};

	/** Function: registerEventHandlers
	 * Adds listening handlers to the connection.
	 *
	 * Use with caution from outside of Candy.
	 */
	self.registerEventHandlers = function() {
		self.addHandler(self.Event.Jabber.Version, Strophe.NS.VERSION, 'iq');
		self.addHandler(self.Event.Jabber.Presence, null, 'presence');
		self.addHandler(self.Event.Jabber.Message, null, 'message');
		self.addHandler(self.Event.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
		self.addHandler(self.Event.Jabber.Room.Disco, Strophe.NS.DISCO_INFO, 'iq', 'result');

		self.addHandler(_connection.disco._onDiscoInfo.bind(_connection.disco), Strophe.NS.DISCO_INFO, 'iq', 'get');
		self.addHandler(_connection.disco._onDiscoItems.bind(_connection.disco), Strophe.NS.DISCO_ITEMS, 'iq', 'get');
		self.addHandler(_connection.caps._delegateCapabilities.bind(_connection.caps), Strophe.NS.CAPS);
	};

	/** Function: connect
	 * Connect to the jabber host.
	 *
	 * There are four different procedures to login:
	 *   connect('JID', 'password') - Connect a registered user
	 *   connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
	 *   connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
	 *   connect('JID') - Show login form and prompt for password. JID input is hidden.
	 *   connect() - Show login form and prompt for JID and password.
	 *
	 * See:
	 *   <Candy.Core.attach()> for attaching an already established session.
	 *
	 * Parameters:
	 *   (String) jidOrHost - JID or Host
	 *   (String) password  - Password of the user
	 *   (String) nick      - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
	 *                        and this param is not set, Candy will prompt for a nick.
	 */
	self.connect = function(jidOrHost, password, nick) {
		// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
		_connection.reset();
		self.registerEventHandlers();
		/** Event: candy:core.before-connect
		 * Triggered before a connection attempt is made.
		 *
		 * Plugins should register their stanza handlers using this event
		 * to ensure that they are set.
		 *
		 * See also <#84 at https://github.com/candy-chat/candy/issues/84>.
		 *
		 * Parameters:
		 *   (Strophe.Connection) conncetion - Strophe connection
		 */
		$(Candy).triggerHandler('candy:core.before-connect', {
			connection: _connection
		});

		_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;

		if(jidOrHost && password) {
			// authentication
			_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, password, Candy.Core.Event.Strophe.Connect);
			if (nick) {
				_user = new self.ChatUser(jidOrHost, nick);
			} else {
				_user = new self.ChatUser(jidOrHost, Strophe.getNodeFromJid(jidOrHost));
			}
		} else if(jidOrHost && nick) {
			// anonymous connect
			_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, null, Candy.Core.Event.Strophe.Connect);
			_user = new self.ChatUser(null, nick); // set jid to null because we'll later receive it
		} else if(jidOrHost) {
			Candy.Core.Event.Login(jidOrHost);
		} else {
			// display login modal
			Candy.Core.Event.Login();
		}
	};

	/** Function: attach
	 * Attach an already binded & connected session to the server
	 *
	 * _See_ Strophe.Connection.attach
	 *
	 * Parameters:
	 *   (String) jid - Jabber ID
	 *   (Integer) sid - Session ID
	 *   (Integer) rid - rid
	 */
	self.attach = function(jid, sid, rid) {
		_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
		self.registerEventHandlers();
		_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
	};

	/** Function: disconnect
	 * Leave all rooms and disconnect
	 */
	self.disconnect = function() {
		if(_connection.connected) {
			$.each(self.getRooms(), function() {
				Candy.Core.Action.Jabber.Room.Leave(this.getJid());
			});
			_connection.disconnect();
		}
	};

	/** Function: addHandler
	 * Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
	 *
	 * Parameters:
	 *   (Function) handler - The user callback.
	 *   (String) ns - The namespace to match.
	 *   (String) name - The stanza name to match.
	 *   (String) type - The stanza type attribute to match.
	 *   (String) id - The stanza id attribute to match.
	 *   (String) from - The stanza from attribute to match.
	 *   (String) options - The handler options
	 *
	 * Returns:
	 *   A reference to the handler that can be used to remove it.
	 */
	self.addHandler = function(handler, ns, name, type, id, from, options) {
		return _connection.addHandler(handler, ns, name, type, id, from, options);
	};

	/** Function: getUser
	 * Gets current user
	 *
	 * Returns:
	 *   Instance of Candy.Core.ChatUser
	 */
	self.getUser = function() {
		return _user;
	};

	/** Function: setUser
	 * Set current user. Needed when anonymous login is used, as jid gets retrieved later.
	 *
	 * Parameters:
	 *   (Candy.Core.ChatUser) user - User instance
	 */
	self.setUser = function(user) {
		_user = user;
	};

	/** Function: getConnection
	 * Gets Strophe connection
	 *
	 * Returns:
	 *   Instance of Strophe.Connection
	 */
	self.getConnection = function() {
		return _connection;
	};

	/** Function: removeRoom
	 * Removes a room from the rooms list
	 *
	 * Parameters:
	 *   (String) roomJid - roomJid
	 */
	self.removeRoom = function(roomJid) {
		delete _rooms[roomJid];
	};

	/** Function: getRooms
	 * Gets all joined rooms
	 *
	 * Returns:
	 *   Object containing instances of Candy.Core.ChatRoom
	 */
	self.getRooms = function() {
		return _rooms;
	};

	/** Function: getStropheStatus
	 * Get the status set by Strophe.
	 *
	 * Returns:
	 *   (Strophe.Status.*) - one of Strophe's statuses
	 */
	self.getStropheStatus = function() {
		return _status;
	};

	/** Function: setStropheStatus
	 * Set the strophe status
	 *
	 * Called by:
	 *   Candy.Core.Event.Strophe.Connect
	 *
	 * Parameters:
	 *   (Strophe.Status.*) status - Strophe's status
	 */
	self.setStropheStatus = function(status) {
		_status = status;
	};

	/** Function: isAnonymousConnection
	 * Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
	 *
	 * Returns:
	 *   (Boolean)
	 */
	self.isAnonymousConnection = function() {
		return _anonymousConnection;
	};

	/** Function: getOptions
	 * Gets options
	 *
	 * Returns:
	 *   Object
	 */
	self.getOptions = function() {
		return _options;
	};

    /** Function: getRoom
	 * Gets a specific room
	 *
	 * Parameters:
	 *   (String) roomJid - JID of the room
	 *
	 * Returns:
	 *   If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
	 */
	self.getRoom = function(roomJid) {
		if (_rooms[roomJid]) {
			return _rooms[roomJid];
		}
		return null;
	};

	/** Function: onWindowUnload
	 * window.onbeforeunload event which disconnects the client from the Jabber server.
	 */
	self.onWindowUnload = function() {
		// Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
		// Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
		_connection.options.sync = true;
		self.disconnect();
		_connection.flush();
	};

	/** Function: rawInput
	 * (Overridden from Strophe.Connection.rawInput)
	 *
	 * Logs all raw input if debug is set to true.
	 */
	self.rawInput = function(data) {
		this.log('RECV: ' + data);
	};

	/** Function rawOutput
	 * (Overridden from Strophe.Connection.rawOutput)
	 *
	 * Logs all raw output if debug is set to true.
	 */
	self.rawOutput = function(data) {
		this.log('SENT: ' + data);
	};

	/** Function: log
	 * Overridden to do something useful if debug is set to true.
	 *
	 * See: Candy.Core#init
	 */
	self.log = function() {};

	return self;
}(Candy.Core || {}, Strophe, jQuery));
<commit_msg>Log strophe messages to console when debug mode is on<commit_after>/** File: core.js
 * Candy - Chats are not dead yet.
 *
 * Authors:
 *   - Patrick Stadler <patrick.stadler@gmail.com>
 *   - Michael Weibel <michael.weibel@gmail.com>
 *
 * Copyright:
 *   (c) 2011 Amiado Group AG. All rights reserved.
 *   (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
 */
'use strict';

/* global Candy, window, Strophe, jQuery */

/** Class: Candy.Core
 * Candy Chat Core
 *
 * Parameters:
 *   (Candy.Core) self - itself
 *   (Strophe) Strophe - Strophe JS
 *   (jQuery) $ - jQuery
 */
Candy.Core = (function(self, Strophe, $) {
		/** PrivateVariable: _connection
		 * Strophe connection
		 */
	var _connection = null,
		/** PrivateVariable: _service
		 * URL of BOSH service
		 */
		_service = null,
		/** PrivateVariable: _user
		 * Current user (me)
		 */
		_user = null,
		/** PrivateVariable: _rooms
		 * Opened rooms, containing instances of Candy.Core.ChatRooms
		 */
		_rooms = {},
		/** PrivateVariable: _anonymousConnection
		 * Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
		 */
		_anonymousConnection = false,
		/** PrivateVariable: _status
		 * Current Strophe connection state
		 */
		_status,
		/** PrivateVariable: _options
		 * Options:
		 *   (Boolean) debug - Debug (Default: false)
		 *   (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
		 */
		_options = {
			/** Boolean: autojoin
			 * If set to `true` try to get the bookmarks and autojoin the rooms (supported by ejabberd, Openfire).
			 * You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
			 */
			autojoin: undefined,
			debug: false,
			disableWindowUnload: false,
			/** Integer: presencePriority
			 * Default priority for presence messages in order to receive messages across different resources
			 */
			presencePriority: 1,
			/** String: resource
			 * JID resource to use when connecting to the server.
			 * Specify `''` (an empty string) to request a random resource.
			 */
			resource: Candy.about.name
		},

		/** PrivateFunction: _addNamespace
		 * Adds a namespace.
		 *
		 * Parameters:
		 *   (String) name - namespace name (will become a constant living in Strophe.NS.*)
		 *   (String) value - XML Namespace
		 */
		_addNamespace = function(name, value) {
			Strophe.addNamespace(name, value);
		},

		/** PrivateFunction: _addNamespaces
		 * Adds namespaces needed by Candy.
		 */
		_addNamespaces = function() {
			_addNamespace('PRIVATE', 'jabber:iq:private');
			_addNamespace('BOOKMARKS', 'storage:bookmarks');
			_addNamespace('PRIVACY', 'jabber:iq:privacy');
			_addNamespace('DELAY', 'jabber:x:delay');
			_addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
		},

		_getEscapedJidFromJid = function(jid) {
			var node = Strophe.getNodeFromJid(jid),
				domain = Strophe.getDomainFromJid(jid);
			return node ? Strophe.escapeNode(node) + '@' + domain : domain;
		};

	/** Function: init
	 * Initialize Core.
	 *
	 * Parameters:
	 *   (String) service - URL of BOSH/Websocket service
	 *   (Object) options - Options for candy
	 */
	self.init = function(service, options) {
		_service = service;
		// Apply options
		$.extend(true, _options, options);

		// Enable debug logging
		if(_options.debug) {
			if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
				// Strophe has a polyfill for bind which doesn't work in IE8.
				if(Function.prototype.bind && Candy.Util.getIeVersion() > 8) {
					self.log = Function.prototype.bind.call(console.log, console);
				} else {
					self.log = function() {
						Function.prototype.apply.call(console.log, console, arguments);
					};
				}
			}
			Strophe.log = function (level, message) {
				var level_name, console_level;
				switch (level) {
					case Strophe.LogLevel.DEBUG:
						level_name = 'DEBUG';
						console_level = 'log';
						break;
					case Strophe.LogLevel.INFO:
						level_name = 'INFO';
						console_level = 'info';
						break;
					case Strophe.LogLevel.WARN:
						level_name = 'WARN';
						console_level = 'info';
						break;
					case Strophe.LogLevel.ERROR:
						level_name = 'ERROR';
						console_level = 'error';
						break;
					case Strophe.LogLevel.FATAL:
						level_name = 'FATAL';
						console_level = 'error';
						break;
				}
				console[console_level]('[Strophe][' + level_name + ']: ' + message);
			};
			self.log('[Init] Debugging enabled');
		}

		_addNamespaces();

		// Connect to BOSH/Websocket service
		_connection = new Strophe.Connection(_service);
		_connection.rawInput = self.rawInput.bind(self);
		_connection.rawOutput = self.rawOutput.bind(self);

		// set caps node
		_connection.caps.node = 'https://candy-chat.github.io/candy/';

		// Window unload handler... works on all browsers but Opera. There is NO workaround.
		// Opera clients getting disconnected 1-2 minutes delayed.
		if (!_options.disableWindowUnload) {
			window.onbeforeunload = self.onWindowUnload;
		}
	};

	/** Function: registerEventHandlers
	 * Adds listening handlers to the connection.
	 *
	 * Use with caution from outside of Candy.
	 */
	self.registerEventHandlers = function() {
		self.addHandler(self.Event.Jabber.Version, Strophe.NS.VERSION, 'iq');
		self.addHandler(self.Event.Jabber.Presence, null, 'presence');
		self.addHandler(self.Event.Jabber.Message, null, 'message');
		self.addHandler(self.Event.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
		self.addHandler(self.Event.Jabber.Room.Disco, Strophe.NS.DISCO_INFO, 'iq', 'result');

		self.addHandler(_connection.disco._onDiscoInfo.bind(_connection.disco), Strophe.NS.DISCO_INFO, 'iq', 'get');
		self.addHandler(_connection.disco._onDiscoItems.bind(_connection.disco), Strophe.NS.DISCO_ITEMS, 'iq', 'get');
		self.addHandler(_connection.caps._delegateCapabilities.bind(_connection.caps), Strophe.NS.CAPS);
	};

	/** Function: connect
	 * Connect to the jabber host.
	 *
	 * There are four different procedures to login:
	 *   connect('JID', 'password') - Connect a registered user
	 *   connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
	 *   connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
	 *   connect('JID') - Show login form and prompt for password. JID input is hidden.
	 *   connect() - Show login form and prompt for JID and password.
	 *
	 * See:
	 *   <Candy.Core.attach()> for attaching an already established session.
	 *
	 * Parameters:
	 *   (String) jidOrHost - JID or Host
	 *   (String) password  - Password of the user
	 *   (String) nick      - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
	 *                        and this param is not set, Candy will prompt for a nick.
	 */
	self.connect = function(jidOrHost, password, nick) {
		// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
		_connection.reset();
		self.registerEventHandlers();
		/** Event: candy:core.before-connect
		 * Triggered before a connection attempt is made.
		 *
		 * Plugins should register their stanza handlers using this event
		 * to ensure that they are set.
		 *
		 * See also <#84 at https://github.com/candy-chat/candy/issues/84>.
		 *
		 * Parameters:
		 *   (Strophe.Connection) conncetion - Strophe connection
		 */
		$(Candy).triggerHandler('candy:core.before-connect', {
			connection: _connection
		});

		_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;

		if(jidOrHost && password) {
			// authentication
			_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, password, Candy.Core.Event.Strophe.Connect);
			if (nick) {
				_user = new self.ChatUser(jidOrHost, nick);
			} else {
				_user = new self.ChatUser(jidOrHost, Strophe.getNodeFromJid(jidOrHost));
			}
		} else if(jidOrHost && nick) {
			// anonymous connect
			_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, null, Candy.Core.Event.Strophe.Connect);
			_user = new self.ChatUser(null, nick); // set jid to null because we'll later receive it
		} else if(jidOrHost) {
			Candy.Core.Event.Login(jidOrHost);
		} else {
			// display login modal
			Candy.Core.Event.Login();
		}
	};

	/** Function: attach
	 * Attach an already binded & connected session to the server
	 *
	 * _See_ Strophe.Connection.attach
	 *
	 * Parameters:
	 *   (String) jid - Jabber ID
	 *   (Integer) sid - Session ID
	 *   (Integer) rid - rid
	 */
	self.attach = function(jid, sid, rid) {
		_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
		self.registerEventHandlers();
		_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
	};

	/** Function: disconnect
	 * Leave all rooms and disconnect
	 */
	self.disconnect = function() {
		if(_connection.connected) {
			$.each(self.getRooms(), function() {
				Candy.Core.Action.Jabber.Room.Leave(this.getJid());
			});
			_connection.disconnect();
		}
	};

	/** Function: addHandler
	 * Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
	 *
	 * Parameters:
	 *   (Function) handler - The user callback.
	 *   (String) ns - The namespace to match.
	 *   (String) name - The stanza name to match.
	 *   (String) type - The stanza type attribute to match.
	 *   (String) id - The stanza id attribute to match.
	 *   (String) from - The stanza from attribute to match.
	 *   (String) options - The handler options
	 *
	 * Returns:
	 *   A reference to the handler that can be used to remove it.
	 */
	self.addHandler = function(handler, ns, name, type, id, from, options) {
		return _connection.addHandler(handler, ns, name, type, id, from, options);
	};

	/** Function: getUser
	 * Gets current user
	 *
	 * Returns:
	 *   Instance of Candy.Core.ChatUser
	 */
	self.getUser = function() {
		return _user;
	};

	/** Function: setUser
	 * Set current user. Needed when anonymous login is used, as jid gets retrieved later.
	 *
	 * Parameters:
	 *   (Candy.Core.ChatUser) user - User instance
	 */
	self.setUser = function(user) {
		_user = user;
	};

	/** Function: getConnection
	 * Gets Strophe connection
	 *
	 * Returns:
	 *   Instance of Strophe.Connection
	 */
	self.getConnection = function() {
		return _connection;
	};

	/** Function: removeRoom
	 * Removes a room from the rooms list
	 *
	 * Parameters:
	 *   (String) roomJid - roomJid
	 */
	self.removeRoom = function(roomJid) {
		delete _rooms[roomJid];
	};

	/** Function: getRooms
	 * Gets all joined rooms
	 *
	 * Returns:
	 *   Object containing instances of Candy.Core.ChatRoom
	 */
	self.getRooms = function() {
		return _rooms;
	};

	/** Function: getStropheStatus
	 * Get the status set by Strophe.
	 *
	 * Returns:
	 *   (Strophe.Status.*) - one of Strophe's statuses
	 */
	self.getStropheStatus = function() {
		return _status;
	};

	/** Function: setStropheStatus
	 * Set the strophe status
	 *
	 * Called by:
	 *   Candy.Core.Event.Strophe.Connect
	 *
	 * Parameters:
	 *   (Strophe.Status.*) status - Strophe's status
	 */
	self.setStropheStatus = function(status) {
		_status = status;
	};

	/** Function: isAnonymousConnection
	 * Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
	 *
	 * Returns:
	 *   (Boolean)
	 */
	self.isAnonymousConnection = function() {
		return _anonymousConnection;
	};

	/** Function: getOptions
	 * Gets options
	 *
	 * Returns:
	 *   Object
	 */
	self.getOptions = function() {
		return _options;
	};

    /** Function: getRoom
	 * Gets a specific room
	 *
	 * Parameters:
	 *   (String) roomJid - JID of the room
	 *
	 * Returns:
	 *   If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
	 */
	self.getRoom = function(roomJid) {
		if (_rooms[roomJid]) {
			return _rooms[roomJid];
		}
		return null;
	};

	/** Function: onWindowUnload
	 * window.onbeforeunload event which disconnects the client from the Jabber server.
	 */
	self.onWindowUnload = function() {
		// Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
		// Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
		_connection.options.sync = true;
		self.disconnect();
		_connection.flush();
	};

	/** Function: rawInput
	 * (Overridden from Strophe.Connection.rawInput)
	 *
	 * Logs all raw input if debug is set to true.
	 */
	self.rawInput = function(data) {
		this.log('RECV: ' + data);
	};

	/** Function rawOutput
	 * (Overridden from Strophe.Connection.rawOutput)
	 *
	 * Logs all raw output if debug is set to true.
	 */
	self.rawOutput = function(data) {
		this.log('SENT: ' + data);
	};

	/** Function: log
	 * Overridden to do something useful if debug is set to true.
	 *
	 * See: Candy.Core#init
	 */
	self.log = function() {};

	return self;
}(Candy.Core || {}, Strophe, jQuery));
<|endoftext|>


<commit_before>import {BottomBar} from 'widget/sectionLayout'
import {Button, ButtonGroup} from 'widget/button'
import {Field, form} from 'widget/form'
import ComboBox from 'widget/comboBox'
import Icon from 'widget/icon'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
import styles from './landCoverBottomBar.module.css'

const fields = {
    primitive: new Field()
}

class LandCoverBottomBar extends Component {
    render() {
        const hasNext = this.hasNext()
        return <BottomBar padding className={styles.container}>

                {this.renderBackButton()}

                {this.renderPrimitiveSelector()}
                {hasNext ? this.renderNextPrimitiveButton() : null}

            <ButtonGroup type='horizontal-nowrap'>
                {this.renderCreatePrimitiveButton()}
                {this.renderNextButton()}
            </ButtonGroup>
        </BottomBar>
    }

    renderBackButton() {
        return (
            <Button
                additionalClassName={styles.back}
                look={'transparent'}
                onClick={() => console.log('Going back to composites')}>
                <Icon name='backward'/>
                Composite
            </Button>
        )
    }

    renderPrimitiveSelector() {
        const {inputs: {primitive}} = this.props
        const primitives = [
            {value: 'forest', label: 'Forest'},
            {value: 'plantation', label: 'Plantation'},
            {value: 'shrub', label: 'Shrub'},
            {value: 'grass', label: 'Grass'},
            {value: 'crop', label: 'Crop'},
            {value: 'paramo', label: 'Paramo'},
            {value: 'water', label: 'Water'},
            {value: 'urban', label: 'Urban'},
            {value: 'barren', label: 'Barren'}
        ]
        return (

                <ComboBox
                    input={primitive}
                    options={primitives}
                    menuPlacement='top'/>

        )
    }

    renderNextPrimitiveButton() {
        return (
            <Button
                icon='angle-double-right'
                tooltip={'Select next primitive'}
                chromeless
                onClick={() => console.log('Next primitive')}
                additionalClassName={styles.nextPrimitive}/>
        )
    }

    renderCreatePrimitiveButton() {
        return (
            <Button
                look={'transparent'}
                onClick={() => console.log('Starting to create primitive')}
                label='Create primitive'/>
        )
    }

    renderNextButton() {
        return (
            <Button
                disabled={!this.allPrimitivesCreated()}
                additionalClassName={styles.next}
                look={'transparent'}
                onClick={() => console.log('Navigate to assemblage')}>
                Assemblage
                <Icon name='forward'/>
            </Button>
        )
    }

    hasNext() {
        return true
        // return false
    }

    allPrimitivesCreated() {
        return true
        // return false
    }
}

LandCoverBottomBar.propTypes = {
    recipeId: PropTypes.string.isRequired
}

export default form({fields})(LandCoverBottomBar)
<commit_msg>Cleanup.<commit_after>import {BottomBar} from 'widget/sectionLayout'
import {Button, ButtonGroup} from 'widget/button'
import {Field, form} from 'widget/form'
import ComboBox from 'widget/comboBox'
import Icon from 'widget/icon'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
import styles from './landCoverBottomBar.module.css'

const fields = {
    primitive: new Field()
}

class LandCoverBottomBar extends Component {
    render() {
        const hasNext = this.hasNext()
        return <BottomBar padding className={styles.container}>

                {this.renderBackButton()}

                {this.renderPrimitiveSelector()}
                {hasNext ? this.renderNextPrimitiveButton() : null}

            <ButtonGroup type='horizontal-nowrap'>
                {this.renderCreatePrimitiveButton()}
                {this.renderNextButton()}
            </ButtonGroup>
        </BottomBar>
    }

    renderBackButton() {
        return (
            <Button
                additionalClassName={styles.back}
                look={'transparent'}
                // onClick={() => console.log('Going back to composites')}
            >
                <Icon name='backward'/>
                Composite
            </Button>
        )
    }

    renderPrimitiveSelector() {
        const {inputs: {primitive}} = this.props
        const primitives = [
            {value: 'forest', label: 'Forest'},
            {value: 'plantation', label: 'Plantation'},
            {value: 'shrub', label: 'Shrub'},
            {value: 'grass', label: 'Grass'},
            {value: 'crop', label: 'Crop'},
            {value: 'paramo', label: 'Paramo'},
            {value: 'water', label: 'Water'},
            {value: 'urban', label: 'Urban'},
            {value: 'barren', label: 'Barren'}
        ]
        return (

                <ComboBox
                    input={primitive}
                    options={primitives}
                    menuPlacement='top'/>

        )
    }

    renderNextPrimitiveButton() {
        return (
            <Button
                icon='angle-double-right'
                tooltip={'Select next primitive'}
                chromeless
                // onClick={() => console.log('Next primitive')}
                additionalClassName={styles.nextPrimitive}/>
        )
    }

    renderCreatePrimitiveButton() {
        return (
            <Button
                look={'transparent'}
                // onClick={() => console.log('Starting to create primitive')}
                label='Create primitive'/>
        )
    }

    renderNextButton() {
        return (
            <Button
                disabled={!this.allPrimitivesCreated()}
                additionalClassName={styles.next}
                look={'transparent'}
                // onClick={() => console.log('Navigate to assemblage')}
            >
                Assemblage
                <Icon name='forward'/>
            </Button>
        )
    }

    hasNext() {
        return true
        // return false
    }

    allPrimitivesCreated() {
        return true
        // return false
    }
}

LandCoverBottomBar.propTypes = {
    recipeId: PropTypes.string.isRequired
}

export default form({fields})(LandCoverBottomBar)
<|endoftext|>


<commit_before>var data_user, data_priv,
	rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
	rmultiDash = /([A-Z])/g;

function Data() {
	// Nodes|Objects
	this.owners = [];
	// Data objects
	this.cache = [];
}

Data.index = function( array, node ) {
	return array.indexOf( node );
};

Data.prototype = {
	add: function( owner ) {
		this.owners.push( owner );
		return (this.cache[ this.owners.length - 1 ] = {});
	},
	set: function( owner, data, value ) {
		var prop,
				index = Data.index( this.owners, owner );

		// If there is no entry for this "owner", create one inline
		// and set the index as though an owner entry had always existed
		if ( index === -1 ) {
			this.add( owner );
			index = this.owners.length - 1;
		}
		// Handle: [ owner, key, value ] args
		if ( typeof data === "string" ) {
			this.cache[ index ][ data ] = value;

		// Handle: [ owner, { properties } ] args
		} else {
			// In the case where there was actually no "owner" entry and
			// this.add( owner ) was called to create one, there will be
			// a corresponding empty plain object in the cache.
			if ( jQuery.isEmptyObject( this.cache[ index ] ) ) {
				this.cache[ index ] = data;

			// Otherwise, copy the properties one-by-one to the cache object
			} else {
				for ( prop in data ) {
					this.cache[ index ][ prop ] = data[ prop ];
				}
			}
		}
		return this;
	},
	get: function( owner, key ) {
		var cache,
				index = Data.index( this.owners, owner );

		// A valid cache is found, or needs to be created.
		// New entries will be added and return the current
		// empty data object to be used as a return reference
		// return this.add( owner );
		// This logic was required by expectations made of the
		// old data system.
		cache = index === -1 ?
			this.add( owner ) : this.cache[ index ];

		return key === undefined ?
			cache : cache[ key ];
	},
	access: function( owner, key, value ) {
		if ( value === undefined && (key && typeof key !== "object") ) {
			// Assume this is a request to read the cached data
			return this.get( owner, key );
		} else {

			// If only an owner was specified, return the entire
			// cache object.
			if ( key === undefined ) {
				return this.get( owner );
			}

			// Allow setting or extending (existing objects) with an
			// object of properties, or a key and val
			this.set( owner, key, value );
			return value !== undefined ? value : key;
		}
		// Otherwise, this is a read request.
		return this.get( owner, key );
	},
	remove: function( owner, key ) {
		var i, l, name,
				camel = jQuery.camelCase,
				index = Data.index( this.owners, owner ),
				cache = this.cache[ index ];

		if ( key === undefined ) {
			cache = {};
		} else {
			if ( cache ) {
				// Support array or space separated string of keys
				if ( !Array.isArray( key ) ) {
					// Try the string as a key before any manipulation
					//

					if ( key in cache ) {
						name = [ key ];
					} else {
						// Split the camel cased version by spaces unless a key with the spaces exists
						name = camel( key );
						name = name in cache ?
							[ name ] : name.split(" ");
					}
				} else {
					// If "name" is an array of keys...
					// When data is initially created, via ("key", "val") signature,
					// keys will be converted to camelCase.
					// Since there is no way to tell _how_ a key was added, remove
					// both plain key and camelCase key. #12786
					// This will only penalize the array argument path.
					name = key.concat( key.map( camel ) );
				}
				i = 0;
				l = name.length;

				for ( ; i < l; i++ ) {
					delete cache[ name[i] ];
				}
			}
		}
		this.cache[ index ] = cache;
	},
	hasData: function( owner ) {
		var index = Data.index( this.owners, owner );

		if ( index > -1 ) {
			return !jQuery.isEmptyObject( this.cache[ index ] );
		}
		return false;
	},
	discard: function( owner ) {
		var index = Data.index( this.owners, owner );

		if ( index >= 0 ) {
			this.owners.splice( index, 1 );
			this.cache.splice( index, 1 );
		}
		return this;
	}
};

// This will be used by remove()/cleanData() in manipulation to sever
// remaining references to node objects. One day we'll replace the dual
// arrays with a WeakMap and this won't be an issue.
// (Splices the data objects out of the internal cache arrays)
function data_discard( owner ) {
	data_user.discard( owner );
	data_priv.discard( owner );
}

// These may be used throughout the jQuery core codebase
data_user = new Data();
data_priv = new Data();

jQuery.extend({
	// This is no longer relevant to jQuery core, but must remain
	// supported for the sake of jQuery 1.9.x API surface compatibility.
	acceptData: function() {
		return true;
	},
	// Unique for each copy of jQuery on the page
	// Non-digits removed to match rinlinejQuery
	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),

	hasData: function( elem ) {
		return data_user.hasData( elem ) || data_priv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return data_user.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		return data_user.remove( elem, name );
	},

	// TODO: Replace all calls to _data and _removeData with direct
	// calls to
	//
	// data_priv.access( elem, name, data );
	//
	// data_priv.remove( elem, name );
	//
	_data: function( elem, name, data ) {
		return data_priv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		return data_priv.remove( elem, name );
	}
});

jQuery.fn.extend({
	data: function( key, value ) {
		var attrs, name,
			elem = this[0],
			i = 0,
			data = null;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = data_user.get( elem );

				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
					attrs = elem.attributes;
					for ( ; i < attrs.length; i++ ) {
						name = attrs[i].name;

						if ( name.indexOf( "data-" ) === 0 ) {
							name = jQuery.camelCase( name.substring(5) );
							dataAttr( elem, name, data[ name ] );
						}
					}
					data_priv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each(function() {
				data_user.set( this, key );
			});
		}

		return jQuery.access( this, function( value ) {
			var data,
					camelKey = jQuery.camelCase( key );

			// Get the Data...
			if ( value === undefined ) {

				// Attempt to get data from the cache
				// with the key as-is
				data = data_user.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, key, undefined );
				if ( data !== undefined ) {
					return data;
				}

				// As a last resort, attempt to find
				// the data by checking AGAIN, but with
				// a camelCased key.
				data = data_user.get( elem, camelKey );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return undefined;
			}

			// Set the data...
			this.each(function() {
				// First, attempt to store a copy or reference of any
				// data that might've been store with a camelCased key.
				var data = data_user.get( this, camelKey );

				// For HTML5 data-* attribute interop, we have to
				// store property names with dashes in a camelCase form.
				// This might not apply to all properties...*
				data_user.set( this, camelKey, value );

				// *... In the case of properties that might ACTUALLY
				// have dashes, we need to also store a copy of that
				// unchanged property.
				if ( /-/.test( key ) && data !== undefined ) {
					data_user.set( this, key, value );
				}
			});
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each(function() {
			data_user.remove( this, key );
		});
	}
});

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {

		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
				data === "false" ? false :
				data === "null" ? null :
				// Only convert to a number if it doesn't change the string
				+data + "" === data ? +data :
				rbrace.test( data ) ?
					JSON.parse( data ) : data;
			} catch( e ) {}

			// Make sure we set the data so it isn't changed later
			data_user.set( elem, key, data );
		} else {
			data = undefined;
		}
	}

	return data;
}
<commit_msg>Reduce Data.prototype.add by using the returned length value of this.owners.push(owner)<commit_after>var data_user, data_priv,
	rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
	rmultiDash = /([A-Z])/g;

function Data() {
	// Nodes|Objects
	this.owners = [];
	// Data objects
	this.cache = [];
}

Data.index = function( array, node ) {
	return array.indexOf( node );
};

Data.prototype = {
	add: function( owner ) {
		return (this.cache[ this.owners.push( owner ) - 1 ] = {});
	},
	set: function( owner, data, value ) {
		var prop,
				index = Data.index( this.owners, owner );

		// If there is no entry for this "owner", create one inline
		// and set the index as though an owner entry had always existed
		if ( index === -1 ) {
			this.add( owner );
			index = this.owners.length - 1;
		}
		// Handle: [ owner, key, value ] args
		if ( typeof data === "string" ) {
			this.cache[ index ][ data ] = value;

		// Handle: [ owner, { properties } ] args
		} else {
			// In the case where there was actually no "owner" entry and
			// this.add( owner ) was called to create one, there will be
			// a corresponding empty plain object in the cache.
			if ( jQuery.isEmptyObject( this.cache[ index ] ) ) {
				this.cache[ index ] = data;

			// Otherwise, copy the properties one-by-one to the cache object
			} else {
				for ( prop in data ) {
					this.cache[ index ][ prop ] = data[ prop ];
				}
			}
		}
		return this;
	},
	get: function( owner, key ) {
		var cache,
				index = Data.index( this.owners, owner );

		// A valid cache is found, or needs to be created.
		// New entries will be added and return the current
		// empty data object to be used as a return reference
		// return this.add( owner );
		// This logic was required by expectations made of the
		// old data system.
		cache = index === -1 ?
			this.add( owner ) : this.cache[ index ];

		return key === undefined ?
			cache : cache[ key ];
	},
	access: function( owner, key, value ) {
		if ( value === undefined && (key && typeof key !== "object") ) {
			// Assume this is a request to read the cached data
			return this.get( owner, key );
		} else {

			// If only an owner was specified, return the entire
			// cache object.
			if ( key === undefined ) {
				return this.get( owner );
			}

			// Allow setting or extending (existing objects) with an
			// object of properties, or a key and val
			this.set( owner, key, value );
			return value !== undefined ? value : key;
		}
		// Otherwise, this is a read request.
		return this.get( owner, key );
	},
	remove: function( owner, key ) {
		var i, l, name,
				camel = jQuery.camelCase,
				index = Data.index( this.owners, owner ),
				cache = this.cache[ index ];

		if ( key === undefined ) {
			cache = {};
		} else {
			if ( cache ) {
				// Support array or space separated string of keys
				if ( !Array.isArray( key ) ) {
					// Try the string as a key before any manipulation
					//

					if ( key in cache ) {
						name = [ key ];
					} else {
						// Split the camel cased version by spaces unless a key with the spaces exists
						name = camel( key );
						name = name in cache ?
							[ name ] : name.split(" ");
					}
				} else {
					// If "name" is an array of keys...
					// When data is initially created, via ("key", "val") signature,
					// keys will be converted to camelCase.
					// Since there is no way to tell _how_ a key was added, remove
					// both plain key and camelCase key. #12786
					// This will only penalize the array argument path.
					name = key.concat( key.map( camel ) );
				}
				i = 0;
				l = name.length;

				for ( ; i < l; i++ ) {
					delete cache[ name[i] ];
				}
			}
		}
		this.cache[ index ] = cache;
	},
	hasData: function( owner ) {
		var index = Data.index( this.owners, owner );

		if ( index > -1 ) {
			return !jQuery.isEmptyObject( this.cache[ index ] );
		}
		return false;
	},
	discard: function( owner ) {
		var index = Data.index( this.owners, owner );

		if ( index >= 0 ) {
			this.owners.splice( index, 1 );
			this.cache.splice( index, 1 );
		}
		return this;
	}
};

// This will be used by remove()/cleanData() in manipulation to sever
// remaining references to node objects. One day we'll replace the dual
// arrays with a WeakMap and this won't be an issue.
// (Splices the data objects out of the internal cache arrays)
function data_discard( owner ) {
	data_user.discard( owner );
	data_priv.discard( owner );
}

// These may be used throughout the jQuery core codebase
data_user = new Data();
data_priv = new Data();

jQuery.extend({
	// This is no longer relevant to jQuery core, but must remain
	// supported for the sake of jQuery 1.9.x API surface compatibility.
	acceptData: function() {
		return true;
	},
	// Unique for each copy of jQuery on the page
	// Non-digits removed to match rinlinejQuery
	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),

	hasData: function( elem ) {
		return data_user.hasData( elem ) || data_priv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return data_user.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		return data_user.remove( elem, name );
	},

	// TODO: Replace all calls to _data and _removeData with direct
	// calls to
	//
	// data_priv.access( elem, name, data );
	//
	// data_priv.remove( elem, name );
	//
	_data: function( elem, name, data ) {
		return data_priv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		return data_priv.remove( elem, name );
	}
});

jQuery.fn.extend({
	data: function( key, value ) {
		var attrs, name,
			elem = this[0],
			i = 0,
			data = null;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = data_user.get( elem );

				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
					attrs = elem.attributes;
					for ( ; i < attrs.length; i++ ) {
						name = attrs[i].name;

						if ( name.indexOf( "data-" ) === 0 ) {
							name = jQuery.camelCase( name.substring(5) );
							dataAttr( elem, name, data[ name ] );
						}
					}
					data_priv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each(function() {
				data_user.set( this, key );
			});
		}

		return jQuery.access( this, function( value ) {
			var data,
					camelKey = jQuery.camelCase( key );

			// Get the Data...
			if ( value === undefined ) {

				// Attempt to get data from the cache
				// with the key as-is
				data = data_user.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, key, undefined );
				if ( data !== undefined ) {
					return data;
				}

				// As a last resort, attempt to find
				// the data by checking AGAIN, but with
				// a camelCased key.
				data = data_user.get( elem, camelKey );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return undefined;
			}

			// Set the data...
			this.each(function() {
				// First, attempt to store a copy or reference of any
				// data that might've been store with a camelCased key.
				var data = data_user.get( this, camelKey );

				// For HTML5 data-* attribute interop, we have to
				// store property names with dashes in a camelCase form.
				// This might not apply to all properties...*
				data_user.set( this, camelKey, value );

				// *... In the case of properties that might ACTUALLY
				// have dashes, we need to also store a copy of that
				// unchanged property.
				if ( /-/.test( key ) && data !== undefined ) {
					data_user.set( this, key, value );
				}
			});
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each(function() {
			data_user.remove( this, key );
		});
	}
});

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {

		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
				data === "false" ? false :
				data === "null" ? null :
				// Only convert to a number if it doesn't change the string
				+data + "" === data ? +data :
				rbrace.test( data ) ?
					JSON.parse( data ) : data;
			} catch( e ) {}

			// Make sure we set the data so it isn't changed later
			data_user.set( elem, key, data );
		} else {
			data = undefined;
		}
	}

	return data;
}
<|endoftext|>


<commit_before>'use strict'

const dd=(a,b)=>`\\frac{\\mathrm{d}${a}}{\\mathrm{d}${b}}`
const dydt=dd('y','t')
const int=(fx,x)=>`\\int\\!${fx}\\,\\mathrm{d}${x}`
const eqsol=(name)=>`<a href='https://en.wikipedia.org/wiki/Equilibrium_point'>${name}</a>`

module.exports={
	root: {
		parents: {},
		name: "first-order",
		equation: `${dydt} = f(t,y)`,
		properties: [
			"can test if \\(y_p(t)\\) is a solution by substituting \\(y = y_p\\) into the equation",
		]
	},
	separable: {
		parents: {
			root: true,
		},
		name: "first-order separable",
		equation: `${dydt} = f_1(t) \\cdot f_2(y)`,
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f_1(t) \\cdot f_2(y) \\]`+
				`\\[ \\frac{1}{f_2(y)} \\cdot ${dydt} = f_1(t) \\]`+
				`\\[ ${int(`\\frac{1}{f_2(y)} \\cdot ${dydt}`,'t')} = ${int('f_1(t)','t')} \\]`+
				`\\[ ${int(`\\frac{1}{f_2(y)}`,'y')} = ${int('f_1(t)','t')} \\]`+
				`this may or may not include ${eqsol("equilibrium solutions")}`,
			""+eqsol("Equilibrium solutions")+":
"+
				`Solve \\( f_2(y) = 0 \\) for constant \\( y \\);`+"
"+
				`domain is not necessary \\( -\\infty < t < +\\infty \\)`
		],
	},
	autonomous: {
		parents: {
			separable: true,
		},
		name: "first-order autonomous",
		equation: `${dydt} = f(y)`,
		properties: [
			"horizontal isoclines",
			"\\( y_p(t )\\) is a solution \\( \\Rightarrow \\) \\( y_p(t+C) \\) is a solution",
		],
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f(y) \\]`+
				`\\[ \\frac{1}{f(y)} \\cdot ${dydt} = 1 \\]`+
				`\\[ ${int(`\\frac{1}{f(y)} \\cdot ${dydt}`,'t')} = ${int('1','t')} \\]`+
				`\\[ ${int(`\\frac{1}{f(y)}`,'y')} = t \\]`+
				`this may or may not include ${eqsol("equilibrium solutions")}`,
			""+eqsol("Equilibrium solutions")+":
"+
				`Solve \\( f(y) = 0 \\) for constant \\( y \\);`+"
"+
				`domain is \\( -\\infty < t < +\\infty \\)`
		],
	},
	linear: {
		parents: {
			root: true,
		},
		name: "first-order linear",
		equation: `${dydt} = a(t) \\cdot y + b(t)`,
		properties: [
			`<em>Extended linearity</em>:<br>`+
				`If \\( y_p(t) \\) is a solution of the original equation \\( ${dydt} = a(t) \\cdot y + b(t) \\)<br>`+
				`and \\( y_h(t) \\) is a solution of its associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\),<br>`+
				`then \\( k \\cdot y_h(t) + y_p(t) \\) is a solution of the original equation.<br>`+
				`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) + y_p(t) \\) is a general solution.`,
			`If \\( y_p(t) \\) and \\( y_q(t) \\) are solutions,<br>`+
				`then \\( y_p(t) - y_q(t) \\) is a solution of the associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\).`,
		],
	},
	linearHomogeneous: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order linear homogeneous",
		equation: `${dydt} = a(t) \\cdot y`,
		properties: [
			`<em>Linearity</em>:<br>`+
				`If \\( y_h(t) \\) is a solution, then \\( k \\cdot y_h(t) \\) is a solution.<br>`+
				`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) \\) is a general solution.`,
		],
		solutions: [
			""+eqsol("Equilibrium solution")+":"+
				`\\[ y(t) = 0 \\]` // +
				// `domain is \\( -\\infty < t < +\\infty \\)` // why?
		],
	},
	separableInT: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order separable in independent variable",
		equation: `${dydt} = f(t)`,
		properties: [
			"vertical isoclines",
			"\\( y_p(t) \\) is a solution \\( \\Rightarrow \\) \\( y_p(t) + C \\) is a solution",
		],
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f(t) \\]`+
				`\\[ ${int(`${dydt}`,'t')} = ${int('f(t)','t')} \\]`+
				`\\[ y = ${int('f(t)','t')} \\]`,
		],
	},
}
<commit_msg>added exponential growth<commit_after>'use strict'

const dd=(a,b)=>`\\frac{\\mathrm{d}${a}}{\\mathrm{d}${b}}`
const dydt=dd('y','t')
const int=(fx,x)=>`\\int\\!${fx}\\,\\mathrm{d}${x}`
const eqsol=(name)=>`<a href='https://en.wikipedia.org/wiki/Equilibrium_point'>${name}</a>`

module.exports={
	root: {
		parents: {},
		name: "first-order",
		equation: `${dydt} = f(t,y)`,
		properties: [
			"can test if \\(y_p(t)\\) is a solution by substituting \\(y = y_p\\) into the equation",
		]
	},
	separable: {
		parents: {
			root: true,
		},
		name: "first-order separable",
		equation: `${dydt} = f_1(t) \\cdot f_2(y)`,
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f_1(t) \\cdot f_2(y) \\]`+
				`\\[ \\frac{1}{f_2(y)} \\cdot ${dydt} = f_1(t) \\]`+
				`\\[ ${int(`\\frac{1}{f_2(y)} \\cdot ${dydt}`,'t')} = ${int('f_1(t)','t')} \\]`+
				`\\[ ${int(`\\frac{1}{f_2(y)}`,'y')} = ${int('f_1(t)','t')} \\]`+
				`this may or may not include ${eqsol("equilibrium solutions")}`,
			""+eqsol("Equilibrium solutions")+":
"+
				`Solve \\( f_2(y) = 0 \\) for constant \\( y \\);`+"
"+
				`domain is not necessary \\( -\\infty < t < +\\infty \\)`
		],
	},
	autonomous: {
		parents: {
			separable: true,
		},
		name: "first-order autonomous",
		equation: `${dydt} = f(y)`,
		properties: [
			"horizontal isoclines",
			"\\( y_p(t )\\) is a solution \\( \\Rightarrow \\) \\( y_p(t+C) \\) is a solution",
		],
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f(y) \\]`+
				`\\[ \\frac{1}{f(y)} \\cdot ${dydt} = 1 \\]`+
				`\\[ ${int(`\\frac{1}{f(y)} \\cdot ${dydt}`,'t')} = ${int('1','t')} \\]`+
				`\\[ ${int(`\\frac{1}{f(y)}`,'y')} = t \\]`+
				`this may or may not include ${eqsol("equilibrium solutions")}`,
			""+eqsol("Equilibrium solutions")+":
"+
				`Solve \\( f(y) = 0 \\) for constant \\( y \\);`+"
"+
				`domain is \\( -\\infty < t < +\\infty \\)`
		],
	},
	linear: {
		parents: {
			root: true,
		},
		name: "first-order linear",
		equation: `${dydt} = a(t) \\cdot y + b(t)`,
		properties: [
			`<em>Extended linearity</em>:<br>`+
				`If \\( y_p(t) \\) is a solution of the original equation \\( ${dydt} = a(t) \\cdot y + b(t) \\)<br>`+
				`and \\( y_h(t) \\) is a solution of its associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\),<br>`+
				`then \\( k \\cdot y_h(t) + y_p(t) \\) is a solution of the original equation.<br>`+
				`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) + y_p(t) \\) is a general solution.`,
			`If \\( y_p(t) \\) and \\( y_q(t) \\) are solutions,<br>`+
				`then \\( y_p(t) - y_q(t) \\) is a solution of the associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\).`,
		],
	},
	linearHomogeneous: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order linear homogeneous",
		equation: `${dydt} = a(t) \\cdot y`,
		properties: [
			`<em>Linearity</em>:<br>`+
				`If \\( y_h(t) \\) is a solution, then \\( k \\cdot y_h(t) \\) is a solution.<br>`+
				`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) \\) is a general solution.`,
		],
		solutions: [
			""+eqsol("Equilibrium solution")+":"+
				`\\[ y(t) = 0 \\]` // +
				// `domain is \\( -\\infty < t < +\\infty \\)` // why?
		],
	},
	separableInT: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order separable in independent variable",
		equation: `${dydt} = f(t)`,
		properties: [
			"vertical isoclines",
			"\\( y_p(t) \\) is a solution \\( \\Rightarrow \\) \\( y_p(t) + C \\) is a solution",
		],
		solutions: [
			`<em>Solutions</em>:`+
				`\\[ ${dydt} = f(t) \\]`+
				`\\[ ${int(`${dydt}`,'t')} = ${int('f(t)','t')} \\]`+
				`\\[ y = ${int('f(t)','t')} \\]`,
		],
	},
	expGrowth: {
		parents: {
			autonomous: true,
			linearHomogeneous: true,
		},
		name: "exponential growth",
		equation: `${dydt} = k \\cdot y`,
		solutions: [
			`<em>Solution</em>:`+
				`\\[ y(t) = y(0) e^{kt} \\]`,
		],
	},
}
<|endoftext|>


<commit_before>/*global angular */
/*
 jQuery UI Datepicker plugin wrapper

 @note If ≤ IE8 make sure you have a polyfill for Date.toISOString()
 @param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto uiDateConfig
 */

angular.module('ui.date', [])

.constant('uiDateConfig', {})

.directive('uiDate', ['uiDateConfig', function (uiDateConfig) {
  'use strict';
  var options;
  options = {};
  angular.extend(options, uiDateConfig);
  return {
    require:'?ngModel',
    link:function (scope, element, attrs, controller) {
      var getOptions = function () {
        return angular.extend({}, uiDateConfig, scope.$eval(attrs.uiDate));
      };
      var initDateWidget = function () {
        var showing = false;
        var opts = getOptions();

        // If we have a controller (i.e. ngModelController) then wire it up
        if (controller) {

          // Set the view value in a $apply block when users selects
          // (calling directive user's function too if provided)
          var _onSelect = opts.onSelect || angular.noop;
          opts.onSelect = function (value, picker) {
            scope.$apply(function() {
              showing = true;
              controller.$setViewValue(element.datepicker("getDate"));
              _onSelect(value, picker);
              element.blur();
            });
          };
          opts.beforeShow = function() {
            showing = true;
          };
          opts.onClose = function(value, picker) {
            showing = false;
          };
          element.on('blur', function() {
            if ( !showing ) {
              scope.$apply(function() {
                element.datepicker("setDate", element.datepicker("getDate"));
                controller.$setViewValue(element.datepicker("getDate"));
              });
            }
          });

          // Update the date picker when the model changes
          controller.$render = function () {
            var date = controller.$viewValue;
            if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
              throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
            }
            element.datepicker("setDate", date);
          };
        }
        // If we don't destroy the old one it doesn't update properly when the config changes
        element.datepicker('destroy');
        // Create the new datepicker widget
        element.datepicker(opts);
        if ( controller ) {
          // Force a render to override whatever is in the input text box
          controller.$render();
        }
      };
      // Watch for changes to the directives options
      scope.$watch(getOptions, initDateWidget, true);
    }
  };
}
])

.constant('uiDateFormatConfig', '')

.directive('uiDateFormat', ['uiDateFormatConfig', function(uiDateFormatConfig) {
  var directive = {
    require:'ngModel',
    link: function(scope, element, attrs, modelCtrl) {
      var dateFormat = attrs.uiDateFormat || uiDateFormatConfig;
      if ( dateFormat ) {
        // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string
        modelCtrl.$formatters.push(function(value) {
          if (angular.isString(value) ) {
            return jQuery.datepicker.parseDate(dateFormat, value);
          }
          return null;
        });
        modelCtrl.$parsers.push(function(value){
          if (value) {
            return jQuery.datepicker.formatDate(dateFormat, value);
          }
          return null;
        });
      } else {
        // Default to ISO formatting
        modelCtrl.$formatters.push(function(value) {
          if (angular.isString(value) ) {
            return new Date(value);
          }
          return null;
        });
        modelCtrl.$parsers.push(function(value){
          if (value) {
            return value.toISOString();
          }
          return null;
        });
      }
    }
  };
  return directive;
}]);
<commit_msg>add check for readonly attribute<commit_after>/*global angular */
/*
 jQuery UI Datepicker plugin wrapper

 @note If ≤ IE8 make sure you have a polyfill for Date.toISOString()
 @param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto uiDateConfig
 */

angular.module('ui.date', [])

.constant('uiDateConfig', {})

.directive('uiDate', ['uiDateConfig', function (uiDateConfig) {
  'use strict';
  var options;
  options = {};
  angular.extend(options, uiDateConfig);
  return {
    require:'?ngModel',
    link:function (scope, element, attrs, controller) {
      var getOptions = function () {
        return angular.extend({}, uiDateConfig, scope.$eval(attrs.uiDate));
      };
      var initDateWidget = function () {
        var showing = false;
        var opts = getOptions();

        // If we have a controller (i.e. ngModelController) then wire it up
        if (controller) {

          // Set the view value in a $apply block when users selects
          // (calling directive user's function too if provided)
          var _onSelect = opts.onSelect || angular.noop;
          opts.onSelect = function (value, picker) {
            scope.$apply(function() {
              showing = true;
              controller.$setViewValue(element.datepicker("getDate"));
              _onSelect(value, picker);
              element.blur();
            });
          };
          opts.beforeShow = function() {
            showing = true;
          };
          opts.onClose = function(value, picker) {
            showing = false;
          };
          // this must be the first focus event added to ensure we can
          // stop the event before it reaches the datepicker widget
          element.on('focus', function(focusEvent) {
            if ( attrs.readonly ) {
              focusEvent.stopImmediatePropagation();
            }
          });
          element.on('blur', function() {
            if ( !showing ) {
              scope.$apply(function() {
                element.datepicker("setDate", element.datepicker("getDate"));
                controller.$setViewValue(element.datepicker("getDate"));
              });
            }
          });

          // Update the date picker when the model changes
          controller.$render = function () {
            var date = controller.$viewValue;
            if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
              throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
            }
            element.datepicker("setDate", date);
          };
        }
        // If we don't destroy the old one it doesn't update properly when the config changes
        element.datepicker('destroy');
        // Create the new datepicker widget
        element.datepicker(opts);
        if ( controller ) {
          // Force a render to override whatever is in the input text box
          controller.$render();
        }
      };
      // Watch for changes to the directives options
      scope.$watch(getOptions, initDateWidget, true);
    }
  };
}
])

.constant('uiDateFormatConfig', '')

.directive('uiDateFormat', ['uiDateFormatConfig', function(uiDateFormatConfig) {
  var directive = {
    require:'ngModel',
    link: function(scope, element, attrs, modelCtrl) {
      var dateFormat = attrs.uiDateFormat || uiDateFormatConfig;
      if ( dateFormat ) {
        // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string
        modelCtrl.$formatters.push(function(value) {
          if (angular.isString(value) ) {
            return jQuery.datepicker.parseDate(dateFormat, value);
          }
          return null;
        });
        modelCtrl.$parsers.push(function(value){
          if (value) {
            return jQuery.datepicker.formatDate(dateFormat, value);
          }
          return null;
        });
      } else {
        // Default to ISO formatting
        modelCtrl.$formatters.push(function(value) {
          if (angular.isString(value) ) {
            return new Date(value);
          }
          return null;
        });
        modelCtrl.$parsers.push(function(value){
          if (value) {
            return value.toISOString();
          }
          return null;
        });
      }
    }
  };
  return directive;
}]);
<|endoftext|>


<commit_before>/* global document */
(function(CooCoo, document) {
    var handlers = {},
        CooCooDOM,
        captureEvents = {focus: true, blur: true},
        props = {checked: true};

    function unwrapArguments(args) {
        var i;

        for (i = 0; i < args.length; i++) {
            args[i] = CooCoo.u(args[i]);
        }
    }

    function splitClass(val) {
        var ret = {},
            i;

        val = (val || '').split(' ');

        for (i = 0; i < val.length; i++) {
            ret[val[i]] = true;
        }

        return ret;
    }

    function modifyClass(node, val, remove) {
        var cls,
            ret = [],
            nodeClass = splitClass(node.getAttribute('class'));

        val = splitClass(val);

        for (cls in val) {
            if (remove) {
                delete nodeClass[cls];
            } else {
                nodeClass[cls] = true;
            }
        }

        for (cls in nodeClass) {
            cls && ret.push(cls);
        }

        node.setAttribute('class', ret.join(' '));
    }

    CooCooDOM = CooCoo.DOM = CooCoo.Extendable.extend({
        init: function(parent, id, node) {
            var self = this,
                h = handlers[id];

            self.id = id;
            self.node = CooCoo.u(node);
            self.parent = parent;

            if (h) {
                h.count++;
            } else {
                handlers[id] = self._handlers = {
                    count: 1,
                    events: {} // events: {click: callback, ...}
                };
            }

            node['_coo' + id] = {parent: parent, cb: {}};

            CooCoo.DOM.__super__.init.call(self, parent);
        },

        destroy: function() {
            var self = this,
                h = handlers[self.id],
                event;

            if (h && !(--h.count)) {
                h = h.events;
                for (event in h) {
                    document.body.removeEventListener(event, h[event]);
                }
                delete handlers[self.id];
            }

            CooCooDOM.__super__.destroy.call(self);
        },

        on: function(event, callback) {
            var self = this,
                handler = self._handlers,
                id = '_coo' + self.id,
                funcs;

            if (handler) {
                if (!handler.events[event]) {
                    document.body.addEventListener(
                        event,
                        (handler.events[event] = function(e) {
                            var meta = e.target,
                                i,
                                ret = 0,
                                lastRet = 0;

                            while (meta && !meta[id]) {
                                meta = meta.parentNode;
                            }

                            if (meta && ((meta = meta[id]))) {
                                funcs = meta.cb[event];
                                for (i = 0; i < funcs.length; i++) {
                                    lastRet = funcs[i].call(meta.parent, e);

                                    // (ret & 1) - prevent default.
                                    // (ret & 2) - stop propagation.
                                    // (ret & 4) - stop immediate propagation.

                                    if (!(ret & 1) && (lastRet & 1)) {
                                        e.preventDefault();
                                    }

                                    if (!((ret & 2) || (ret & 4)) && ((lastRet & 2) || (lastRet & 4))) {
                                        e.stopPropagation();
                                    }

                                    ret |= lastRet;

                                    if (ret & 4) {
                                        break;
                                    }
                                }
                            }
                        }),
                        event in captureEvents
                    );
                }
            }

            funcs = self.node[id].cb;
            funcs = funcs[event] || (funcs[event] = []);
            funcs.push(callback);

            return self;
        }
    });

    CooCooDOM.val = function(node, val) {
        unwrapArguments(arguments);

        if (val === undefined) {
            return node.value || '';
        } else {
            node.value = val;
        }
    };

    CooCooDOM.append = function(parent, node) {
        unwrapArguments(arguments);
        parent.appendChild(node);
    };

    CooCooDOM.text = function(node, val) {
        unwrapArguments(arguments);
        node.innerHTML = '';
        node.appendChild(document.createTextNode(val));
    };

    CooCooDOM.addClass = function(node, val) {
        unwrapArguments(arguments);
        modifyClass(node, val, false);
    };

    CooCooDOM.removeClass = function(node, val) {
        unwrapArguments(arguments);
        modifyClass(node, val, true);
    };

    CooCooDOM.toggleClass = function(node, cls, on) {
        unwrapArguments(arguments);
        modifyClass(node, cls, !on);
    };

    CooCooDOM.attr = function(node, attr, val) {
        unwrapArguments(arguments);

        if (val === undefined) {
            return attr in props ? node[attr] : node.getAttribute(attr);
        } else {
            if (attr in props) {
                node[attr] = val;
            } else {
                node.setAttribute(attr, val);
            }
        }
    };

    CooCooDOM.trigger = function(node, name/*, ...*/) {
        unwrapArguments(arguments);
        if ((name === 'focus' || name === 'blur') && node[name]) {
            node[name]();
        } else {
            var e = document.createEvent('HTMLEvents');
            e.initEvent(name, true, false);
            node.dispatchEvent(e);
        }
    };

    CooCooDOM.serialize = function(node) {
        unwrapArguments(arguments);
        /* global $ */
        return $(node).serializeArray();
    };
})(CooCoo, document);
<commit_msg>Redoing DOM events handling.<commit_after>/* global document */
(function(CooCoo, document) {
    var CooCooDOM,
        eventHandlers = {},
        captureEvents = {focus: true, blur: true},
        props = {checked: true};

    function hasKeys(obj) {
        for (obj in obj) { return true; }
        return false;
    }

    function unwrapArguments(args) {
        var i;

        for (i = 0; i < args.length; i++) {
            args[i] = CooCoo.u(args[i]);
        }
    }

    function splitClass(val) {
        var ret = {},
            i;

        val = (val || '').split(' ');

        for (i = 0; i < val.length; i++) {
            ret[val[i]] = true;
        }

        return ret;
    }

    function modifyClass(node, val, remove) {
        var cls,
            ret = [],
            nodeClass = splitClass(node.getAttribute('class'));

        val = splitClass(val);

        for (cls in val) {
            if (remove) {
                delete nodeClass[cls];
            } else {
                nodeClass[cls] = true;
            }
        }

        for (cls in nodeClass) {
            cls && ret.push(cls);
        }

        node.setAttribute('class', ret.join(' '));
    }

    CooCooDOM = CooCoo.DOM = CooCoo.Extendable.extend({
        init: function(parent, id, node) {
            var self = this;

            self.id = id;
            self.node = CooCoo.u(node);
            self.parent = parent;
            self.events = {};

            CooCoo.DOM.__super__.init.call(self, parent);
        },

        destroy: function() {
            var self = this,
                meta = self.node._coo,
                events = self.events,
                event,
                tmp,
                cur,
                eventHandler;

            for (event in events) {
                tmp = meta[event];
                cur = tmp[self.id];
                delete tmp[self.id];

                if (!hasKeys(tmp)) {
                    delete meta[event];
                }

                eventHandler = eventHandlers[event];
                eventHandler.count -= cur.cb.length;

                if (!eventHandler.count) {
                    document.body.removeEventListener(event, eventHandler.cb);
                    delete eventHandlers[event];
                }
            }

            if (!hasKeys(meta)) {
                delete self.node._coo;
            }

            CooCooDOM.__super__.destroy.call(self);
        },

        on: function(event, callback) {
            var self = this,
                eventHandler,
                cb,
                meta;

            if ((eventHandler = eventHandlers[event])) {
                eventHandler.count++;
            } else {
                document.body.addEventListener(
                    event,
                    (cb = function(e) {
                        var node = e.target,
                            meta,
                            id,
                            funcs,
                            parent,
                            ret = 0,
                            i;

                        while (node) {
                            if (((meta = node._coo)) && ((meta = meta[event]))) {
                                for (id in meta) {
                                    funcs = meta[id];
                                    parent = funcs.parent;
                                    funcs = funcs.cb;

                                    for (i = 0; i < funcs.length; i++) {
                                        /* jshint -W016 */
                                        ret |= funcs[i].call(parent, e);

                                        // (ret & 1) - prevent default.
                                        // (ret & 2) - stop propagation.
                                        // (ret & 4) - stop immediate propagation.

                                        if ((ret & 1) && !e.defaultPrevented) {
                                            e.preventDefault();
                                        }

                                        if (ret & 4) {
                                            return;
                                        }
                                        /* jshint +W016 */
                                    }
                                }
                            }

                            /* jshint -W016 */
                            if (ret & 2) {
                                break;
                            }
                            /* jshint +W016 */
                            node = node.parentNode;
                        }
                    }),
                    event in captureEvents
                );

                eventHandlers[event] = {
                    count: 1,
                    cb: cb
                };
            }

            self.events[event] = true;

            if (!(meta = self.node._coo)) {
                meta = self.node._coo = {};
            }

            if (event in meta) {
                meta = meta[event];
            } else {
                meta = meta[event] = {};
            }

            if (self.id in meta) {
                meta[self.id].cb.push(callback);
            } else {
                meta[self.id] = {
                    parent: self.parent,
                    cb: [callback]
                };
            }

            return self;
        }
    });

    CooCooDOM.val = function(node, val) {
        unwrapArguments(arguments);

        if (val === undefined) {
            return node.value || '';
        } else {
            node.value = val;
        }
    };

    CooCooDOM.append = function(parent, node) {
        unwrapArguments(arguments);
        parent.appendChild(node);
    };

    CooCooDOM.text = function(node, val) {
        unwrapArguments(arguments);
        node.innerHTML = '';
        node.appendChild(document.createTextNode(val));
    };

    CooCooDOM.addClass = function(node, val) {
        unwrapArguments(arguments);
        modifyClass(node, val, false);
    };

    CooCooDOM.removeClass = function(node, val) {
        unwrapArguments(arguments);
        modifyClass(node, val, true);
    };

    CooCooDOM.toggleClass = function(node, cls, on) {
        unwrapArguments(arguments);
        modifyClass(node, cls, !on);
    };

    CooCooDOM.attr = function(node, attr, val) {
        unwrapArguments(arguments);

        if (val === undefined) {
            return attr in props ? node[attr] : node.getAttribute(attr);
        } else {
            if (attr in props) {
                node[attr] = val;
            } else {
                node.setAttribute(attr, val);
            }
        }
    };

    CooCooDOM.trigger = function(node, name/*, ...*/) {
        unwrapArguments(arguments);
        if ((name === 'focus' || name === 'blur') && node[name]) {
            node[name]();
        } else {
            var e = document.createEvent('HTMLEvents');
            e.initEvent(name, true, false);
            node.dispatchEvent(e);
        }
    };

    CooCooDOM.serialize = function(node) {
        unwrapArguments(arguments);
        /* global $ */
        return $(node).serializeArray();
    };
})(CooCoo, document);
<|endoftext|>


<commit_before>(function(){
  var elif = angular.module('elif', []);

  // This is copied from AngularJS because it is not
  // part of the public interface.
  var getBlockElements = function(nodes){
    if(!nodes || !nodes.length){
      return $();
    }

    var startNode = nodes[0];
    var endNode = nodes[nodes.length - 1];

    if(startNode === endNode){
      return $(startNode);
    }

    var element = startNode;
    var elements = [element];

    do {
      element = element.nextSibling;
      if(!element){
        break;
      }
      elements.push(element);
    }
    while(element !== endNode);

    return $(elements);
  };

  elif.factory('elif', [
    function(){
      // By requiring the scope have it's own copy of `elif.conditionals`
      // we avoid if/else-if/else structures that span AngularJS scopes.
      var getConditionals = function(scope){
        if(angular.hasOwnProperty.call(scope, 'elif.conditionals')){
          var conditionals = scope['elif.conditionals'];
          return conditionals[conditionals.length - 1];
        }
      };

      return {
        create: function(scope, fn, callback){
          var conditionals = [{
            fn: fn,
            callback: callback || angular.identity
          }];
          var conditionalValues = [];

          scope.$watch(function(){
            // Watch the boolean conditionals; we only need
            // to run through the if/else-if/else chain if one
            // of them changes.
            conditionalValues.length = conditionals.length;
            for(var i = 0, len = conditionals.length; i < len; i++){
              conditionalValues[i] = !!conditionals[i].fn();
            }
            return conditionalValues;
          }, function(conditionalValues){
            // Find first matching if/else-if.
            var index = -1;
            for(var i = 0, len = conditionals.length; i < len; i++){
              if(conditionalValues[i]){
                conditionals[i].callback(true);
                index = i;
                i++;
                break;
              }
              else {
                conditionals[i].callback(false);
              }
            }

            // Mark the rest of the else-ifs as not matched.
            for(; i < len; i++){
              conditionals[i].callback(false);
            }

            // Handle else, if there is one.
            conditionals.fallthrough && conditionals.fallthrough(index === -1);

            return index;
          }, true); // Deep watch; we know that it is a simple list of booleans.

          // Keep track of if/else-if/else structures per AngularJS scope.
          if(!angular.hasOwnProperty.call(scope, 'elif.conditionals')){
            scope['elif.conditionals'] = [];
          }
          scope['elif.conditionals'].push(conditionals);
        },
        extend: function(scope, fn, callback){
          var conditionals = getConditionals(scope);
          if(!conditionals){
            throw new Error('elif.extend: no if found at this level');
          }
          if(conditionals.fallthrough){
            throw new Error('elif.extend: else-if after else');
          }

          conditionals.push({
            fn: fn,
            callback: callback
          });
        },
        fallthrough: function(scope, fn, callback){
          var conditionals = getConditionals(scope);
          if(!conditionals){
            throw new Error('elif.fallthrough: no if found at this level');
          }
          if(conditionals.fallthrough){
            throw new Error('elif.fallthrough: else already found at this level');
          }
          conditionals.fallthrough = callback;
        }
      };
    }
  ]);

  // This implementation is basically the built-in `ng-if`, hooked into the `elif` service.
  var elifDirective = function(name, method, getter){
    elif.directive(name, [
      '$animate',
      '$document',
      '$injector',
      'elif',
      function($animate, $document, $injector, elif){
        var getterFn = getter && $injector.invoke(getter);

        return {
          transclude: 'element',
          restrict: 'A',
          priority: 600,
          terminal: true,
          link: function(scope, element, attrs, ctrls, transcludeFn){
            var watchFn = getterFn && getterFn(scope, element, attrs);
            var childScope;
            var childElement;
            var previousElements;

            elif[method](scope, watchFn, function(value, conditionals){
              if(value){
                if(!childScope){
                  childScope = scope.$new();
                  transcludeFn(childScope, function(clone){
                    clone[clone.length + 1] = $document[0].createComment(' end ' + name + ': ' + attrs[name] + ' ');
                    childElement = clone;
                    $animate.enter(clone, element.parent(), element);
                  });
                }
              }
              else {
                if(childScope){
                  childScope.$destroy();
                  childScope = null;
                }
                if(previousElements){
                  previousElements.remove();
                  previousElements = null;
                }
                if(childElement){
                  previousElements = getBlockElements(childElement);
                  $animate.leave(previousElements, function(){
                    previousElements = null;
                  });
                  childElement = null;
                }
              }
            });
          }
        };
      }
    ]);
  };

  // Reads the attribute given by `name` and converts it to a boolean.
  var getter = function(name){
    return [
      '$parse',
      function($parse){
        return function(scope, element, attrs){
          var testFn = $parse(attrs[name]);
          return function(){
            return !!testFn(scope);
          };
        };
      }
    ];
  };

  // We rely on the built-in `ng-if` directive to actually perform
  // the transclusion, and simply tie it in to the `elif` service.
  elif.directive('ngIf', [
    '$injector',
    'elif',
    function($injector, elif){
      var getterFn = $injector.invoke(getter('ngIf'));
      return {
        priority: 600,
        link: function(scope, element, attrs){
          var watchFn = getterFn(scope, element, attrs);
          elif.create(scope, watchFn);
        }
      }
    }
  ]);

  // Else-if and else perform their own transclusions.
  elifDirective('ngElseIf', 'extend', getter('ngElseIf'));
  elifDirective('ngElif', 'extend', getter('ngElif'));

  // Else doesn't take an argument.
  elifDirective('ngElse', 'fallthrough');
})();
<commit_msg>Update elif.js<commit_after>(function(){
  var elif = angular.module('elif', []);

  // This is copied from AngularJS because it is not
  // part of the public interface.
  var getBlockElements = function(nodes){
    if(!nodes || !nodes.length){
      return angular.element();
    }

    var startNode = nodes[0];
    var endNode = nodes[nodes.length - 1];

    if(startNode === endNode){
      return angular.element(startNode);
    }

    var element = startNode;
    var elements = [element];

    do {
      element = element.nextSibling;
      if(!element){
        break;
      }
      elements.push(element);
    }
    while(element !== endNode);

    return angular.element(elements);
  };

  elif.factory('elif', [
    function(){
      // By requiring the scope have it's own copy of `elif.conditionals`
      // we avoid if/else-if/else structures that span AngularJS scopes.
      var getConditionals = function(scope){
        if(angular.hasOwnProperty.call(scope, 'elif.conditionals')){
          var conditionals = scope['elif.conditionals'];
          return conditionals[conditionals.length - 1];
        }
      };

      return {
        create: function(scope, fn, callback){
          var conditionals = [{
            fn: fn,
            callback: callback || angular.identity
          }];
          var conditionalValues = [];

          scope.$watch(function(){
            // Watch the boolean conditionals; we only need
            // to run through the if/else-if/else chain if one
            // of them changes.
            conditionalValues.length = conditionals.length;
            for(var i = 0, len = conditionals.length; i < len; i++){
              conditionalValues[i] = !!conditionals[i].fn();
            }
            return conditionalValues;
          }, function(conditionalValues){
            // Find first matching if/else-if.
            var index = -1;
            for(var i = 0, len = conditionals.length; i < len; i++){
              if(conditionalValues[i]){
                conditionals[i].callback(true);
                index = i;
                i++;
                break;
              }
              else {
                conditionals[i].callback(false);
              }
            }

            // Mark the rest of the else-ifs as not matched.
            for(; i < len; i++){
              conditionals[i].callback(false);
            }

            // Handle else, if there is one.
            conditionals.fallthrough && conditionals.fallthrough(index === -1);

            return index;
          }, true); // Deep watch; we know that it is a simple list of booleans.

          // Keep track of if/else-if/else structures per AngularJS scope.
          if(!angular.hasOwnProperty.call(scope, 'elif.conditionals')){
            scope['elif.conditionals'] = [];
          }
          scope['elif.conditionals'].push(conditionals);
        },
        extend: function(scope, fn, callback){
          var conditionals = getConditionals(scope);
          if(!conditionals){
            throw new Error('elif.extend: no if found at this level');
          }
          if(conditionals.fallthrough){
            throw new Error('elif.extend: else-if after else');
          }

          conditionals.push({
            fn: fn,
            callback: callback
          });
        },
        fallthrough: function(scope, fn, callback){
          var conditionals = getConditionals(scope);
          if(!conditionals){
            throw new Error('elif.fallthrough: no if found at this level');
          }
          if(conditionals.fallthrough){
            throw new Error('elif.fallthrough: else already found at this level');
          }
          conditionals.fallthrough = callback;
        }
      };
    }
  ]);

  // This implementation is basically the built-in `ng-if`, hooked into the `elif` service.
  var elifDirective = function(name, method, getter){
    elif.directive(name, [
      '$animate',
      '$document',
      '$injector',
      'elif',
      function($animate, $document, $injector, elif){
        var getterFn = getter && $injector.invoke(getter);

        return {
          transclude: 'element',
          restrict: 'A',
          priority: 600,
          terminal: true,
          link: function(scope, element, attrs, ctrls, transcludeFn){
            var watchFn = getterFn && getterFn(scope, element, attrs);
            var childScope;
            var childElement;
            var previousElements;

            elif[method](scope, watchFn, function(value, conditionals){
              if(value){
                if(!childScope){
                  childScope = scope.$new();
                  transcludeFn(childScope, function(clone){
                    clone[clone.length + 1] = $document[0].createComment(' end ' + name + ': ' + attrs[name] + ' ');
                    childElement = clone;
                    $animate.enter(clone, element.parent(), element);
                  });
                }
              }
              else {
                if(childScope){
                  childScope.$destroy();
                  childScope = null;
                }
                if(previousElements){
                  previousElements.remove();
                  previousElements = null;
                }
                if(childElement){
                  previousElements = getBlockElements(childElement);
                  $animate.leave(previousElements, function(){
                    previousElements = null;
                  });
                  childElement = null;
                }
              }
            });
          }
        };
      }
    ]);
  };

  // Reads the attribute given by `name` and converts it to a boolean.
  var getter = function(name){
    return [
      '$parse',
      function($parse){
        return function(scope, element, attrs){
          var testFn = $parse(attrs[name]);
          return function(){
            return !!testFn(scope);
          };
        };
      }
    ];
  };

  // We rely on the built-in `ng-if` directive to actually perform
  // the transclusion, and simply tie it in to the `elif` service.
  elif.directive('ngIf', [
    '$injector',
    'elif',
    function($injector, elif){
      var getterFn = $injector.invoke(getter('ngIf'));
      return {
        priority: 600,
        link: function(scope, element, attrs){
          var watchFn = getterFn(scope, element, attrs);
          elif.create(scope, watchFn);
        }
      }
    }
  ]);

  // Else-if and else perform their own transclusions.
  elifDirective('ngElseIf', 'extend', getter('ngElseIf'));
  elifDirective('ngElif', 'extend', getter('ngElif'));

  // Else doesn't take an argument.
  elifDirective('ngElse', 'fallthrough');
})();
<|endoftext|>