|
The article provides valuable insight into developing advanced SiteSupra applications using custom editors.
The examples are taken from Kalpaka Park Residence website,
developed by Vide Infra Grupa.
Picture Mapper is a custom application that allows linking a file to a region on the bitmap image.
In the described example the linked file is another image that illustrates the region of an apartment plan,
so that the application allows adhering apartment images to its plan.
|
 Front End
|
 Back End
|
|
"Picture Mapper" is a custom editor for a SiteSupra custom property, and before reviewing it we have to know how to create a custom editor.
Custom editor in SiteSupra is Web Dialog which is called on custom property edit. Custom editor is PHP script which defines obligatory variable called $dialogData,
which defines dialog properties like position, size, caching, and resizing. All output of script is HTML text shown in dialog.
To exchange data between PHP and JavaScript you can use getData/sendData in PHP and sendData in JavaScript, how to use them I will describe below.
Main template for dialog in SiteSupra:
<? if(!defined('SU')) die(); // check for supra $dialogData=Array( 'title' =>__('Dialog title'), //dialog title 'width'=>420, //dialog width 'height'=>350, //dialog height 'resize'=>1, //1 resizable, 0 not resizable 'cache'=>0, //is going to be cached? 'debug'=>1, //does debug info available ); if(getData()) // if we have received request from JavaScript { switch($what) // get type of request { case 'type1': //action for type1; sendData(/*value to send*/); case 'type2': //action for type2; break; //.... } sendData(0); } ?>
<script language=JavaScript>
function init() // function which runs when dialog started
{
//init code
}
// your JavaScript functions
</script>
<!-- dialog HTML data here -->
Function init will be called after dialog is shown. So you can define this function to initialize dialog.
To close dialog and to save data you should call dialogOk() function, and not saving, call dialogCancel() function using
object's onclick attribute or call these functions from another function.
sendData function in JavaScript has 2 parameters, 1st is request type (e.g. 'type1'), 2nd is request data.
In php sendData has only 1 parameter - data, that sends data to the JavaScript. Use suDialog function to call
sub dialog, its parameters are: 1st is dialog URL, 2nd is dialog's parameters. The function returns value passed by returnValue.
You can use suDialog to call standard supra dialogs, these are 'edhtml' (HTML Editor), 'edtext' (Text Editor), 'edfile' (File dialog),
'edcolor' (Color selection dialog), 'site' (Site Map), 'eddate' (Calendar). For standard dialog parameters please refer to SiteSupra manuals.
Ok() function must be defined if you want to return and save data for a custom property. Use returnValue variable to return data to be saved.
If you want to change button text after you custom editor has been closed, make returnValue an associative array and specify button key's value.
That will be the button's text. That was about how to return values, now let's go to dialog parameters. When dialog runs argv variable is available, so argv.data is custom property's value.
conf file to use this custom property:
<? $cache = 1; $info = 'Block with custom property'; $properties = array ( 'customProp' => array ( 'label' => 'Custom Property', 'type' => 'custom', 'value' => array('button' => 'Edit', 'data' => array()), 'file' => 'customprop.php' // editor, like template above ), // ... ); ?>
It creates property like this:

Picture mapper written for Kalpaka7 uses VML to draw mapped areas.
I use these two simple functions to calculate absolute coordinates of the element from the beginning of the document. It sums all offsets of parent elements and its own.
function calcOffsetX(el) {
offset = el.offsetLeft;
while (el.offsetParent)
{
el = el.offsetParent;
offset += el.offsetLeft;
}
return offset;
}
function calcOffsetY(el) {
offset = el.offsetTop;
while (el.offsetParent)
{
el = el.offsetParent;
offset += el.offsetTop;
}
return offset;
}
This function is written to convert from array of coordinates to string:
function getCoords(coords) {
var points = '';
var i,max;
if (coords) {
for (i = 0, max = coords.length; i < max; i++) {
points += coords[i].x+","+coords[i].y+" ";
}
points += coords[0].x + "," + coords[0].y;
}
return points;
}
Here is a function which displays regions using absolute position, it places drawings in div element with id=vvId. Coordinates are counted relative to picture (which is divided into regions) position in document.
function updateMap(data) {
var i,max;
var html, el, element;
html = '';
if (data) {
for (i = 0, max = data.length; i < max; i++) {
points = getCoords(data[i].coords);
element = document.getElementById('planFile');
html += '<v:polyline id="vvId'+i+'" fillcolor="' + colors[i] + '" strokecolor="' + colors[i] + '" points="' +
points + '" style="position: absolute; left: ' +
calcOffsetX(element) + '; top: ' +
calcOffsetY(element) + ';"/>';
}
}
el = document.getElementById("vvId");
el.innerHTML = html;
}
You must specify this in CSS whether inline or file to have VML working v\:* { behavior: url(#default#VML); }
This must be in editor file to be able to use VML
<XML:NAMESPACE NS="urn:schemas-microsoft-com:vml" PREFIX="v" /> For more information about VML refer to MSDN
This functions deletes points in range, cx, cy center, rx, ry maximum distance from center for points to be deleted (using rectangle, not circle).
function deletePoints(cx, cy, rx, ry) {
var i, max, coor;
coor = [];
point = 0;
for (i = 0, max = coords.length; i < max; i++)
if ((coords[i].x < cx - rx) || (coords[i].x > cx + rx)
|| (coords[i].y < cy - ry) || (coords[i].y > cy + ry)) {
coor[coor.length] = coords[i];
point++;
}
curPoint = -1;
coords = coor;
}
This function gets first point in range using method used in deletePoints
function getCurPoint(cx, cy, rx, ry) {
var i, max;
for (i = 0, max = coords.length; i < max; i++)
if ((coords[i].x >= cx - rx) && (coords[i].x <= cx + rx)
&& (coords[i].y >= cy - ry) && (coords[i].y <= cy + ry)) {
curPoint = i;
break;
}
}
This function adds/deletes/gets point at clicked location
function addPoint(evt) {
var el, x, y, elX, elY, elem;
if (!evt) evt = event;
el = document.getElementById("planFile");
x = evt.clientX;
y = evt.clientY;
x += document.body.scrollLeft;
y += document.body.scrollTop;
x -= elX = calcOffsetX(el);
y -= elY = calcOffsetY(el);
x--;
y--;
switch(drawModeOption) {
case 0:
if (!coords) {
coords = [];
}
insertPointInArray(curPoint, x, y);
break;
case 1:
deletePoints(x,y,2,2);
break;
case 2:
getCurPoint(x,y,2,2);
}
elem = document.getElementById("vvId");
elem.style.left = elX;
elem.style.top = elY;
elem.points.value = getCoords(coords);
drawPoints(coords);
}
This function draw points using insertPoint function. It uses vml oval shape.
function drawPoints(coord) {
var element, i, color;
element = document.getElementById("zonePoints");
element.innerHTML = '';
for (i = 0, max = coord.length; i < max; i++) {
color = (curPoint == i) ? '#00FF00' : '#0000FF';
insertPoint(element, coord[i].x, coord[i].y, color, color, 1);
}
}
function insertPoint(element1, x, y, color, colorfill, filled) {
html = '<v:oval style="position: absolute;left: ' + (x - 1 + calcOffsetX(element = document.getElementById('planFile'))) +
'; top: '+ (y - 1 + calcOffsetY(element)) + ';width: 3; height: 3;cursor: crosshair;" strokecolor="' + color +
'" fillcolor="' + colorfill + '" filled="' + filled +'" onMouseDown="addPoint(event);" />';
element1.innerHTML += html;
}
After draw points function we get result similar to this (see blue and green dots connected with red lines, green dot is currently selected dot).

Finally block.html fragment.
Generating array of coordinates and links using pictures entered in piceditor. This function returns HTML of <map>, which maps regions with our pictures.
<? function generateMap($pictures) { $html = ' <map name="photomap">'; $areaArray = array(); if (!count($pictures)) return ''; $photos = array(); foreach($pictures as $id => $pic) { if (file_exists(suROOT . $pic['file'])) { $imageSize = getimagesize(suROOT . $pic['file']); } else { $imageSize = array(0, 0); } $coords = array(); $xmin = $xmax = $pic['coords'][0]['x']; $ymax = $ymin = $pic['coords'][0]['y']; foreach($pic['coords'] as $index => $data) { $xmin = $data['x'] < $xmin ? $data['x'] : $xmin; $xmax = $data['x'] > $xmax ? $data['x'] : $xmax; $ymin = $data['y'] < $ymin ? $data['y'] : $ymin; $ymax = $data['y'] > $ymax ? $data['y'] : $ymax; $coords[] = $data['x'] . ', ' . $data['y']; } $photos[] = '<img src="/pic/floor/app/foto.jpg" alt="" style="position: absolute; left: ' . (($xmax + $xmin) / 2 - 6) . 'px; top: ' . (($ymax + $ymin) / 2 - 6) . 'px;" />'; $html .= ' <AREA href="javascript:void();" onclick="openPicture('' . $pic['file'] . '', ' . $imageSize[0] . ', ' . $imageSize[1] . ');" shape="poly" coords="' . join(', ', $coords) . '" onmouseover="high(document.getElementById('img' . $id . ''))" onmouseout="low(document.getElementById('img' . $id . ''))" />'; } $html .= '</map>'; $html .= '<div id="photoDiv">' . join('', $photos) . '</div>'; return $html; } ?>
openPicture function here is function which somehow shows your full image (eg. opens popup window with image).
Sets camera image in the center of each mapped area
function setPics()
{
var el = document.getElementById("photodiv"), el2 = document.getElementById("divplan");
el = el.getElementsByTagName("img");
var x = calcOffsetX(el2), y = calcOffsetY(el2);
for (var i = 0, max = el.length; i < max; i++)
{
el[i].style.left = (parseInt(el[i].style.left) + x);
el[i].style.top = (parseInt(el[i].style.top) + y);
}
}
Result which this function does is following (do you see placed camera image here?):

For this to work you must set id for the image you mapped regions on to "divplan". That is
<img id="divplan" border="0" src="/your/image" border="0" usemap="#photomap" />
If you have list of this images' thumbnails make list of them and assign ids in form "img{idnumber}" where {idnumber} is image number in array of pictures (that is in custom property). Best choice to do this is
<? foreach($picture as $id => $picval) { $html[] = ' <td><img border="0" id="img' . $id . '" src="'.$picval['thumb'].'" width="92" height="116" onClick="window.execScript(document.getElementById('area' . $id . '').onclick.toString().match(/(?:[{])([^}]*)/)[1]);" style="filter:alpha(opacity=30);-moz-opacity:0.3;" onMouseOver="high(this);" onMouseOut="low(this);" style="cursor: ' . (strstr($_SERVER['HTTP_USER_AGENT'],'MSIE') ? 'hand' : 'pointer') . ';"></td>'; } ?>
Don't forget to use window.onload event to call setPics. Not doing this will not show images of camera over mapped areas.
To do this use line window.onload = setPics;
You can see that i use low and high, these are two JavaScript functions which do highliting of image thumbnail (for example you change opacity or border color).
|