Show:

File: platform/plugins/Streams/web/js/tools/avatar.js

(function (Q, $, window, undefined) {

var Users = Q.Users;
var Streams = Q.Streams;

/**
 * Streams Tools
 * @module Streams-tools
 * @main
 */

/**
 * Avatar representing a user
 * @class Users avatar
 * @constructor
 * @param {String} prefix Prefix of the tool to be constructed.
 * @param {Object} [options] A hash of options, containing:
 *   @param {String} options.userId The id of the user object. Defaults to id of the logged-in user, if any. Can be '' for a blank-looking avatar.
 *   @param {Number|String|true} [options.icon=Q.Users.icon.defaultSize] Size of the icon to render before the display name. Or 0 for no icon. You can also pass true here for default size. Or pass a string to specify the url of the icon.
 *   @param {Boolean} [options.contents=true] Set to false to not show the name
 *   @param {Boolean} [options.short=false] If true, renders the short version of the display name.
 *   @param {Boolean|Array} [options.editable=false] If true, and userId is the logged-in user's id, the tool presents an interface for the logged-in user to edit their name and icon. This can also be an array containing one or more of 'icon', 'name'.
 *   @param {Boolean} [options.short=false] If true, renders the short version of the display name.
 *   @param {String} [options.className] Any css classes to add to the tool element
 *   @param {Boolean} [options.reflectChanges=true] Whether the tool should update its contents on changes to user streams like firstName, lastName, username and icon. Set to false if you are showing many avatars in a list such as "Users/list" or "Streams/participating" tools. Otherwise it can result many database queries – one per avatar!
 *   @param {Boolean} [options.reflectIconChanges=String(options.icon).isUrl()] Whether to automatically update the icon if the user's icon stream changes
 *   @param {Number} [options.cacheBust=null] Number of milliseconds to use for combating the re-use of cached images when they are first loaded.
 *   @param {Object} [options.templates]
 *     @param {Object} [options.templates.icon]
 *       @param {String} [options.templates.icon.dir='{{Users}}/views']
 *       @param {String} [options.templates.icon.name='Users/avatar/icon']
 *       @param {Object} [options.templates.icon.fields]
 *         @param {String} [options.templates.icon.fields.alt="user icon"]
 *     @param {Object} [options.templates.contents]
 *       @param {String} [options.templates.contents.dir='{{Users}}/views']
 *       @param {String} [options.templates.contents.name='Users/avatar/contents']
 *       @param {Object} [options.templates.contents.fields]
 *         @param {String} [options.templates.contents.fields.tag="span"]
 * @param {Object} [options.inplaces] Additional fields to pass to the child Streams/inplace tools, if any
 *   @param {Q.Event} [options.onRefresh]  An event that occurs when the avatar is refreshed
 *   @param {Q.Event} [options.onUpdate]  An event that occurs when the icon is updated via this tool
 *   @param {Q.Event} [options.onImagepicker]  An event that occurs when the imagepicker is activated
 *   @param {Q.Event} [options.onMissing]  An event that occurs if the avatar info turns out to be missing
 */
Q.Tool.define("Users/avatar", function Users_avatar_tool(options) {
	var tool = this;
	var state = this.state;
	if (state.icon === true) {
		state.icon = Users.icon.defaultSize;
	}
	if (state.userId == null) {
		state.userId = Users.loggedInUserId();
	}
	if (state.editable === true) {
		state.editable = ['icon', 'name'];
	}
	this.refresh(true);
	if (!state.userId) {
		return;
	}
	if (state.className) {
		$(tool.element).addClass(state.className);
	}
	if (state.reflectIconChanges === undefined) {
		state.reflectIconChanges = !String(state.icon).isUrl();
	}
	if (state.reflectIconChanges) {
		Streams.Stream.onFieldChanged(state.userId, 'Streams/user/icon', 'icon')
		.set(function (fields, field) {
			var $img = tool.$('.Users_avatar_icon');
			var iconSize = state.icon || $img.width();
			$img.attr('src', 
				Q.url(Streams.iconUrl(fields.icon, iconSize), null, {
					cacheBust: state.cacheBustOnUpdate
				})
			);
		}, this);
	}
	if (!state.editable || state.editable.indexOf('name') < 0) {
		Streams.Stream.onFieldChanged(state.userId, 'Streams/user/firstName', 'content')
		.set(handleChange, this);
		Streams.Stream.onFieldChanged(state.userId, 'Streams/user/lastName', 'content')
		.set(handleChange, this);
		Streams.Stream.onFieldChanged(state.userId, 'Streams/user/gender', 'content')
		.set(handleChange, this);
	}
	function handleChange(fields, field) {
		Streams.Avatar.get.forget(state.userId);
		tool.refresh();
	}
},

{
	userId: undefined,
	icon: Users.icon.defaultSize,
	contents: true,
	"short": false,
	className: null,
	reflectChanges: true,
	templates: {
		icon: {
			dir: '{{Users}}/views',
			name: 'Users/avatar/icon',
			fields: { alt: "user icon" }
		},
		contents: {
			dir: '{{Users}}/views',
			name: 'Users/avatar/contents',
			fields: { tag: "span" }
		}
	},
	editable: false,
	imagepicker: {},
	inplaces: {},
	cacheBust: null,
	cacheBustOnUpdate: 1000,
	onRefresh: new Q.Event(),
	onUpdate: new Q.Event(),
	onImagepicker: new Q.Event(),
	onMissing: new Q.Event(function () {
		this.element.style.display = 'none';
	}, 'Users/avatar')
},

{
	/**
	 * Refresh the avatar's display
	 * @method refresh
	 * @param {boolean} [unlessContent=false] only used by constructor to pass true
	 */
	refresh: function (unlessContent) {
		var tool = this;
		var state = this.state;
		if (state.userId === undefined) {
			console.warn("Users/avatar: no userId provided");
			return; // empty
		}
		if (unlessContent && tool.element.childNodes.length) {
			return _present();
		}
		Q.Tool.clear(tool.element);
		if (state.icon === true) {
			state.icon = Users.icon.defaultSize;
		}
		
		var p = new Q.Pipe(['icon', 'contents'], function (params) {
			var icon = state.icon ? params.icon[0] : '';
			var contents = state.contents ? params.contents[0] : '';
			tool.element.innerHTML = icon + contents;
			_present();
		});
		if (state.userId === '') {
			var fields = Q.extend({}, state.templates.contents.fields, {
				name: ''
			});
			Q.Template.render('Users/avatar/icon/blank', fields, function (err, html) {
				p.fill('icon')(html);
			});
			Q.Template.render('Users/avatar/contents/blank', fields, function (err, html) {
				p.fill('contents')(html);
			});
			return;
		}

		var fields = Q.extend({}, state.templates.icon.fields, {
			src: Q.url(Users.iconUrl('loading'), null),
			state: state
		});
		Q.Template.render('Users/avatar/loading', fields, function (err, html) {
			tool.element.innerHTML = html;
		});
		tool.element.addClass('Q_loading');
		
		Streams.Avatar.get(state.userId, function (err, avatar) {
			var fields;
			tool.element.removeClass('Q_loading');
			if (!avatar) {
				return Q.handle(state.onMissing, tool, [err]);
			}
			state.avatar = avatar;
			if (state.icon) {
				var src = isNaN(state.icon)
					? state.icon
					: Q.url(avatar.iconUrl(state.icon), null);
				fields = Q.extend({}, state.templates.icon.fields, {
					src: src,
					size: parseInt(state.icon)
				});
				Q.Template.render('Users/avatar/icon', fields, 
				function (err, html) {
					p.fill('icon')(html);
				}, Q.extend({size: state.icon}, state.templates.icon));
			} else {
				p.fill('icon')('');
			}

			fields = Q.extend({}, state.templates.contents.fields, {
				name: this.displayName(Q.extend({}, state, {html: true}))
			});
			if (fields.name) {
				Q.Template.render('Users/avatar/contents', fields,
				function (err, html) {
					p.fill('contents')(html);
				}, state.templates.contents);
			} else {
				Q.Template.render('Users/avatar/contents/blank', fields,
				function (err, html) {
					p.fill('contents')(html);
				});
			}
		});
		
		if (state.reflectChanges) {
			// Retain the streams, so they can be refreshed while this tool is active,
			// triggering the Streams plugin to update the avatar.
			Streams.Stream.retain(state.userId, [
				'Streams/user/firstName', 
				'Streams/user/lastName', 
				'Streams/user/gender', 
				'Streams/user/username',
				'Streams/user/icon'
			], tool);
		}
	
		function _present() {
			Q.handle(state.onRefresh, tool, []);
			if (!state.editable) return;
			if (state.editable === true) {
				state.editable = ['icon', 'name'];
			}
			if (state.editable.indexOf('name') >= 0) {
				var zIndex = 5;
				Q.each(['firstName', 'lastName', 'username'], function (k, vName) {
					var f = tool.getElementsByClassName('Streams_'+vName)[0];
					if (!f || f.getElementsByClassName('Streams_inplace_tool').length) {
						return;
					}
					var opt = Q.extend({
						publisherId: state.userId,
						streamName: 'Streams/user/'+vName,
						inplaceType: 'text',
						inplace: {
							bringToFront: f,
							placeholder: 'Your '+vName.substr(0, vName.length-4)+' name',
							staticHtml: f.innerHTML
						}
					}, state.inplaces);
					Q.Tool.setUpElement(
						f, 'Streams/inplace', opt,
						tool.prefix+'Streams_inplace-'+vName, tool.prefix
					);
					f.style.zIndex = --zIndex;
					Q.activate(f);
				});
			}
			if (state.editable.indexOf('icon') >= 0 && Users.loggedInUser) {
				var $img = tool.$('.Users_avatar_icon').addClass('Streams_editable');
				var saveSizeName = {};
				Q.each(Users.icon.sizes, function (k, v) {
					saveSizeName[v] = v+".png";
				});
				Streams.retainWith(tool).get(
					Users.loggedInUser.id,
					'Streams/user/icon',
					function (err) {
						var stream = this;
						var o = Q.extend({
							saveSizeName: saveSizeName,
							showSize: state.icon || $img.width(),
							path: 'Q/uploads/Users',
							preprocess: function (callback) {
								callback({
									subpath: state.userId.splitId()+'/icon/'
										+Math.floor(Date.now()/1000)
								});
							},
							onSuccess: {"Users/avatar": function () {
								stream.refresh(function () {
									state.onUpdate.handle.call(tool, this);
								}, {
									unlessSocket: true,
									changed: { icon: true }
								});
							}},
							cacheBust: state.cacheBust
						}, state.imagepicker);
						$img.plugin('Q/imagepicker', o, function () {
							state.onImagepicker.handle($img.state('Q/imagepicker'));
						});
					}
				)
			}
			Streams.onAvatar(state.userId).set(function () {
				tool.refresh();
			}, tool);
		}
	}
}

);

Q.Template.set('Users/avatar/loading', '{{#if state.icon}}<img src="{{& src}}" alt="{{alt}}" class="Users_avatar_loading Users_avatar_icon Users_avatar_icon_{{size}}">{{else}}...{{/if}}');
Q.Template.set('Users/avatar/icon', '<img src="{{& src}}" alt="{{alt}}" class="Users_avatar_icon Users_avatar_icon_{{size}}">');
Q.Template.set('Users/avatar/contents', '<{{tag}} class="Users_avatar_name">{{& name}}</{{tag}}>');
Q.Template.set('Users/avatar/icon/blank', '<div class="Users_avatar_icon Users_avatar_icon_blank"></div>');
Q.Template.set('Users/avatar/contents/blank', '<div class="Users_avatar_name Users_avatar_name_blank">&nbsp;</div>');

})(Q, jQuery, window);