JCrop selection on image problem in IE - asp.net

I am using JCrop to crop an image after upload with Ajax upload
control in my web application. It works great on Chrome, Firefox but
not in IE. I use JCrop v0.9.12 (build: 20130202) and IE
v10.0.9.9200.16635. Problem is JCrop selection doesn't work in IE.
Thanks!
Here is my scripts.
<script type="text/javascript">
jQuery(document).ready
(function ($) {
// To hold the API and image size.
var jcrop_api, boundx, boundy;
$('#<%=imgCrop.ClientID%>').Jcrop (
{ // img_crop is the ID of image control
onChange: updatePreview, // will display the selected img on change.
onSelect: updatePreview, // will display the selected img Img_preview
onSelect: storeCoords, // will tell the coordinates
aspectRatio: 11 / 15
}, function ()
{
jcrop_api = this;
var bounds = this.getBounds();
boundx = bounds[0];
boundy = bounds[1];
}
);
function updatePreview(c) {
if (parseInt(c.w) > 0) {
var rx = 100 / c.w;
var ry = 100 / c.h;
$('#<%=Img_preview.ClientID%>').css({ //Img_preview is the ID of image control
width: Math.round(rx * boundx) + 'px',
height: Math.round(ry * boundy) + 'px',
marginLeft: '-' + Math.round(rx * c.x) + 'px',
marginTop: '-' + Math.round(ry * c.y) + 'px'
});
}
};
});
// will store the selected part the images coordinates
function storeCoords(c) {
jQuery('#<%=W.ClientID%>').val(c.w);
jQuery('#<%=H.ClientID%>').val(c.h);
jQuery('#<%=X.ClientID%>').val(c.x);
jQuery('#<%=Y.ClientID%>').val(c.y);
};
</script>

A common fix for IE is to do
$.Jcrop('#<%=imgCrop.ClientID%>', {
// your inits
});

Related

Visjs timeline edit content item template

I'd like to be able to edit the content attribute of visjs timeline items in the timeline itself. However, when I use an input as part of a template, it doesn't appear to receive any mouse events; I can't click in it and type anything, and clicking buttons doesn't work, either. Buttons appear to get the mouseover event, though:
function test(item) {
alert('clicked');
}
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div onClick="test"><input value="click in the middle"></input><button onClick="test">test</button></div>'
}
};
/* create timeline */
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) properties.event.target.focus();
});
https://codepen.io/barticula/pen/EpWJKd
Edit: Code above CodePen example have been updated to use the click event to focus on the input, but all other normal mouse behavior is missing. Keyboard events appear to function normally.
To get a reaction with a click on a timeline element, you can use the library's own events (see events on doc and this exemple on website).
On your example, you could do something like this among other possible solutions in pure javascript including...
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div id="test-div"><input placeholder="hey" type="text" id="inputTest" ><button id="test-button">test</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) alert('click on' + target.id);
});
UPDATED
It is difficult to know exactly what you want to do because there are several possible solutions anyway.
Eventually, I propose another snippet below and a codepen updated.... but will it meet your need, not sure ?
2nd UPDATE (for another work track, see comments)
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div><input placeholder="edit me..." type="text"></input><button>send value</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function(properties) {
var target = properties.event.target;
var item = items.get(properties.item);
console.log(properties.event);
// if (properties.item && target.tagName === "DIV") focusMethod(target);
if (properties.item && target.tagName === "INPUT") target.focus();
if (properties.item && target.tagName === "BUTTON") getInputValue(item, target);
});
focusMethod = function getFocus(target) {
// target.insertAfter("BUTTON");
target.firstChild.focus();
}
getInputValue = function getValue(item, target) {
target.focus();
var inputValue = (target.parentNode.firstChild.value) ? target.parentNode.firstChild.value : "no value entered ";
alert("Input value : " + inputValue + " => send by: " + item.content)
}

Rendering from two cameras at the same time in A-Frame

