/** * A closure containing the functions the Add_Img_Maps metadata box. * * @package Add_Img_Maps/admin * @access protected (via closure) * @since 0.1.0 * @returns Array with pseudo-public functions as values. * @param Passed jQuery as an argument to be able to locally refer to it as $. * * @throws Errors that resresent code problems are caught with console * assertions. These are not intrusive (they generate logs rather * than alerts), and appropriate to what should be a fatal error. * * (jQuery elements are prefixed with 'jQ_'). */ var addImgMapsClosure = function($) { 'use strict'; // Not hyphenated because I use hyphens as a separator var pluginClassName = "add_img_maps", // abbreviated plugin name in HTML id's because they get very long pluginIdName = "addimgmaps", // deactivate any code that's ready for a future release ADD_IMG_MAPS_HANDLE_SIZES = false, size_dimensions = { }; // Keep track of sizes (for _HANDLE_SIZES) /** * Gets the attachment width for that imageSize. * * @since 1.0 * @access private * @param {string} [=full] which WP size of the image * (eg "thumbnail") * @returns {int} width in pixels */ function getAttachmentWidth( imageSize ) { if ( undefined === imageSize ) { imageSize = 'full'; } return size_dimensions[imageSize].width; } /** * Gets the attachment height for that imageSize. * * @since 1.0 * @access private * @param {string} [var=full] which Wordpress size of the image * @returns {int} width in pixels */ function getAttachmentHeight( imageSize ) { if ( undefined === imageSize ) { imageSize = 'full'; } return size_dimensions[imageSize].height; } /** * Tells Init functions whether the client can run this. * * Only requirement is support for the "number" input type (absent in IE<9) * * @since 1.0 * @access private * @returns {boool} True or False */ function dependenciesSatisfied( ) { /* Check that we can actually do this */ var test = document.createElement("input"); test.type="number"; return ( test.type==="number"); } /** * Initialises the metabox. * * @since 1.0 * @access public * @returns {none} */ function init( ){ // Fail gracefully if unsupported if ( ! dependenciesSatisfied ) { $( '#' + pluginIdName + '-metabox > .inside').get().innerHTML( addimgmaps_metabox_i18n.please_upgrade ); return; } // Put the canvas over the attachment pages' main image var jQ_attachmentImage = $( '.wp_attachment_image img'); console.assert(jQ_attachmentImage.length == 1, '!=1 main images'); var canvasElement = document.getElementById( pluginIdName + '-canvas' ); // Move the canvas element to be next to the image in the DOM jQ_attachmentImage.get(0).parentElement.appendChild( canvasElement ); // Give it the same dimensions canvasElement.width = jQ_attachmentImage[0].width; canvasElement.height = jQ_attachmentImage[0].height; /* Although the img element itself has no margin, its parent

element does, * and so I move the canvasElement down by as many pixels as the

top offset * to compensate. * * NB: offsetTop is a number; style.top is a CSS element and needs 'px' suffix. */ canvasElement.style.top = jQ_attachmentImage[0].parentElement.offsetTop + 'px'; /* * The canvas will need to be resized when the window resizes. * * @access closure, handling 'resize' function */ $(window).bind('resize', function() { // Same three lines as initial setup canvasElement.width = jQ_attachmentImage[0].width; canvasElement.height = jQ_attachmentImage[0].height; canvasElement.style.top = jQ_attachmentImage[0].parentElement.offsetTop + 'px'; if ( $(canvasElement).is(":visible") ) { // Must redraw. Takes an element in the form for that size. var jQ_fieldSetInUse = $("fieldset.add_img_maps-editmap:visible"); // In the initial state, the canvas element is technically ':visible', // so check fieldset too. if ( jQ_fieldSetInUse.length ) { drawImageMap( jQ_fieldSetInUse.get(0) ); } } }); /* * Import the size_dimensions hash. */ size_dimensions = $('#' + pluginIdName + '-ctrlmaps' ).data('size_dimensions'); console.assert( typeof (size_dimensions) == 'object', size_dimensions, typeof(size_dimensions) ); // Initialise any 'create map' buttons var createMapButtons = $( '#' + pluginIdName + '-cr' ); createMapButtons.click( function() { /** * (anonymous function) Set up form for map for image size. * * @access Closure */ // BACKLLOG: Let user choose between sizes in a pulldown menu var image_size; if ( $(this).data('imagesize') ) { image_size = $(this).data('imagesize'); } else { throw "Cannot find imagesize data attribute."; } // Set up the map for this image size. setupMap( image_size ); }); // Initialise any 'edit map' buttons. var editMapButtons = $( '.' + pluginClassName + '-ed' ); //button includes size on attr editMapButtons.click( function() { /** * (anonymos function) to open existing map for editing * * @access Closure */ var image_size = $(this).data('imagesize'); console.assert( image_size ); //setupMap will open either new or saved map & call openEditMap to make visible. setupMap ( image_size ); } ); } /** * Hides the control panel, unhides the editing area, dims the image, &c. * * Other maintenance includes removing the 'unchanged' flag, if it exists, * redrawing the map. It also switches the display from showing the * control fields, hiding the map fields, and hiding the canvas into the * 'editing' state where the main image is dimmed, the canvas appears * over it, the 'control' form elements are hidden, and the map editing * form elements appear. * * @since 1.0 * @access closure * @var string imageSize * @returns {none} */ function openEditMap ( imageSize ) { $('#addimgmaps-ctrlmaps').hide(); //Grey the main image out a little; as another cue about imagemap being edited. $( 'img.thumbnail' )[0].style.opacity = 0.6; // Show addimgmaps- fieldset var jQ_thisFieldSet = $( 'fieldset#' + pluginIdName + '-' + imageSize ); jQ_thisFieldSet.show(); drawImageMap( jQ_thisFieldSet.get(0) ); // Unset 'unchanged' flag (if any) to show that it's changed now. $( '#' + pluginIdName + '-' + imageSize + '-unchanged').val(0); // ensure the Canvas is visible $( 'canvas#addimgmaps-canvas' ).show(); } /** * Unhides the control panel, hides the editing area, restores the image. * * @since 1.0 * @access private * @var string imageSize * @returns {none} */ function closeEditMap( imageSize ) { // Show the control panel $('#addimgmaps-ctrlmaps').show(); // Hide addimgmaps- fieldset $( 'fieldset#addimgmaps-' + imageSize ).hide(); // Restore the main image. $( 'img.thumbnail' )[0].style.opacity = 1.0; //And hide the canvas $( 'canvas#addimgmaps-canvas' ).hide(); } /** * Initialises the editing area for one size within the metadata box. * * Sets up the canvas, initialises event listeners, optionally draws shape. * * @since 1.0 * @access private * @param {string} [var=full] which Wordpress size to open * @returns {int} width in pixels */ function setupMap( imageSize ) { if ( undefined === imageSize ) { throw 'Called setupMap without imageSize'; } // Find Metabox element for imageSize - part of mapInit var mapForImageSize = $( 'fieldset#' + pluginIdName + "-" + imageSize ).get(0); var savedMap = false; // Check that it's a fieldset console.assert( typeof (mapForImageSize) == 'object', 'Failed to get element ' + pluginIdName + '-' + imageSize ); console.assert( mapForImageSize.tagName == "FIELDSET", mapForImageSize); // Are we loading an existing map? if ( mapForImageSize.hasAttribute('data-map') ) { savedMap = JSON.parse( mapForImageSize.getAttribute('data-map') ); //console.log ( 'Extracted JSON object:' . savedMap ); } // And the remove Map button var rmMapButton = $('', { 'id': pluginIdName + "-" + imageSize + "-rm", 'class': 'button-secondary '+ pluginClassName +'-rm dashicons-before dashicons-trash', 'text' : ' ' + addimgmaps_metabox_i18n.map_button_rm, 'href' : '#', 'click' : function() { /** * (Closure) Wipes editing fieldset & sets 'rm' flag. * * @see onClick */ /* Delete the image map */ $('fieldset#addimgmaps-' + imageSize).empty(); /* set 'rm' flag (unless this is a new map, in which it * neither matters nor exists. */ $( '[name=' + pluginIdName + '-' + imageSize + '-rm]' ).val(1); closeEditMap (imageSize); /* Expected to return ~-ed button (= start/resume editing) */ $( 'a#' + pluginIdName + '-' + imageSize + '-ed' ).text( addimgmaps_metabox_i18n.map_button_rm2ed.replace( '%s', imageSize) ); //'Cancel deletion & re-open "' + imageSize + '" map' ); } } ); // NOT CURRENTLY INCLUDED - WILL BE USED TO _HANDLE_SIZES /* var closeMapButton = $('', { 'id': pluginIdName + "-" + imageSize + "-close", 'class': 'button-secondary addimgmaps-close dashicons-before dashicons-admin-collapse', //addimgmaps_metabox_i18n.map_button_ed2close 'text' : 'Pause editing', 'href' : '#', 'click' : function() { /** * (Closure) to close the editing window without removing it. * * @See Click on 'stop editing' button. */ /* There are state changes to addimgmaps-ctrlmaps * - either a "no map" has become "unsaved new map" * - or existing map has "unsaved changes" * * (A deletion is modelled as a different state entirely.) * // Show control panel, modified $( 'a#' + pluginIdName + '-' + imageSize + '-ed' ).text( 'Re-open editing "' + imageSize + '" map' ); closeEditMap(imageSize); } } ); */ var cancelMapButton = $('', { 'id': pluginIdName + "-" + imageSize + "-close", 'class': 'button-secondary ' + pluginClassName + '-close dashicons-before dashicons-undo', 'text' : ' ' + addimgmaps_metabox_i18n.map_button_close, 'href' : '#', 'click' : function() { /** * (Closure) to cancel the edit, and return to the state on page load. * @See Click on 'stop editing' button. */ /* Delete the fieldset for the editing image map */ $('fieldset#addimgmaps-' + imageSize).empty(); /* set 'unchanged' flag (unless this is a new map, in which it * neither matters nor exists. */ $( '#' + pluginIdName + '-' + imageSize + '-unchanged').val(1); /* Switch back to the control fieldset */ closeEditMap (imageSize); } } ); // CF: Create Map ID is "#addimgmaps-cr" with a value of the imageSize var createAreaButton = $("", { 'id': pluginIdName + "-" + imageSize + "-cr", 'class': 'button-secondary add_img_maps-area-cr ' + 'dashicons-before dashicons-plus-alt', 'text' : ' ' + addimgmaps_metabox_i18n.map_button_cr, 'href' : '#', 'click' : function() { /** * (closure handles event) Add form fields for new area & redraw the canvas. * * @See Click on the "add area" button */ var newArea = createAreaBox( imageSize , nextChildIdNum( mapForImageSize ) ); mapForImageSize.appendChild( newArea ); drawImageMap( mapForImageSize ); } } ); //Append all the buttons, with linkebreak space between $(mapForImageSize).append( rmMapButton, ' ', cancelMapButton, ' ', createAreaButton, ' ' ); // Either set up the input forms for an existing image map ... if ( savedMap ) { var numAreas = savedMap.areas.length; for ( var i = 0; i< numAreas; i++ ) { var area = createAreaBox( imageSize, i, savedMap.areas[i]); $(mapForImageSize).append(area); } // ... or for a new one } else { var firstArea = createAreaBox(imageSize, 0 , "rect"); $(mapForImageSize).append( firstArea ); } openEditMap ( imageSize ); // This is a JScript event, not a JQuery one. mapForImageSize.addEventListener("change", drawImageMap); } /** * Create a div object with the input forms representing a single clickable area * * @since 1.0.0 * @access private * @see createShapeSelect * @see createCoordForRect, createCoordForCircle, appendCoordForPoly * @param {string} imageSize which Wordpress size of the image * @param {int} areaIndex which area we are creating * @param {object} areaObj EITHER array representing existing area * OR {string} OR shape of the clickable area * @returns {object} DIV element containing the input forms for area */ function createAreaBox( imageSize, areaIndex, areaObj ) { var shape; // shape defaults to 'rect'; if ( !areaObj ) { shape ="rect"; } else if ( typeof areaObj == 'string' ) { shape = areaObj; areaObj = null; } else { // Existing area shape = areaObj.shape; } console.assert ( shape=="rect"|| shape=="circle"||shape=="poly", "Invalid shape ", shape); var metaBoxForImageSize = $( 'fieldset#' + pluginIdName + "-" + imageSize ).get(0) ; console.assert( metaBoxForImageSize ); var newArea = document.createElement("div"); var newAreaId = pluginIdName + "-" + imageSize + "-" + areaIndex; newArea.id = newAreaId; newArea.className = pluginClassName + "-area"; newArea.appendChild( createShapeSelect( newArea.id, shape ) ); var deleteButton = document.createElement("a"); deleteButton.className= "button-secondary add_img_maps-area-rm " + "dashicons-before dashicons-dismiss" ; deleteButton.title= addimgmaps_metabox_i18n.area_button_rm; deleteButton.text= addimgmaps_metabox_i18n.area_button_rm; deleteButton.addEventListener("click", function() { /** * Remove this clickable area & redraw (closure) * * @Listens Clicks on the "Delete area" button */ metaBoxForImageSize.removeChild( newArea ); drawImageMap( metaBoxForImageSize ); }); newArea.appendChild( deleteButton); switch ( shape ) { case "rect": newArea.appendChild( createCoordForRect( newArea, areaObj? areaObj.coords : null ) ); break; case "circle": newArea.appendChild( createCoordForCircle( newArea, areaObj? areaObj.coords : null ) ); break; // Poly also needs to add a button for extra co-ordinates. case "poly": var addCoordButton = document.createElement("a"); addCoordButton.className= "button-secondary add_img_maps-addcoord " + "dashicons-before dashicons-plus"; addCoordButton.title= ' ' + addimgmaps_metabox_i18n.area_button_coord; addCoordButton.text= addimgmaps_metabox_i18n.area_button_coord; addCoordButton.addEventListener("click", function() { addCoordPairForPoly( newArea ); drawImageMap( metaBoxForImageSize ); }); newArea.appendChild(addCoordButton); if ( areaObj ) { appendCoordForSavedPoly( newArea, areaObj.coords ); } else { appendCoordForNewPoly( newArea ) ; } break; default: console.assert(false, "Unrecognised shape", shape); } /* Do the link field. */ var newField = document.createElement("input"); newField.type="url"; newField.className="regular-text"; newField.name= newAreaId + '-href'; newField.maxlength=128; newField.size=32; newField.placeholder= addimgmaps_metabox_i18n.area_placehold_href; if ( areaObj ) { newField.value = areaObj.href; } newArea.appendChild( newField ); newArea.appendChild( document.createElement('br')); /* Do the alt text field */ newField = document.createElement("input"); newField.type="text"; newField.name= newAreaId + '-alt'; newField.className="regular-text"; newField.maxlength=128; newField.size=32; newField.placeholder= addimgmaps_metabox_i18n.area_placehold_alt; if ( areaObj ) { newField.value = areaObj.alt; } newArea.appendChild( newField ); return newArea; } /** * Creates the select dropdown box to choose between the clickable area's shape * * @since 1.0 * @access private * @param {string} areaId HTML id of the div of the clickable area * @param {string} shape the shape of the clickable area * @returns {object} A DIV element containing the input forms for that area */ function createShapeSelect ( areaId, shapeValue ) { // console.log( areaId ); var shape = document.createElement("select"); var option = document.createElement("option"); option.text="□ " + addimgmaps_metabox_i18n.shape_rect; option.value="rect"; option.name= areaId + "-shape"; shape.add(option); option = document.createElement("option"); option.text="○ " + addimgmaps_metabox_i18n.shape_circle; option.value="circle"; option.name=areaId + "-shape"; shape.add(option); option = document.createElement("option"); option.text="☆ " + addimgmaps_metabox_i18n.shape_poly; option.value="poly"; option.name=areaId + "-shape"; shape.add(option); // shape.selectedIndex = 0; shape.value = shapeValue; shape.name = areaId + '-shape'; shape.className = pluginClassName + "-shape"; shape.addEventListener("change", function( ) { /** * Recreate the clickable area when its shape is changed (closure) * * @Listens The shape selector changing value. */ var newShapeValue = shape.value; // Our ID follows the rule {plugin}-{imageSize}-{areanum} var idBits = areaId.split("-"); var parentMetaBox = $( 'fieldset#' + idBits[0] + "-" + idBits[1] ).get(0) ; /* * The alt text & URL are passed on to the new area, but not co-ordinates. * Wishlist: translate co-ordinates when shape changes. (So a polygon * turns into a rectangle occupying roughtly the same area). */ //console.log( parentMetaBox, shape, shape.parentNode ); var newAreaBox = createAreaBox( idBits[1], idBits[2], newShapeValue ); var oldAreaBox = parentMetaBox.replaceChild( newAreaBox, shape.parentNode ); // The only field of type TEXT is the ALT field. $( newAreaBox ).children('input:text').val( $( oldAreaBox ).children('input:text').val() ); // Only one with a name field sending in HREF $( newAreaBox ).children('[name$="href"]').val( $( oldAreaBox ).children('[name$="href"]').val() ); } ); return shape; } /* * Create a div element with input boxes for 2 pairs of co-ordinates. * * @access private * @param {DOMObject} areaDiv Div representing area getting co-ords. * @param {array} coordArray (optional) A list of co-ordinates. * * @returns {DOMObject} The div element, ready to be appended. */ function createCoordForRect( areaDiv, coordArray ) { // createNumberInput - -0-x -0-y -1-x -1-y var coordsDiv = document.createElement( "div" ); coordsDiv.id = areaDiv.id + "-co"; var span1 = document.createElement( "span" ); span1.className='add_img_maps-coord-pair'; /** * If no co-ordinates are passed, then put the rectangle * roughly in the middle of the image, but give it a random * offset so that new areas do not automatically superimpose * over each other. */ if( ! coordArray ) { var randomOffset = Math.random(); coordArray = [ getAttachmentWidth() * 0.2 + 0.1*randomOffset, getAttachmentHeight() * 0.2 + 0.1*randomOffset, getAttachmentWidth() * 0.7 + 0.1*randomOffset, getAttachmentHeight() * 0.7 + 0.1*randomOffset, ]; } span1.appendChild ( createNumberInput( areaDiv.id + "-0-x", coordArray[0], getAttachmentWidth() - 1, '→' ) ); span1.appendChild ( createNumberInput( areaDiv.id + "-0-y", coordArray[1], getAttachmentHeight() - 1, '↓' ) ); var span2 = document.createElement( "span" ); span2.className='add_img_maps-coord-pair'; span2.appendChild ( createNumberInput( areaDiv.id + "-1-x", coordArray[2], getAttachmentWidth() - 1, '→' ) ); span2.appendChild ( createNumberInput( areaDiv.id + "-1-y", coordArray[3], getAttachmentHeight() - 1, '↓' ) ); coordsDiv.appendChild( span1); coordsDiv.appendChild( document.createTextNode(' ') ); coordsDiv.appendChild( span2); return coordsDiv; } /* * Create a div element with input boxes for the circle's position & radius. * * @param {DOMObject} areaDiv DOM form element for the circle * @param {array} coordsArray List of co-ordinates (optional) * * @returns {DOMObject} The div form element, ready to be appended. */ function createCoordForCircle(areaDiv, coordsArray ) { // create NumberInput - x, y, r var coordsDiv = document.createElement( "div" ); // Put a new area in the middle, with a random jiggle if ( ! coordsArray ) { var randomOffset = Math.random(); coordsArray = [ getAttachmentWidth() * 0.3 + 0.4*randomOffset, getAttachmentHeight() * 0.3 - 0.4*randomOffset, (randomOffset+0.2)*(getAttachmentHeight()+getAttachmentWidth())/4 ]; } coordsDiv.id = areaDiv.id + "-co"; coordsDiv.appendChild ( createNumberInput( areaDiv.id + "-x", coordsArray[0], getAttachmentWidth() - 1, '→' ) ); coordsDiv.appendChild ( createNumberInput( areaDiv.id + "-y", coordsArray[1], getAttachmentHeight() -1, '↓' ) ); coordsDiv.appendChild ( createNumberInput( areaDiv.id + "-r", coordsArray[2], /* At this maximum, the circle could eclipse the whole area */ (getAttachmentHeight()+getAttachmentWidth())/2, addimgmaps_metabox_i18n.shape_label_r ) ); return coordsDiv; } /* * Append 3 co-ordinate div elements with input boxes for a co-ordinate * pair each. * * Polygons have an arbitrary number of co-ordinates, that can rise or fall. * * Thus these all polygon co-ordinates come with a button to delete them. But * because a polygon needs at least 3 co-ordinate pairs, the delete button is * hidden when the polygon is initially created. * * The other difference to other shapes is the need to append the divs with * the co-ordinate pairs within the function rather than to return them. * * @param {DOMObject} areaDiv DOM form element for the polygon * * @see createCoordPairForPoly * * @returns {boolean} True */ function appendCoordForNewPoly( areaDiv ) { // The polygons have multiple co-ordinate divisions var randomOffset = Math.random(); areaDiv.appendChild( createCoordPairForPoly( areaDiv.id + "-0", getAttachmentWidth() * 0.2 + 0.1*randomOffset, getAttachmentHeight() * 0.3 + 0.1*randomOffset ) ); areaDiv.appendChild( createCoordPairForPoly( areaDiv.id + "-1", getAttachmentWidth() * 0.4 + 0.2*randomOffset, getAttachmentHeight() * 0.75 ) ); areaDiv.appendChild( createCoordPairForPoly( areaDiv.id + "-2", getAttachmentWidth() * 0.75, getAttachmentHeight() * 0.3 - 0.1*randomOffset ) ); // Make sure the delete buttons start off hidden $(areaDiv).find(".add_img_maps-delete-coords").hide(); return true; } /* * Append div elements with input elements for a previously saved polygon area. * * @param {DOMObject} areaDiv DOM form element for the polygon * @param {array} coordsArray Array of the co-ordinates * * @see createCoordPairForPoly * * @returns {boolean} True (*not* an element, unlike similar functions) */ function appendCoordForSavedPoly( areaDiv, coordsArray ) { // The polygons have multiple co-ordinate divisions for (var i = 0; i*2 < coordsArray.length ; i++ ) { areaDiv.appendChild( createCoordPairForPoly( areaDiv.id + "-" + i, coordsArray[2*i], coordsArray[2*i+1] ) ); } // Make sure the delete buttons start off hidden if this is already // a triangle. if ( coordsArray.length == 6 ) { $(areaDiv).find(".add_img_maps-delete-coords").hide(); } return true; } /* * Create a pair of polygon co-ordinates starting at the given dimensions. * * @see appendCoordForSavedPoly, appendCoordForNewPoly, * appendCoordForNewPoly, addCoordPairForPoly * * @param {string} idStem area div id and index of co-ord pair * @param {int} x x co-ordinate. * @param {int} y y co-ordinate. * * @returns {object} DOM object for co-ordinate pair input elements. */ function createCoordPairForPoly( idStem, x, y ) { var coordsDiv = document.createElement( "div" ); coordsDiv.id = idStem; coordsDiv.appendChild ( createNumberInput( idStem + "-x", x, getAttachmentWidth(), '→' ) ); coordsDiv.appendChild ( createNumberInput( idStem + "-y", y, getAttachmentHeight(), '↓' ) ); coordsDiv.className="poly-coords"; // Create a button to delete the co-ordinates var deleteCoords = document.createElement( "a" ); deleteCoords.className= "button-secondary add_img_maps-delete-coords " + "dashicons-before dashicons-no-alt"; deleteCoords.title= addimgmaps_metabox_i18n.shape_coord_rm; deleteCoords.text=" "; /* The dashicon does enough. */ deleteCoords.addEventListener("click", function() { /* * Deletes the co-ordinate pair & makes follow-on changes (closure). * * Delete co-ordinate pair, redraw the image, and hide the buttons if * the polygon has now become a triangle. * * @access closure * @Listens for clicks on the "delete" button by a polygon co-ord pair */ var jQ_areaDiv = $(coordsDiv).closest( "div." + pluginClassName + "-area" ); var jQ_numCoords = jQ_areaDiv.find(".poly-coords").length; // If we are about to hit min # co-ord pairs if (jQ_numCoords <= 4) { jQ_areaDiv.find(".add_img_maps-delete-coords").hide(); } var areaDiv = jQ_areaDiv.get(0); areaDiv.removeChild( coordsDiv ); // Pass drawImageMap the event, allowing it to track down the // calling element that has the data drawImageMap( areaDiv ); }); coordsDiv.appendChild( deleteCoords ); // BTW, this doesn't try to count the number of coords. Done by callers. return coordsDiv; } /** * Add a new polygon co-ordinate pair (or rather their input elements) * * Becase the polygon now has vertices to lose, this makes the 'delete' * button visible. * @Listens to the "add" button on polygon area. * * @see createCoordPairForPoly * * @param {DOMObject} areaDiv DOM form element for the polygon * @returns {DomObject} areaDiv DOM form element with added co-ord pair */ function addCoordPairForPoly ( areaDiv ) { var whichIdNum = nextChildIdNum( areaDiv ), jQ_coords = $( areaDiv).find(".poly-coords"); // A sanity check console.assert( whichIdNum > 2, "Called addCoordPairForPoly with ", areaDiv, "NextChildIdNum returned ", whichIdNum ); jQ_coords.last().after( createCoordPairForPoly( areaDiv.id + "-" + whichIdNum, getAttachmentWidth()*2/whichIdNum, //Will slowly track to the left, starting at 66% getAttachmentHeight()*( 0.1 + 0.2 * (whichIdNum % 2)) // Defaults to an up-down zig-zag ) ); // Make sure all the delete buttons are visible // (In theory, I could set this to only happen if jQ_coords.lenght==3, because that's // the only time it should be needed, but a little robustness won't hurt.) $(areaDiv).find(".add_img_maps-delete-coords").show(); return areaDiv; } /** * Finds the next child index number for an HTML element with countable sub-elements * * This is used both when adding a new area to an imageMap, or a new vertex to a * polygon. It relies on a consistent HTML id convention: a list of categories, * subcategories, and index numbers, connected by hyphens: * addImgMaps-full-0-3 * * Note that the "next index" isn't the same as the number of relevant children, because * some elements could have been deleted from the middle. * * @param {DOMObject} htmlElement The element to search. * @returns {int} index to give to the *next* sub-element */ function nextChildIdNum( htmlElement ) { var lastAreaDiv= $(htmlElement).children("div").get(-1); if ( lastAreaDiv === undefined || lastAreaDiv.tagName.toUpperCase() != "DIV" ) { // console.log ("Looking for lastAreaDiv of ", htmlElement, "Found only ", lastAreaDiv); // console.trace; return 0; } else { var lastId = lastAreaDiv.id; //console.log(lastId); // Find the bit after the last "-" and turn it into a number. var suffix = lastId.substr( lastId.lastIndexOf("-")+1); return parseInt( suffix) + 1; } } /** * Create an Input element for a number. * * Used to set up all co-ordinates. * * @param {string} id HTML id to give the new number input box * @param {int} value Numerical value to give the input box * @param {int} max Max numerical value * * @returns {DOMObject} DOM element of the new numerical input box */ function createNumberInput( numberId, defaultValue, max, labelText ) { var label = document.createElement('label'); label.textContent = labelText; var numberInput = document.createElement("input"); numberInput.type="number"; numberInput.name=numberId; numberInput.id=numberId; numberInput.className="regular-text"; numberInput.min=0; if ( max ) { numberInput.max=max; } numberInput.value = Math.round( defaultValue ); label.appendChild(numberInput); return label; } /** * Redraws the clickable areas on the canvas. * * @param {object} [e] The event that triggered the redraw, OR * The DOM Object that was clicked to trigger the event OR The DOM Object on which the event handler sat * * @returns null */ function drawImageMap( e ) { // For some reason, I can't trust 'this' being a form entry field; it might be a div. // And I certainly can't trust "targetElement" either. var jQ_metaBoxForImageSize, canvas, context, scale; /* If this was triggered by a deletion, then "e.target" (or e) could be a DOMObject * that has already been removed. So we look at e.currentTarget */ if (e.currentTarget) { jQ_metaBoxForImageSize = $(e.currentTarget); } else if (e.hasChildNodes) { jQ_metaBoxForImageSize = $(e); } else { // Else throw fatal error, as this should not happen. throw "drawImageMap called with " + e + " neither event nor DOM ancestor."; } // Find the overall parent of the input form if ( ! jQ_metaBoxForImageSize.is("fieldset") ) { jQ_metaBoxForImageSize = jQ_metaBoxForImageSize.closest("fieldset"); console.assert( jQ_metaBoxForImageSize.length == 1, jQ_metaBoxForImageSize ); } // There's only going to be one canvas canvas = $('#' + pluginIdName +"-canvas")[0]; console.assert ( canvas ); context = canvas.getContext("2d"); context.globalCompositeOperation="xor"; // About to start drawing, so choose this moment to clear the canvas. context.clearRect(0, 0, canvas.width, canvas.height); context.strokeStyle = "black"; context.linewidth = "2em"; context.shadowBlur = "10"; context.shadowColor = "#ff8"; /* * {scale} is canvas/attachment */ scale = Math.min( ( canvas.width / getAttachmentWidth() ), ( canvas.height / getAttachmentHeight() ) ); jQ_metaBoxForImageSize.children("div").each( function(index,element) { // What shape is this? var shapeChooser = $( element ).children("select." + pluginClassName + "-shape"); if ( ! shapeChooser.length ) { // Then this isn't an area div; it's something else. // console.log("Skipping div. Index & element are:", index, element); return null; } console.assert ( shapeChooser.length == 1 , shapeChooser ); // NB: this ignores the id & relies entirely on the input order var x, y, r, coords; // All 3 start with x & y co-ords & a new path. coords = $( element ).find(":input[type=number]"); console.assert ( coords.length > 2, coords ); x = coords[0].value; y = coords[1].value; context.beginPath(); switch( shapeChooser.val() ) { // Both of these involve getting a list of x/y pairs and drawing line between them case "rect": var x2 = coords[2].value; var y2 = coords[3].value; // strokeRect takes width & height, not co-ords context.strokeRect(scale*x, scale*y, scale*(x2-x), scale*(y2-y)); // Doesn't actually use the beginPath / end / stroke sequence, but I put it // outside the switch block just to avoid repitition. break; case "poly": context.moveTo(scale*x,scale*y); coords.splice(0,2); // remove the first pair of co-ords while ( coords.length ) { x = coords[0].value; y = coords[1].value; coords.splice(0,2); // and then remove that pair context.lineTo(scale*x,scale*y); } break; // Circles involve fetching x, y, and r case "circle": r = coords[2].value; context.arc( scale*x, scale*y, scale*r, 0, Math.PI * 2, false); break; default: console.assert ( false, "Unrecognised shape", shapeChooser ); } // End switch context.closePath(); // Still need to fill it in context.stroke(); } // end of Each closer ); // end of fxn } // end function drawImageMap return { init: init }; }( jQuery ); // closureDefined // wait till all loaded & call the init method within the closure jQuery(document).ready( function() { addImgMapsClosure.init(); // now handled by button } ); /** * Extant issues: * * Currently none. * */