example11.php HOME Examples overview Download Ask a question / privacy policy / imprint
example13.php

AJAX-ZOOM for developers. Tagging images with hotspots, adding title and description. "createNewHotspot" API tutorial.

This is a demonstration of the AJAX-ZOOM API methods that deal with hotspots. Therefore, it makes only sense for developers!

You can tag images with interactive markers (hotspots) using the AJAX-ZOOM $.fn.axZm.createNewHotspot API method by clicking on some spot at the AJAX-ZOOM viewer. The position of the hotspots is adjustable via drag & drop. It works in the zoomed and not zoomed states so that the user can precisely set the position.

Additionally, the user can define a title and a description for these markers. All data such as coordinates of the hotspots and the text is present in external JavaScript object, which keeps track on all changes. The functionality applies to standard images and 360 product views equally well.

>> HTML console

About

In this example, the data object prints in the "virtual console" as JSON. The output is updated when the data changes. You can store this data within your application or do something different with it. For instance, you can send it via PostMessage to a different client. In a Digital-Asset-Management system, the marketing manager could assign a task to edit the image in a certain way.

This snippet is not meant to represent a final or complete solution! All codes have inline comments, so as a developer you can start adapting them fast. In the AJAX-ZOOM documentation, there is plenty of additional information about all API methods that are present in this example code.

There are several other examples for developers that may be useful to extend the functionality of this one. For example, it is possible to take snapshots of the specific areas of an image, save them or add to the report. In any case, as a developer, you should inspect the possible callbacks/hooks before proceeding to edit the code of this snippet.

If you have a determinate idea of the functionality that you want to achieve but do not want or do not have time to complete, you can ask the AJAX-ZOOM team for a quote.

JavaScript ans CSS in head

<!-- Include jQuery core into head section if not already present -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

<!-- JSON -->
<script type="text/javascript" src="../axZm/plugins/JSON/jquery.json-2.3.min.js"></script>

<!--  AJAX-ZOOM javascript -->
<script type="text/javascript" src="../axZm/jquery.axZm.js"></script>
<link type="text/css" href="../axZm/axZm.css" rel="stylesheet" />

CSS

/* cursor in tagging mode */
.azTagging {cursor: crosshair !important;}