the recent v0.3.0 blog post mentions WebVR 1.0 support allowing "us to have different content on the desktop display than the headset, opening the door for asynchronous gameplay and spectator modes." This is precisely what I'm trying to get working. I'm looking to have one camera in the scene represent the viewpoint of the HMD and a secondary camera represent a spectator of the same scene and render that view to a canvas on the same webpage. 0.3.0 removes the ability to render a-scene to a specific canvas in favor of embedded component. Any thoughts on how to accomplish two cameras rendering a single scene simultaneously?
My intention is to have a the desktop display show what a user is doing from a different perspective. My end goal is to be able to build a mixed reality green screen component.
While there may be a better or cleaner way to do this in the future, I was able to get a second camera rendering by looking at examples of how this is done in the THREE.js world.
I add a component to a non-active camera called spectator. in the init function I set up a new renderer and attach to div outside the scene to create a new canvas. I then call the render method inside the tick() part of the lifecycle.
I have not worked out how to isolate the movement of this camera yet. The default look controls of the 0.3.0 aframe scene still control both camera
Source code:
https://gist.github.com/derickson/334a48eb1f53f6891c59a2c137c180fa
I've created a set of components that can help with this. https://github.com/diarmidmackenzie/aframe-multi-camera
Here's an example showing usage with A-Frame 1.2.0 to display the main camera on the left half of the screen, and a secondary camera on the right half.
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/diarmidmackenzie/aframe-multi-camera#latest/src/multi-camera.min.js"></script>
</head>
<body>
<div>
<a-scene>
<a-entity camera look-controls wasd-controls position="0 1.6 0">
<!-- first secondary camera is a child of the main camera, so that it always has the same position / rotation -->
<!-- replace main camera (since main camera is rendered across the whole screen, which we don't want) -->
<a-entity
id="camera1"
secondary-camera="outputElement:#viewport1;sequence: replace"
>
</a-entity>
</a-entity>
<!-- PUT YOUR SCENE CONTENT HERE-->
<!-- position of 2nd secondary camera-->
<a-entity
id="camera2"
secondary-camera="outputElement:#viewport2"
position="8 1.6 -6"
rotation="0 90 0"
>
</a-entity>
</a-scene>
</div>
<!-- standard HTML to contrl layout of the two viewports-->
<div style="width: 100%; height:100%; display: flex">
<div id="viewport1" style="width: 50%; height:100%"></div>
<div id="viewport2" style="width: 50%; height:100%"></div>
</div>
</body>
</html>
Also here as a glitch: https://glitch.com/edit/#!/recondite-polar-hyssop
It's also been suggested that I post the entire source code for the multi-camera component here.
Here it is...
/* System that supports capture of the the main A-Frame render() call
by add-render-call */
AFRAME.registerSystem('add-render-call', {
init() {
this.render = this.render.bind(this);
this.originalRender = this.el.sceneEl.renderer.render;
this.el.sceneEl.renderer.render = this.render;
this.el.sceneEl.renderer.autoClear = false;
this.preRenderCalls = [];
this.postRenderCalls = [];
this.suppresssDefaultRenderCount = 0;
},
addPreRenderCall(render) {
this.preRenderCalls.push(render)
},
removePreRenderCall(render) {
const index = this.preRenderCalls.indexOf(render);
if (index > -1) {
this.preRenderCalls.splice(index, 1);
}
},
addPostRenderCall(render) {
this.postRenderCalls.push(render)
},
removePostRenderCall(render) {
const index = this.postRenderCalls.indexOf(render);
if (index > -1) {
this.postRenderCalls.splice(index, 1);
}
else {
console.warn("Unexpected failure to remove render call")
}
},
suppressOriginalRender() {
this.suppresssDefaultRenderCount++;
},
unsuppressOriginalRender() {
this.suppresssDefaultRenderCount--;
if (this.suppresssDefaultRenderCount < 0) {
console.warn("Unexpected unsuppression of original render")
this.suppresssDefaultRenderCount = 0;
}
},
render(scene, camera) {
renderer = this.el.sceneEl.renderer
// set up THREE.js stats to correctly count across all render calls.
renderer.info.autoReset = false;
renderer.info.reset();
this.preRenderCalls.forEach((f) => f());
if (this.suppresssDefaultRenderCount <= 0) {
this.originalRender.call(renderer, scene, camera)
}
this.postRenderCalls.forEach((f) => f());
}
});
/* Component that captures the main A-Frame render() call
and adds an additional render call.
Must specify an entity and component that expose a function call render(). */
AFRAME.registerComponent('add-render-call', {
multiple: true,
schema: {
entity: {type: 'selector'},
componentName: {type: 'string'},
sequence: {type: 'string', oneOf: ['before', 'after', 'replace'], default: 'after'}
},
init() {
this.invokeRender = this.invokeRender.bind(this);
},
update(oldData) {
// first clean up any old settings.
this.removeSettings(oldData)
// now add new settings.
if (this.data.sequence === "before") {
this.system.addPreRenderCall(this.invokeRender)
}
if (this.data.sequence === "replace") {
this.system.suppressOriginalRender()
}
if (this.data.sequence === "after" ||
this.data.sequence === "replace")
{
this.system.addPostRenderCall(this.invokeRender)
}
},
remove() {
this.removeSettings(this.data)
},
removeSettings(data) {
if (data.sequence === "before") {
this.system.removePreRenderCall(this.invokeRender)
}
if (data.sequence === "replace") {
this.system.unsuppressOriginalRender()
}
if (data.sequence === "after" ||
data.sequence === "replace")
{
this.system.removePostRenderCall(this.invokeRender)
}
},
invokeRender()
{
const componentName = this.data.componentName;
if ((this.data.entity) &&
(this.data.entity.components[componentName])) {
this.data.entity.components[componentName].render(this.el.sceneEl.renderer, this.system.originalRender);
}
}
});
/* Component to set layers via HTML attribute. */
AFRAME.registerComponent('layers', {
schema : {type: 'number', default: 0},
init: function() {
setObjectLayer = function(object, layer) {
if (!object.el ||
!object.el.hasAttribute('keep-default-layer')) {
object.layers.set(layer);
}
object.children.forEach(o => setObjectLayer(o, layer));
}
this.el.addEventListener("loaded", () => {
setObjectLayer(this.el.object3D, this.data);
});
if (this.el.hasAttribute('text')) {
this.el.addEventListener("textfontset", () => {
setObjectLayer(this.el.object3D, this.data);
});
}
}
});
/* This component has code in common with viewpoint-selector-renderer
However it's a completely generic stripped-down version, which
just delivers the 2nd camera function.
i.e. it is missing:
- The positioning of the viewpoint-selector entity.
- The cursor / raycaster elements.
*/
AFRAME.registerComponent('secondary-camera', {
schema: {
output: {type: 'string', oneOf: ['screen', 'plane'], default: 'screen'},
outputElement: {type: 'selector'},
cameraType: {type: 'string', oneOf: ['perspective, orthographic'], default: 'perspective'},
sequence: {type: 'string', oneOf: ['before', 'after', 'replace'], default: 'after'},
quality: {type: 'string', oneOf: ['high, low'], default: 'high'}
},
init() {
if (!this.el.id) {
console.error("No id specified on entity. secondary-camera only works on entities with an id")
}
this.savedViewport = new THREE.Vector4();
this.sceneInfo = this.prepareScene();
this.activeRenderTarget = 0;
// add the render call to the scene
this.el.sceneEl.setAttribute(`add-render-call__${this.el.id}`,
{entity: `#${this.el.id}`,
componentName: "secondary-camera",
sequence: this.data.sequence});
// if there is a cursor on this entity, set it up to read this camera.
if (this.el.hasAttribute('cursor')) {
this.el.setAttribute("cursor", "canvas: user; camera: user");
this.el.addEventListener('loaded', () => {
this.el.components['raycaster'].raycaster.layers.mask = this.el.object3D.layers.mask;
const cursor = this.el.components['cursor'];
cursor.removeEventListeners();
cursor.camera = this.camera;
cursor.canvas = this.data.outputElement;
cursor.canvasBounds = cursor.canvas.getBoundingClientRect();
cursor.addEventListeners();
cursor.updateMouseEventListeners();
});
}
if (this.data.output === 'plane') {
if (!this.data.outputElement.hasLoaded) {
this.data.outputElement.addEventListener("loaded", () => {
this.configureCameraToPlane()
});
} else {
this.configureCameraToPlane()
}
}
},
configureCameraToPlane() {
const object = this.data.outputElement.getObject3D('mesh');
function nearestPowerOf2(n) {
return 1 << 31 - Math.clz32(n);
}
// 2 * nearest power of 2 gives a nice look, but at a perf cost.
const factor = (this.data.quality === 'high') ? 2 : 1;
const width = factor * nearestPowerOf2(window.innerWidth * window.devicePixelRatio);
const height = factor * nearestPowerOf2(window.innerHeight * window.devicePixelRatio);
function newRenderTarget() {
const target = new THREE.WebGLRenderTarget(width,
height,
{
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
stencilBuffer: false,
generateMipmaps: false
});
return target;
}
// We use 2 render targets, and alternate each frame, so that we are
// never rendering to a target that is actually in front of the camera.
this.renderTargets = [newRenderTarget(),
newRenderTarget()]
this.camera.aspect = object.geometry.parameters.width /
object.geometry.parameters.height;
},
remove() {
this.el.sceneEl.removeAttribute(`add-render-call__${this.el.id}`);
if (this.renderTargets) {
this.renderTargets[0].dispose();
this.renderTargets[1].dispose();
}
// "Remove" code does not tidy up adjustments made to cursor component.
// rarely necessary as cursor is typically put in place at the same time
// as the secondary camera, and so will be disposed of at the same time.
},
prepareScene() {
this.scene = this.el.sceneEl.object3D;
const width = 2;
const height = 2;
if (this.data.cameraType === "orthographic") {
this.camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
}
else {
this.camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000);
}
this.scene.add(this.camera);
return;
},
render(renderer, renderFunction) {
// don't bother rendering to screen in VR mode.
if (this.data.output === "screen" && this.el.sceneEl.is('vr-mode')) return;
var elemRect;
if (this.data.output === "screen") {
const elem = this.data.outputElement;
// get the viewport relative position of this element
elemRect = elem.getBoundingClientRect();
this.camera.aspect = elemRect.width / elemRect.height;
}
// Camera position & layers match this entity.
this.el.object3D.getWorldPosition(this.camera.position);
this.el.object3D.getWorldQuaternion(this.camera.quaternion);
this.camera.layers.mask = this.el.object3D.layers.mask;
this.camera.updateProjectionMatrix();
if (this.data.output === "screen") {
// "bottom" position is relative to the whole viewport, not just the canvas.
// We need to turn this into a distance from the bottom of the canvas.
// We need to consider the header bar above the canvas, and the size of the canvas.
const mainRect = renderer.domElement.getBoundingClientRect();
renderer.getViewport(this.savedViewport);
renderer.setViewport(elemRect.left - mainRect.left,
mainRect.bottom - elemRect.bottom,
elemRect.width,
elemRect.height);
renderFunction.call(renderer, this.scene, this.camera);
renderer.setViewport(this.savedViewport);
}
else {
// target === "plane"
// store off current renderer properties so that they can be restored.
const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
// temporarily override renderer proeperties for rendering to a texture.
renderer.xr.enabled = false; // Avoid camera modification
renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
const renderTarget = this.renderTargets[this.activeRenderTarget];
renderTarget.texture.encoding = renderer.outputEncoding;
renderer.setRenderTarget(renderTarget);
renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897
renderer.clear();
renderFunction.call(renderer, this.scene, this.camera);
this.data.outputElement.getObject3D('mesh').material.map = renderTarget.texture;
// restore original renderer settings.
renderer.setRenderTarget(currentRenderTarget);
renderer.xr.enabled = currentXrEnabled;
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
this.activeRenderTarget = 1 - this.activeRenderTarget;
}
}
});

