Show:

File: platform/plugins/Places/web/js/tools/location.js

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

/**
 * Places Tools
 * @module Places-tools
 */

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

/**
 * Allows the logged-in user to add his own locations and select among these locations
 * @class Places location
 * @constructor
 * @param {Object} [options] used to pass options
 * @param {String} [options.publisherId=Q.Users.loggedInUserId()] Override the publisherId
 *  for saving new locations.
 * @param {Object} [options.geocode] Default google location object, if available
 * @param {Places.Coordinates} [options.location] Provide a location to start off with.
 *  Can be anything that is accepted by Places.Coordinates constructor.
 * @param {Boolean} [options.showCurrent=true] Whether to allow user to select current location
 * @param {Boolean} [options.showLocations=true] Whether to allow user to select their saved locations
 * @param {Boolean} [options.showAddress=true] Whether to allow user to enter a custom address
 * @param {Boolean} [options.showAreas=false] Whether to show Places/areas tool for selected location
 * @param {Q.Event} [options.onChoose] User selected some valid location. First parameter is Places.Coordinates object.
 */
Q.Tool.define("Places/location", function (options) {
	var tool = this;
	var state = this.state;
	var $te = $(tool.element);

	Q.addStylesheet('Q/plugins/Places/css/location.css');

	// change location event
	$te.on(Q.Pointer.click, "[data-location], .Places_location_preview_tool", function () {
		tool.toggle(this);
	});

	tool.Q.onStateChanged('location').set(function () {
		Q.handle(state.onChoose, tool, [state.location]);
	});

	// if showAreas - add Places/areas tool if not exist
	if(state.showAreas){
		state.onChoose.set(function(location){
			var placesAreas = tool.$(".Q_tool.Places_areas_tool")[0];
			placesAreas = placesAreas ? Q.Tool.from(placesAreas, "Places/areas") : null;

			if (Q.isEmpty(location)) {
				if (Q.typeOf(placesAreas) === "Q.Tool") {
					Q.Tool.remove(placesAreas.element);
				}

				return false;
			}

			if (!placesAreas) {
				$("<div>").tool("Places/areas", {
					location: location
				}).appendTo($te).activate();

				return true;
			}

			placesAreas.state.location = location;
			placesAreas.refresh();

			return true;
		}, tool);
	}

	tool.refresh();
},

{ // default options here
	geocode: null,
	onChoose: new Q.Event(function (coordinates) {
		this.state.location = coordinates;
	}, 'Places/location'),
	location: null, // currently selected location
	showCurrent: true,
	showLocations: true,
	showAddress: true,
	showAreas: false
},

{ // methods go here
	/**
	 * Refresh the display
	 * @method refresh
	 */
	refresh: function () {
		var tool = this;
		var state = tool.state;
		var $te = $(tool.element);

		// location empty - render standard location select template
		if (Q.isEmpty(state.location)) {
			tool.selectLocation();
			return;
		}

		// location already set in state - just type it
		Places.Coordinates.from(state.location).geocode(function (err, results) {
			var fem = Q.firstErrorMessage(err);
			if (fem) {
				// if something wrong with location
				// render standard location select template
				tool.selectLocation();

				return console.warn(fem);
			}

			var result = results[0];
			var address = result.formatted_address || result.address;
			$te.html(address).addClass("Q_selected");
			Q.handle(state.onChoose, tool, [this, result.geometry.location]);
		});
	},
	/**
	 * Render template with locations, so user can select one of them
	 * @method selectLocation
	 */
	selectLocation: function(){
		var tool = this;
		var state = tool.state;
		var $te = $(tool.element);
		var userId = Users.loggedInUserId();

		Q.Text.get('Places/content', function (err, text) {
			Q.Template.render('Places/location/select', Q.extend({
				text: text
			}, state), function (err, html) {
				$te.html(html).activate(function () {

					// set address tool
					tool.$(".Places_location_address")
					.tool('Places/address', {
						onChoose: _onChoose
					}, 'Places_address', tool.prefix)
					.activate(function () {
						tool.addressTool = this;
						var $toolParent = $(tool.addressTool.element).closest("[data-location=address]");

						// wait when included Q/filter tool activated
						this.state.onFilterActivated.set(function () {
							// start toggle locations when address tool input focused.
							this.state.onFocus.set(function () {
								tool.toggle($toolParent);
							}, tool);
						}, tool);
					});

					// set showLocations state.
					if (state.showLocations && userId) {
						tool.$(".Places_location_related")
							.tool('Streams/related', {
								publisherId: userId,
								streamName: 'Places/user/locations',
								relationType: 'Places/locations',
								isCategory: true,
								editable: false,
								realtime: true,
								sortable: false
							}, tool.prefix + 'relatedLocations')
							.activate(function () {
								tool.relatedTool = this;
							});
					}

					function _onChoose(place) {
						if (!place || !place.id) {
							return Q.handle(state.onChoose, tool, [null, null]);
						}
						Places.Coordinates.from({
							placeId: place.id
						}).geocode(function (err, results) {
							var msg = Q.firstErrorMessage(err);
							if (msg) {
								throw new Q.Error(msg);
							}

							var result = results[0];
							if (!result || !userId) {
								return;
							}
							var attributes = {
								types: result.types,
								latitude: result.geometry.location.lat(),
								longitude: result.geometry.location.lng(),
								locationType: result.geometry.type,
								venue: place.name
							};
							if (result.place_id) {
								attributes.placeId = result.place_id;
							}
							var textConfirm = text.location.confirm;
							Q.confirm(textConfirm.message, function (shouldSave) {
								if (!shouldSave) {
									return;
								}
								var textAdd = text.location.add;
								Q.prompt(textAdd.title, function (title) {
									if (!title) {
										return;
									}
									var publisherId = state.publisherId || userId;
									Streams.create({
										publisherId: publisherId,
										type: 'Places/location',
										title: title,
										attributes: attributes,
										readLevel: 0,
										writeLevel: 0,
										adminLevel: 0
									}, function (err) {
										if (!err) {
											var sf = this.fields;
											tool.relatedTool.state.onRefresh.setOnce(
											function (previews, map, entering, exiting, updating) {
												var key = Q.firstKey(entering);
												var index = map[key];
												var preview = previews[index];
												Q.Pointer.canceledClick = false;
												tool.toggle(preview.element);
											});
										}
									}, {
										publisherId: userId,
										streamName: 'Places/user/locations',
										type: 'Places/locations'
									});
								}, {
									title: textAdd.title,
									placeholder: textAdd.placeholder,
									ok: textAdd.ok
								});
							}, {
								title: textConfirm.title,
								ok: textConfirm.ok,
								cancel: textConfirm.cancel
							});

							this.venue = place.name;
							var description = place.description;
							if (description.indexOf(',') >= 0) {
								this.venue += description.substr(description.indexOf(','));
							}

							$te.find(".Q_selected").removeClass("Q_selected");
							$(tool.addressTool.element).addClass('Q_selected');
							Q.handle(state.onChoose, tool, [this, result.geometry.location]);
						});
					}
				});
			}, {tool: tool});
		});
	},
	/**
	 * Get current geolocation
	 * @method getCurrentPosition
	 * @param {function} [success] Callback for success
	 * @param {function} [fail] Callback for fail
	 */
	getCurrentPosition: function (success, fail) {
		var tool = this;

		navigator.geolocation.getCurrentPosition(function (pos) {
			Q.handle(success, tool, [pos]);
		}, function (err) {
			Q.handle(fail, tool, [err]);
			Q.alert("Places.location.getCurrentPosition: ERROR(" + err.code + "): " + err.message);

			return false;
		}, {
			enableHighAccuracy: false, // need to set true to make it work consistently, it doesn't seem to make it any more accurate
			timeout: 5000,
			maximumAge: 0
		});
	},
	/**
	 * Make some needed actions when user select some other location.
	 * @method toggle
	 * @param {HTMLElement} elem Selected currently element
	 */
	toggle: function (elem) {
		var tool = this;
		var state = this.state;
		var $te = $(tool.element);
		var $this = $(elem);

		if ($this.hasClass('Q_selected')) {
			return false;
		}

		// toggle Q_selected class
		$te.find(".Q_selected").removeClass("Q_selected");
		$this.addClass('Q_selected');

		var $olt = $this.find(tool.addressTool.element);
		if ($olt.length) {
			$olt.plugin('Q/placeholders', function () {
				tool.addressTool.filter.setText('');
				tool.addressTool.filter.$input.plugin('Q/clickfocus');
			});
		}

		var selector = $this.attr("data-location");
		if (selector === 'current') {
			tool.getCurrentPosition(function (pos) {
				var crd = pos.coords;
				if (!crd) {
					Q.alert("Places/location tool: could not obtain location", pos);
					return false;
				}
				Places.Coordinates.from({
					latitude: crd.latitude,
					longitude: crd.longitude
				}).geocode(function (err, results) {
					var loc = Q.getObject([0, 'geometry', 'location'], results);
					Q.handle(state.onChoose, tool, [this, loc]);
				});
			}, function (err) {
				Q.handle(state.onChoose, tool, [null, null]);
			});

			return;
		} else if (selector === 'address') {
			// if address selected just repeat onChoose event of Places/address tool
			Q.handle(
				tool.addressTool.state.onChoose,
				tool.addressTool,
				[tool.addressTool.place]
			);
			return;
		}

		// related location selected
		var locationPreviewTool = Q.Tool.from($this, "Streams/preview");
		var ls = locationPreviewTool.state;
		Streams.get(ls.publisherId, ls.streamName, function () {
			Places.Coordinates.from(this).geocode(function (err, results) {
				var loc = Q.getObject([0, 'geometry', 'location'], results);
				Q.handle(state.onChoose, tool, [this, loc]);
			});
		});
	}
});

Q.Template.set('Places/location/select',
	'{{#if showCurrent}}' +
		'<div data-location="current">{{text.location.myCurrentLocation}}</div>' +
	'{{/if}}' +
	'{{#if showLocations}}' +
		'<div class="Places_location_related"></div>' +
	'{{/if}}' +
	'{{#if showAddress}}' +
		'<div data-location="address">' +
			'<label>{{text.location.enterAddress}}</label>' +
			'<div class="Places_location_address"></div>' +
		'</div>' +
	'{{/if}}'
);
})(Q, jQuery, window, document);