/* title textfield */
.azTextField {width: 100%; margin-bottom: 5px; box-sizing: border-box !important; padding: 5px; font-family: arial; font-size: 12px; border: 1px solid #999999; border-radius: 3px;}

/* description textarea */
.azTextArea {width: 100%; height: 100px; box-sizing: border-box !important; padding: 5px; background-color: #FFF; font-family: arial; font-size: 12px; border: 1px solid #999999; border-radius: 3px;}

/* save, delete button */
.azButton {margin-top: 5px;}

/* message about click to place a hotspot */
.azTaggingMsg {position: absolute; background-color: #B50904; width: 290px; margin-left: -140px; top: -1px; left: 50%; border: #000 1px solid; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 11px; color: #FFF; padding: 1px 5px 1px 5px; z-index: 1; pointer-events: none;}

/* parent container for navigation (mNavi) */
.azCustomNavi{background-color: #AAA; box-sizing: border-box; height: 58px; padding: 4px 0px 4px 0px; border-left: #000 1px solid; border-bottom: #000 1px solid; border-right: #000 1px solid;}

/* html console */
.azTaggingResults{background-color: #101010; color: #3cc628; padding: 5px; margin: 0; height: 489px; overflow: hidden; overflow-y: auto;}
.azPre{tab-size: 2; -moz-tab-size: 2; margin: 0; font-size: 11px; font-family: monospace; padding: 5px; background-color: transparent; color: #3cc628; border-width: 0;}

/* Overwrite css from /axZm/jquery.axZm.js */
.axZmToolTipInner {background-color: #c5d8e1; border-color: #5583b4; border-width: 3px;}

.axZmToolTipTitle {color: #FFF; /* #1a4a7a*/ font-size: 16px; line-height: 18px; min-height: 24px; text-shadow: 0px 0px 2px rgba(150, 150, 150, 0.75);}

.axZm_zoomCustomNaviParentID {margin: 0 auto;}

#axZm_zoomLogHolder {width: 55px;}

HTML in body

azTaggingResults shows JSON created after placing the hotspots, so it is not needed in your final code

<div class="embed-responsive" style="padding-bottom: 80%; border: #000 1px solid">
	<!-- Div where AJAX-ZOOM is loaded into -->
	<div id="azParentContainer" class="embed-responsive-item" style="max-height: 94vh; max-height: calc(100vh - 50px);">
		Loading, please wait...
	</div>
</div>
<!-- Parent container for "mNavi" -->
<div id="azCustomNavi" class="azCustomNavi"></div>

<!-- Console -->
<div id="azTaggingResults" class="azTaggingResults">>> HTML console</div>

Javascript

Every line is commented... The whole code could be ofcourse wraped into external JS. If after all you have any questions regarding AJAX-ZOOM - do not hesitate to contact us.

// some var to hold everyting else
window.ajaxZoom = {};

// the ID of the div element where ajax-zoom has to be inserted into
ajaxZoom.divID = "azParentContainer";

// path to the axZm folder
ajaxZoom.path = "../axZm/";

// zoomData - defines, which image should be loaded
ajaxZoom.parameter = "zoomData=/pic/zoom/furniture/furniture_005.jpg";

// example - defines, which "options set" is taken
// options in /axZm/zoomConfig.inc.php are overriden in /axZm/zoomConfigCustom.inc.php
// after elseif ($_GET['example'] == 'tagging'){
ajaxZoom.parameter += "&example=tagging";

// Local time ahead server time (PHP solution), not needed, you can set timeDiff to 0
ajaxZoom.timeDiff = (new Date()).getTime() - 1743033826523;

// switch for tagging mode
ajaxZoom.taggingMode = true;

// Prevent setting and dragging a hotspot outside of the image bounds
ajaxZoom.taggingBounds = true;

// some var where we will store user defined title and description
ajaxZoom.myTags = {};

// define some text strings
ajaxZoom.taggingTxt = {
	"msg": "ยป click somewhere to place a hotspot; right-click to remove",
	"disable": "Disable tagging mode",
	"enable": "Enable tagging mode",
	"title": "Title",
	"description": "Description",
	"confirmDelete": "Really delete?",
	"notes": "Notes",
	"noDescr": "No notes left"
};

// add tagging message and manage other tasks when tagging mode is enabled
ajaxZoom.setTaggingMsg = function() {
	// append tagging message
	if (!$("#azTaggingMsg").length) {
		$("").attr("id", "azTaggingMsg").addClass("azTaggingMsg")
		.html(ajaxZoom.taggingTxt.msg)
		.appendTo("#axZm_zoomLayer");
	}else{
		$("#azTaggingMsg").css("display", "block");
	}

	// change cursor
	$("#axZm_zoomLayerImg").addClass("azTagging");

	// change src and description of the custom button, which changes states
	$("#customBtn_mCustomBtn1")
	.data("btnSrc", $.axZm.icon + $.axZm.buttonSet + "/" + "button_iPad_tag_switched.png")
	.data("bAlt", ajaxZoom.taggingTxt.disable)
	.attr("src", $.axZm.icon + $.axZm.buttonSet + "/" + "button_iPad_tag_switched.png");

	// make hotspots draggable
	$.fn.axZm.hotspotsDraggable();
};

// hide tagging message and manage other tasks when tagging mode is disabled
ajaxZoom.removeTaggingMsg = function() {
	// hide tagging message
	$("#azTaggingMsg").css("display", "none");

	// remove class, which changed the cursor
	$("#axZm_zoomLayerImg").removeClass("azTagging");

	// change src and description of the custom button, which changes states
	$("#customBtn_mCustomBtn1")
	.data("btnSrc", $.axZm.icon + $.axZm.buttonSet + "/" + "button_iPad_tag.png")
	.data("bAlt", ajaxZoom.taggingTxt.enable)
	.attr("src", $.axZm.icon + $.axZm.buttonSet + "/" + "button_iPad_tag.png");

	// make hotspots not traggable any more
	$.fn.axZm.hotspotsDraggable(true);

	// close all opened tooltips
	$.fn.axZm.removeAllToolTips();
};

// function, which will be executed in onZoomInClickStart AJAX-ZOOM callback
// important is that when you do not want AJAX-ZOOM to zoom, this function should return false;
ajaxZoom.evaluateClick = function(info) {
	// do not do anything if ajaxZoom.taggingMode (a switch var) is false
	if (!ajaxZoom.taggingMode) {
		return;
	}

	var xClick;
	var yClick;

	// position of the click related to its original size
	if (ajaxZoom.taggingBounds) {
		xClick = info.viewport.x;
		yClick = info.viewport.y;
	} else {
		xClick = info.viewport.x;
		yClick = info.viewport.y;
	}


	// file (image) name
	var currentImageName = $.axZm.zoomGA[$.axZm.zoomID].img;

	// position of the hotspot in this file
	// there could be same hotspot on an image in the gallery,
	// this is why we need image name here and it is defined this way
	var posObj = {}; posObj[currentImageName] = {left: xClick, top: yClick};

	// show hotspots in case they are disabled
	$.fn.axZm.showHotspotLayer();

	// after we know the position create hotspot "on-the-fly"
	$.fn.axZm.createNewHotspot({
		// generate some image name
		// image name could contain the creation date and time
		// you might also want to get it from server before creating a hotspot
		// or calculate the difference between server time and client time as it is done here
		// prepend the hotspot name with some random string anyway
		name: Math.random().toString(36).substring(2) + "_" + ((new Date()).getTime() - ajaxZoom.timeDiff),
		autoTitle: false, // we do not want alt title to be like hotspot name
		autoPos: false, // we know at which positions to put hotspot at (posObj)
		noInit: false, // we want that the hotspot is added right away
		draggable: true, // and it should be draggable
		noRightClickRemove: false, // could be removed with right mouse click
		posObj: posObj, // our coordinates
		settings: {
			shape: "point", // shape is point (not rect)
			altTitle: false, // mouseover title is disabled
			labelGravity: "bottom", // position of the title shown as label
			labelOffsetY: -2, // vertical offset of the label
			hotspotImage: "hotspot64_map_red.png", // default image from /axZm/icons
			gravity: "top", // important - display hotspot image above the click point
			width: 32, // width of the icon
			height: 32, // height of the icon

			// Prevent dragging a hotspot outside of the image bounds
			dragContainment: ajaxZoom.taggingBounds ? "parent" : false,

			/* tooltip settings */
			toolTipWidth: 300, // width of the tooltip
			toolTipHeight: 120, // min-height of the tooltip
			toolTipGravity: "left", // show tooltip left to the hotspot
			toolTipAutoFlip: true, // but also right depending on position
			toolTipAdjustX: 25, // horizontal space between hotspot and toolTip
			toolTipCloseIcon: "close-32-EA0000.png", // close button icon from /axZm/icons
			toolTipCloseIconWidth: 16,
			toolTipCloseIconHeight: 16,
			toolTipCloseIconOffset: {right: 5, top: 5}, // position of the close button
			toolTipOverlayShow: false, // do not show overlayes

			// toolTipTitle can be also a JS function
			toolTipTitle: ajaxZoom.toolTipTitle,

			// toolTipHtml can be also a JS function, which returns something
			toolTipHtml: ajaxZoom.toolTipHtml
		},
		// callback after hotspot is added
		callback: function(info) {
			// we have created the hotspot so update ajaxZoom.myTags
			ajaxZoom.myTags[info.name] = {};

			// save / update console
			ajaxZoom.updateConsole();

			// trigger tooltip after it has been added
			// if you remove the line below the user would need to extra click on the hotspot
			$("#axZmHotspot_"+info.name).trigger("click");
		}
	});

	// important to return false; otherwise AJAX-ZOOM will zoom
	return false;
};

// delete hotspot wrapper
ajaxZoom.deleteHotspot = function(name) {
	// ask if the user wants to delete the hotspot
	var sureDelete = window.confirm(ajaxZoom.taggingTxt.confirmDelete);
	if (sureDelete) {
		// delete hotspot
		$.fn.axZm.deleteHotspot(name);

		// close all tooltips
		$.fn.axZm.removeAllToolTips();
	}
};

// title, which is shown in the popup when the user clicks on the hotspot
ajaxZoom.toolTipTitle = function(info) {
	// we simply return a string but it could be extended
	return ajaxZoom.taggingTxt.notes;
};

// html, which is shown in the popup when the user clicks on the hotspot
ajaxZoom.toolTipHtml = function(info) {
	// get already prsent information sored in ajaxZoom.myTags
	var myTags = ajaxZoom.myTags[info.name] || {},
		ret = ""; // empty string

	// if tagging mode return form fields
	if (ajaxZoom.taggingMode) {
		// simple form
		ret = "";
			// title
			ret += "";

			// description
			ret += "" + (myTags.descr || ajaxZoom.taggingTxt.description) + "";

			// name of the hotspot
			ret += "";

			// save and delete buttons
			ret += "";
				ret += "";
				ret += "";
			ret += "";
		ret += "";

		setTimeout(function() {
			// Prevent bubbling when clicked on the textarea
			$("#azTextArea, #azTextField").on("mousedown touchstart", function(e) {
				e.stopPropagation();
			});

			// Show names of the form fields within formfields and remove them on focus
			$("#azTextField")
			.on("focus", function(e) {
				if ($(this).val() == ajaxZoom.taggingTxt.title) {
					$(this).val("");
				}
			})
			.on("blur", function(e) {
				if ($(this).val() == "") {
					$(this).val(ajaxZoom.taggingTxt.title);
				}
			});

			$("#azTextArea").on("focus", function(e) {
				if ($(this).val() == ajaxZoom.taggingTxt.description) {
					$(this).val("");
				}
			})
			.on("blur", function(e) {
				if ($(this).val() == "") {
					$(this).val(ajaxZoom.taggingTxt.description);
				}
			});
		}, 100);
	}

	// return when tagging mode is disabled
	else {
		// Calculate time from hotspot name (if not changed)
		var time = info.name.split("_")[1];

		// Date from Unix timestamp
		var date = new Date(parseInt(time));

		// Return what is stored in ajaxZoom.myTags.descr
		ret = ""+(myTags.descr || ajaxZoom.taggingTxt.noDescr)+"\
		Created: " + date + "\
		";
	}

	return ret;
};

// store description and title under ajaxZoom.myTags
ajaxZoom.saveInfo = function() {
	// read values from formfields
	var name = $("#azTextHotspotName").val();
	var title = $("#azTextField").val();
	var descr = $("#azTextArea").val();

	// calculate date and time
	var time = (new Date()).getTime() - ajaxZoom.timeDiff;

	// create new / emtty object under ajaxZoom.myTags
	ajaxZoom.myTags[name] = {};

	// store description from formfields
	if (descr != ajaxZoom.taggingTxt.description) {
		ajaxZoom.myTags[name].descr = descr;
		ajaxZoom.myTags[name].lastChanged = time;
	}

	// store title from formfields
	if (title != ajaxZoom.taggingTxt.title) {
		ajaxZoom.myTags[name].title = title;
		ajaxZoom.myTags[name].lastChanged = time;

		// Update label title
		$.axZm.hotspots[name].labelTitle = title;

		// Redraw hotspot with $.fn.axZm.initHotspots and make them draggable again
		$.fn.axZm.initHotspots(null, $.fn.axZm.hotspotsDraggable);
	}

	// close tooltip
	$.fn.axZm.removeAllToolTips();

	// save / update console
	ajaxZoom.updateConsole();
};

// save / update console function
// this is the function, which you would need to extend
ajaxZoom.updateConsole = function() {
	var json = {};

	// eterate over ajaxZoom.myTags and gather information you would like to store
	$.each(ajaxZoom.myTags, function(name, obj) {
		if ($.axZm.hotspots[name]
			&& $.axZm.hotspots[name]["position"]
			&& !$.isEmptyObject($.axZm.hotspots[name]["position"])
		) {
			json[name] = {
				title: obj.title,
				descr: obj.descr,
				timestamp: obj.lastChanged,
				position: $.fn.axZm.convertHotspotPositionToPx($.axZm.hotspots[name]["position"][$.axZm.zoomID])
			};
		}
	});

	// here we simply visually show that created json without any other action
	$("#azTaggingResults").html("" + JSON.stringify(json, null, "\t")+"");

	// todo: for example save it to session and restore when loaded
};

// AJAX-ZOOM callbacks
ajaxZoom.opt = {
	// some (not all) options from /axZm/zoomConfig.inc.php and
	// from /axZm/zoomConfigCustom.inc.php
	// could be set in this "onBeforeStart" callback
	onBeforeStart: function() {
		// Remove hotspot entirely when right clicked on it
		// normally it is only disabled on the current image
		$.axZm.hsRightClickDel = true;

		// Do not zoom out at 100% on click
		$.axZm.zoomOutClick = false;

		// enable and configure the "mNavi" option
		// which is the toolbar below the player or in the player
		$.axZm.mNavi.enabled = true; // enable it
		$.axZm.mNavi.parentID = "azCustomNavi"; // set ID where it has to be appended to
		$.axZm.mNavi.buttonDescr = true; // enable description of the buttons
		$.axZm.mNavi.alt.enabled = false; // disable description simmilar to alt
		$.axZm.mNavi.fullScreenShow = true; // also show "mNavi" at fullscreen mode
		$.axZm.mNavi.offsetVertFS = 10; // vertical offset of mNavi at fullscreen mode

		// this is a list of buttons which we want to show in the toolbar
		// number value is margin to the next button
		$.axZm.mNavi.order = {
			mZoomOut: 5, // zoom out button
			mZoomIn: 20, // zoom in button
			mReset: 20, // reset button
			mPan: 5, // pan mode button
			mCrop: 20, // crop mode button
			mHotspots: 5, // show / hide hotspots button
			mCustomBtn1: 0 // our "whatever" button
		};

		// there can be as many "whatever" (custom) buttons as you want
		// call them mCustomBtn1, mCustomBtn2, ...

		// now we define how this mCustomBtn1 should look like
		$.axZm.icons.mCustomBtn1 = {file: $.axZm.buttonSet + "/" + "button_iPad_tag", ext: "png", w: 50, h: 50};

		// and the title of mCustomBtn1
		$.axZm.mapButTitle.customBtn1 = ajaxZoom.taggingTxt.disable; // description of the button

		// attach a JS function to the mCustomBtn1
		$.axZm.mNavi.mCustomBtn1 = function() {
			// when tagging mode is already on, disable it
			if (ajaxZoom.taggingMode == true) {
				// Update state of the tagging mode
				ajaxZoom.taggingMode = false;

				// Do other things
				ajaxZoom.removeTaggingMsg();
			}
			// enable tagging mode
			else {
				// Update state of the tagging mode
				ajaxZoom.taggingMode = true;

				// Do other things
				ajaxZoom.setTaggingMsg();
			}
		};
	},

	// when image loads
	onLoad: function() {
		// Add message that tagging mode is activated and activate it
		ajaxZoom.setTaggingMsg();
	},

	// callback executed on any hotspot deletion over API
	onHotspotDelete: function(name) {
		// Save / update console
		ajaxZoom.updateConsole();
	},

	// callback triggered after hotspot is moved
	onHotspotsDragEnd: function() {
		// Save / update console
		ajaxZoom.updateConsole();
	},

	// callback triggered when the user clicks on the image
	onZoomInClickStart: function(info) {
		return ajaxZoom.evaluateClick(info);
	}
};

// open AJAX-ZOOM responsive
// Documentation - https://www.ajax-zoom.com/index.php?cid=docs#api_openFullScreen
$(document).ready(function() {
	$.fn.axZm.openResponsive(
		ajaxZoom.path, // Absolute path to AJAX-ZOOM directory, e.g. '/axZm/'
		ajaxZoom.parameter, // Defines, which images and which options set to load
		ajaxZoom.opt, // callbacks
		ajaxZoom.divID, // target - container ID (default 'window' - fullscreen)
		false, // apiFullscreen- use browser fullscreen mode if available
		true, // disableEsc - prevent closing with Esc key
		false // postMode - use POST instead of GET
	);
});

Comments (0)

Leave a Comment

Looking for a place to add a personal image? Visit www.gravatar.com to get Your own gravatar, a globally-recognized avatar. After you're all setup, your personal image will be attached every time you comment.
Load other examples in slider