Asynchronous update of nodes properties after the graph is rendered (Vivagraph.js)

I am trying to populate nodes' data in a graph, asynchronously.
How to ensure that data fetched asyncrosly is actually bound to the graph, and rendered when ready?
First, you render the graph structure, node and links.
Second, you render data as nodes' properties, when data is ready.
New node can by dynamically added by interacting on parents' nodes, and don't want to wait for completion of node's properties.
Please note I am using Vivagraph.js library.graph is an object created with the library, addLinks() and getNode() are its function properties - see the demo at Vivagraph demo, I am using that as a draft of my attempts.
The issue I experience is that nodes are rendered in the graph as soon as they are added - addNode() either addLinks(node1, node2) functions -, while node's properties fetched asynchronously - getNode(node).property = updatedValue - result undefined.
EDITED - Simplified code based on comment
Below I include a working mockup version, based on tutorial provided by #Anvaka, the author of this (awesome) library.
My goal is to render the graph immediately, enabling interaction, and update data while it is being fetched from third parties.
// attempt 1: fetch data async
var fetchInfo = function (graph, nodeId) {
var root = 'http://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/photos/' + nodeId,
method: 'GET'
}).then(function (data) {
graph.getNode(nodeId).data = data.thumbnailUrl;
console.log(graph.getNode(nodeId));
});
};
// attempt 2: defer ajax directly
var fetchInfo_2 = function (graph, nodeId) {
var root = 'http://jsonplaceholder.typicode.com';
return $.ajax({
url: root + '/photos/' + nodeId,
method: 'GET'
});
};
function main() {
// As in previous steps, we create a basic structure of a graph:
var graph = Viva.Graph.graph();
graph.addLink(1, 2);
fetchInfo(graph, 1); // updated data is undefined when graph is rendered
fetchInfo(graph, 2); // updated data is undefined when graph is rendered
/* trying a different outcome by deferring whole ajax
graph.getNode(1).data = fetchInfo_2(1).done(function(data) {
data.thumbnailUrl;
}); // the whole object is deferred but cannot fetch data
graph.getNode(2).data = fetchInfo_2(2).done(function(data) {
data.thumbnailUrl;
}); // the whole object is deferred but cannot fetch data
*/
var graphics = Viva.Graph.View.svgGraphics(),
nodeSize = 24,
addRelatedNodes = function (nodeId, isOn) {
for (var i = 0; i < 6; ++i) {
var child = Math.floor((Math.random() * 150) + nodeId);
// I add children and update data from external sources
graph.addLink(nodeId, child);
fetchInfo(graph, child);
}
};
// dynamically add nodes on mouse interaction
graphics.node(function (node) {
var ui = Viva.Graph.svg('image')
.attr('width', nodeSize)
.attr('height', nodeSize)
.link(node.data);
console.log('rendered', node.id, node.data);
$(ui).hover(function () {
// nodes are rendered; nodes' data is undefined
addRelatedNodes(node.id);
});
return ui;
}).placeNode(function (nodeUI, pos) {
nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2);
});
graphics.link(function (link) {
return Viva.Graph.svg('path')
.attr('stroke', 'gray');
}).placeLink(function (linkUI, fromPos, toPos) {
var data = 'M' + fromPos.x + ',' + fromPos.y +
'L' + toPos.x + ',' + toPos.y;
linkUI.attr("d", data);
})
var renderer = Viva.Graph.View.renderer(graph, {
graphics: graphics
});
renderer.run();
}
main();
svg {
width: 100%;
height: 100%;
}
<script src="https://rawgit.com/anvaka/VivaGraphJS/master/dist/vivagraph.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
As I said in the comments, any work you want to do as a result of an asynchronous process must be done in the callback. This includes any rendering work.
For jQuery's deferreds (like the result of an Ajax call), the callback is defined through .then or .done, enabling you to detach the act of fetching a resource from working with that resource.
Setting the image source is rendering work, so you must do it in the callback. Below is a function that fetches images and returns the deferred result and the node callback function uses that to do its own piece of work.
function fetchInfo(id) {
var root = 'http://jsonplaceholder.typicode.com';
return $.getJSON(root + '/photos/' + id);
}
function main() {
var graph = Viva.Graph.graph(),
graphics = Viva.Graph.View.svgGraphics(),
nodeSize = 24,
addRelatedNodes = function (nodeId, count) {
var childId, i;
for (i = 0; i < count; ++i) {
childId = Math.floor(Math.random() * 150) + nodeId;
graph.addLink(nodeId, childId);
}
};
graphics
.node(function (node) {
var ui = Viva.Graph.svg('image')
.attr('width', nodeSize)
.attr('height', nodeSize)
.link('http://www.ajaxload.info/images/exemples/24.gif');
$(ui).dblclick(function () {
addRelatedNodes(node.id, 6);
});
// render when ready
fetchInfo(node.id).done(function (data) {
ui.link(data.thumbnailUrl);
});
return ui;
})
.placeNode(function (nodeUI, pos) {
nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2);
})
.link(function (link) {
return Viva.Graph.svg('path')
.attr('stroke', 'gray');
})
.placeLink(function (linkUI, fromPos, toPos) {
var data =
'M' + fromPos.x + ',' + fromPos.y +
'L' + toPos.x + ',' + toPos.y;
linkUI.attr("d", data);
});
graph.addLink(1, 2);
Viva.Graph.View.renderer(graph, {
graphics: graphics
}).run();
}
main();
svg {
width: 100%;
height: 100%;
}
img {
cursor: pointer;
}
<script src="https://rawgit.com/anvaka/VivaGraphJS/master/dist/vivagraph.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
Sadly, it does not seem to run in the StackSnippets, but it works over on jsFiddle: http://jsfiddle.net/tL9992ua/

How can see local images (put in assets folder)?

I can't manage to see my assets pictures in this ChessBoard view, though the navigator console doesn't show me any error :
enyo.kind({
name: "PositionView",
published: {
fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
cellsize: 32,
color: "#CCC"
},
fit: true,
components: [
{
name: "canvas",
kind: "enyo.Canvas",
attributes: {width: 10, height: 10}
}
],
create: function() {
this.inherited(arguments);
},
rendered:function(){
this.inherited(arguments);
this.computeCellSize();
this.drawGrid();
this.drawPieces();
this.$.canvas.update();
},
drawGrid: function() {
for (var line=0; line < 8; line++){
for (var col=0; col < 8; col++){
var compName = "abcdefgh"[col] + (8-line);
var bgcolor = ((line+col)%2 == 0) ? "#CF3" : "#630";
this.$.canvas.createComponent({
kind: "enyo.canvas.Rectangle",
bounds:{t:(line*this.cellsize), l:(col*this.cellsize), w:this.cellsize, h:this.cellsize},
color:bgcolor,
name: compName
});
}
}
},
drawPieces: function(){
var x = 3*this.cellsize;
var y = 4*this.cellsize;
this.$.canvas.createComponent({
kind: "enyo.canvas.Image",
bounds:{t:y, l:x, w:this.cellsize, h:this.cellsize},
src: "assets/wn.svg"
});
},
computeCellSize: function(){
var screenWidth = enyo.dom.getWindowWidth();
var screenHeight = enyo.dom.getWindowHeight();
var minSize = screenWidth < screenHeight ? screenWidth : screenHeight;
this.cellsize = Math.floor(minSize * 1.0 / 8);
this.$.canvas.setAttribute('width', 8*this.cellsize);
this.$.canvas.setAttribute('height', 8*this.cellsize);
}
});
As you should have noticed, I am making grid cells dynamically (this works well) with the method drawGrid(), but the method drawPieces() doesn't seems to work, as I can't see the picture.
Apologizing for not using JsFiddle, it is because I don't know how to adapt my project so that it can fit in it.
It may be that the image tag is not loading in time to be rendered. You might want to try delaying the call to update the canvas for a short time period. I modified the fiddle previously posted and included an SVG image. It doesn't render the first time for me, but when re-run it will render because it's in the browser cache:
http://jsfiddle.net/RoySutton/cxx57780/1
I wrapped your code above in the following to get it going on jsFiddle:
enyo.ready(function() {
...
new enyo.Application({view: "PositionView"});
});
My previous thought about the width/height of the SVG doesn't seem to be correct so I have removed it from this answer.

Changing Google Maps v3 MarkerClustererPlus Title

I am trying to dynamically set the cluster title, rollover text, of clustered icons. I want the cluster count/total to be used in the rollover text.
Through console.log I am able to see that the title has been changed to that set for var txt. It also works with alert( txt ). The default title for a cluster is "" and does not seem to be getting updated and stays at the default value.
Currently I am setting the title in google.maps.event.addListener( markerClusterer, 'mouseover', function( cluster ) {}).
I'm thinking that my code continues to execute and that might be the reason I don't see the change but I haven't been able to narrow it down.
var latlng = new google.maps.LatLng( lat, lng );
var qs = location.search;
var options = {
zoom: 17,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
}
};
map = new google.maps.Map( mapId[0], options );
google.maps.event.addListener( map, 'idle', function() {
var bounds = map.getBounds();
downloadXML( ABSPATH + 'xml/maps/markers.php' + qs + '&bounds=' + bounds, function( data ) {
var xml = parseXml( data );
var markers = xml.documentElement.getElementsByTagName( "marker" );
var markerArray = [];
for ( var i = 0; i < markers.length; i++ ) {
var attributes = getMarkerAttributes( markers[i] );
var marker = createMarker( attributes );
// Add marker to marker array
markerArray.push(marker);
}
// Define the marker clusterer
var clusterOptions = {
zoomOnClick: false,
gridSize: 1
}
var markerClusterer = new MarkerClusterer( map, markerArray, clusterOptions );
// Listen for a cluster to be clicked
google.maps.event.addListener( markerClusterer, 'clusterclick', function( cluster ) {
combineInfoWindows( cluster );
});
// Listen for a cluster to be hovered and set title
google.maps.event.addListener( markerClusterer, 'mouseover', function( cluster ) {
var txt = 'There are ' + cluster.getSize() + ' properties at this location.';
//alert( txt );
console.log( cluster );
markerClusterer.setTitle( txt );
});
}); // downloadXML
}); // google.maps.event.addListener( map, 'idle', ... )
Any help would be greatly appreciated. Thanks!
EDIT: 1
I have a solution based on the suggested solution by Rick.
I have modified the onAdd method.
/**
* Adds the icon to the DOM.
*/
ClusterIcon.prototype.onAdd = function () {
var cClusterIcon = this;
// MY CHANGES - START
this.cluster_.markerClusterer_.title_ = 'There are ' + this.cluster_.getSize() + ' properties at this location.';
// MY CHANGES - END
this.div_ = document.createElement("div");
if (this.visible_) {
this.show();
}
...
};
EDIT: 2 - FINAL SOLUTION
Moved changes to show method versus previous onAdd method as Rick had suggested. Change is made in a file outside of the original source file for MarkerClustererPlus.
/**
* Positions and shows the icon.
*/
ClusterIcon.prototype.show = function () {
if (this.div_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
if (this.cluster_.printable_) {
// (Would like to use "width: inherit;" below, but doesn't work with MSIE)
this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_ + "px;'>" + this.sums_.text + "</div>";
} else {
this.div_.innerHTML = this.sums_.text;
}
//this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
// MY SOLUTION BELOW
this.div_.title = 'There are ' + this.cluster_.getSize() + ' properties at this location.';
this.div_.style.display = "";
}
this.visible_ = true;
};
Are you using this for clustering markers? Did you extend it to make your own setTitle function? If not, You'll have to make your own label. It's not actually a marker per se.
Edit: Didn't know this existed.
The cluster icons just pull the title from the MCOptions. I don't see where ClusterIcon or Cluster has a setTitle function, so I'd think the best bet would be overriding the ClusterIcon show prototype yourself and setting it there.
> ClusterIcon.prototype.show =
> function () { if (this.div_) {
> var pos = this.getPosFromLatLng_(this.center_);
> this.div_.style.cssText = this.createCss(pos);
> if (this.cluster_.printable_) {
> // (Would like to use "width: inherit;" below, but doesn't work with MSIE)
> this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_
> + "px;'>" + this.sums_.text + "</div>";
> } else {
> this.div_.innerHTML = this.sums_.text;
> }
> this.div_.title = **** Your stuff here ***
> this.div_.style.display = ""; } this.visible_ = true; };
Your problem is that you are trying to assign the mouseover event listener (where you set the title) to a MarkerClusterer, but to define a mouseover listener, you have to pass a Cluster.
There is a MarkerClusterer.getClusters() function that will return an Array of the Cluster instances. You want to loop over that Array and pass an instance of Cluster to your mouseover event listeners. If you check the reference doc and scroll down to the MarkerClusterer Events section of the doc, the row for mouseover defines the Argument to be:
c:Cluster
Which is in contrast to events like clusteringbegin and clusteringend, which define the Argument to be:
mc:MarkerClusterer
Having said all that, I'm not sure there is an easy way to set the title for each Cluster. That class does not have a setTitle function. The setTitle on the MarkerClusterer just applies the title to all of the Cluster instances. And I've double checked in JavaScript; there is no setTitle function on the Cluster class. Your best option right now seems to be to dynamically create the content you want to display within the mouseover handler for each Cluster. You could create an InfoBox and then close it on a Cluster mouseoet event. Not the simplest solution ever, but it will get you where you want to be.
I know this is an old question, but it ranked high on Google for my search. Anyway, here is what I did thanks to tips from this page:
google.maps.event.addListener(markerCluster, 'mouseover', function (c) {
if(c.clusterIcon_.div_){
c.clusterIcon_.div_.title = c.getSize() + ' businesses in this area';
}
});
I can't guarantee it will be compatible with future versions of MarkerClusterer Plus since I'm using "private" properties clusterIcon_ and div_.

Resources